Merge "Revert "Enfore cross user permission to getPackagesForUid"" into sc-dev
diff --git a/Android.bp b/Android.bp
index d170913..9655daf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -536,7 +536,7 @@
         "android.hardware.vibrator-V1.3-java",
         "android.security.apc-java",
         "android.security.authorization-java",
-        "android.system.keystore2-java",
+        "android.system.keystore2-V1-java",
         "android.system.suspend.control.internal-java",
         "cameraprotosnano",
         "devicepolicyprotosnano",
@@ -632,6 +632,7 @@
     ],
     sdk_version: "core_platform",
     static_libs: [
+        "bouncycastle-repackaged-unbundled",
         "framework-internal-utils",
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
         // in favor of an API stubs dependency in java_library "framework" below.
@@ -759,6 +760,7 @@
     srcs: [
         ":ipconnectivity-proto-src",
         ":libstats_atom_enum_protos",
+        ":libtombstone_proto-src",
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
         ":service-permission-protos",
@@ -1294,6 +1296,18 @@
     ],
 }
 
+python_binary_host {
+    name: "update_font_metadata",
+    defaults: ["base_default"],
+    main: "tools/fonts/update_font_metadata.py",
+    srcs: [
+        "tools/fonts/update_font_metadata.py",
+    ],
+    libs: [
+        "fontTools",
+    ],
+}
+
 filegroup {
     name: "framework-media-annotation-srcs",
     srcs: [
diff --git a/METADATA b/METADATA
index d97975c..95577d8 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,4 @@
 third_party {
-  license_type: NOTICE
+  # would be NOTICE save for libs/usb/tests/accessorytest/f_accessory.h
+  license_type: RESTRICTED
 }
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index cdf5df6c..30ed7de 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -8,6 +8,7 @@
                cmds/input/
                cmds/uinput/
                core/jni/
+               libs/hwui/
                libs/input/
                native/
                services/core/jni/
diff --git a/StubLibraries.bp b/StubLibraries.bp
index d1ad189..86364af 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -82,12 +82,13 @@
         "android.hardware.vibrator-V1.3-java",
         "framework-protos",
         "stable.core.platform.api.stubs",
-        // There are a few classes from modules used as type arguments that
-        // need to be resolved by metalava. For now, we can use a previously
-        // finalized stub library to resolve them. If a new class gets added,
-        // this may be need to be revisited to use a manually maintained stub
-        // library with empty classes in order to resolve those references.
-        "sdk_system_30_android",
+        // There are a few classes from modules used by the core that
+        // need to be resolved by metalava. We use a prebuilt stub of the
+        // full sdk to ensure we can resolve them. If a new class gets added,
+        // the prebuilts/sdk/current needs to be updated.
+        "sdk_system_current_android",
+        // NOTE: The below can be removed once the prebuilt stub contains IKE.
+        "sdk_system_current_android.net.ipsec.ike",
     ],
     high_mem: true, // Lots of sources => high memory use, see b/170701554
     installable: false,
@@ -116,7 +117,7 @@
         last_released: {
             api_file: ":android-non-updatable.api.public.latest",
             removed_api_file: ":android-non-updatable-removed.api.public.latest",
-            baseline_file: ":public-api-incompatibilities-with-last-released",
+            baseline_file: ":android-incompatibilities.api.public.latest",
         },
         api_lint: {
             enabled: true,
@@ -168,7 +169,7 @@
         last_released: {
             api_file: ":android-non-updatable.api.system.latest",
             removed_api_file: ":android-non-updatable-removed.api.system.latest",
-            baseline_file: ":system-api-incompatibilities-with-last-released"
+            baseline_file: ":android-incompatibilities.api.system.latest"
         },
         api_lint: {
             enabled: true,
@@ -404,7 +405,11 @@
         "android_defaults_stubs_current",
         "android_stubs_dists_default",
     ],
-    libs: ["sdk_system_29_android"],
+    libs: [
+        "sdk_system_current_android",
+        // NOTE: The below can be removed once the prebuilt stub contains IKE.
+        "sdk_system_current_android.net.ipsec.ike",
+    ],
     static_libs: ["art.module.public.api.stubs"],
     dist: {
         dir: "apistubs/android/module-lib",
diff --git a/apct-tests/perftests/core/OWNERS b/apct-tests/perftests/core/OWNERS
new file mode 100644
index 0000000..18486af
--- /dev/null
+++ b/apct-tests/perftests/core/OWNERS
@@ -0,0 +1 @@
+include /graphics/java/android/graphics/fonts/OWNERS
diff --git a/apct-tests/perftests/core/src/android/os/OWNERS b/apct-tests/perftests/core/src/android/os/OWNERS
new file mode 100644
index 0000000..a1719c9
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/OWNERS
@@ -0,0 +1 @@
+per-file PackageParsingPerfTest.kt = file:/services/core/java/com/android/server/pm/OWNERS
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
index 9e519f7..1d94d7e 100644
--- a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt
@@ -178,7 +178,7 @@
             // For testing, just disable enforcement to avoid hooking up to compat framework
             ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
         }
-        val parser = ParsingPackageUtils(false, null, null,
+        val parser = ParsingPackageUtils(false, null, null, emptyList(),
             object : ParsingPackageUtils.Callback {
                 override fun hasFeature(feature: String) = true
 
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index c37f6d9..a2dc1c2 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -19,14 +19,12 @@
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 
-import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.perftests.utils.ManualBenchmarkState;
 import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
 import android.perftests.utils.PerfManualStatusReporter;
 import android.view.Display;
-import android.view.DisplayCutout;
 import android.view.IWindowSession;
 import android.view.InputChannel;
 import android.view.InsetsSourceControl;
@@ -85,9 +83,6 @@
     private static class TestWindow extends BaseIWindow {
         final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
         final InsetsState mRequestedVisibility = new InsetsState();
-        final Rect mOutFrame = new Rect();
-        final DisplayCutout.ParcelableWrapper mOutDisplayCutout =
-                new DisplayCutout.ParcelableWrapper();
         final InsetsState mOutInsetsState = new InsetsState();
         final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
 
@@ -107,7 +102,7 @@
 
                 long startTime = SystemClock.elapsedRealtimeNanos();
                 session.addToDisplay(this, mLayoutParams, View.VISIBLE,
-                        Display.DEFAULT_DISPLAY, mRequestedVisibility, mOutFrame, inputChannel,
+                        Display.DEFAULT_DISPLAY, mRequestedVisibility, inputChannel,
                         mOutInsetsState, mOutControls);
                 final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
                 state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/apex/Android.bp b/apex/Android.bp
index 77ff6db..8310ba7 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -15,185 +15,3 @@
 package {
     default_visibility: [":__subpackages__"],
 }
-
-mainline_stubs_args =
-    "--error UnhiddenSystemApi " +
-    "--hide BroadcastBehavior " +
-    "--hide CallbackInterface " +
-    "--hide DeprecationMismatch " +
-    "--hide HiddenSuperclass " +
-    "--hide HiddenTypedefConstant " +
-    "--hide HiddenTypeParameter " +
-    "--hide MissingPermission " +
-    "--hide RequiresPermission " +
-    "--hide SdkConstant " +
-    "--hide Todo " +
-    "--hide Typo " +
-    "--hide UnavailableSymbol "
-
-// TODO: modularize this so not every module has the same whitelist
-framework_packages_to_document = [
-    "android",
-    "dalvik",
-    "java",
-    "javax",
-    "junit",
-    "org.apache.http",
-    "org.json",
-    "org.w3c.dom",
-    "org.xml.sax",
-    "org.xmlpull",
-]
-
-// TODO: remove the hiding when server classes are cleaned up.
-mainline_framework_stubs_args =
-    mainline_stubs_args +
-    "--hide-package com.android.server "
-
-priv_apps = " " +
-    "--show-annotation android.annotation.SystemApi\\(" +
-        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
-    "\\) "
-
-module_libs = " " +
-    " --show-annotation android.annotation.SystemApi\\(" +
-        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
-    "\\)" +
-    " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
-        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
-    "\\) "
-
-mainline_service_stubs_args =
-    mainline_stubs_args +
-    "--show-annotation android.annotation.SystemApi\\(" +
-        "client=android.annotation.SystemApi.Client.SYSTEM_SERVER" +
-    "\\) " +
-    "--hide-annotation android.annotation.Hide " +
-    "--hide InternalClasses " // com.android.* classes are okay in this interface
-
-// Defaults common to all mainline module java_sdk_library instances.
-java_defaults {
-    name: "framework-module-common-defaults",
-
-    // Additional annotations used for compiling both the implementation and the
-    // stubs libraries.
-    libs: ["framework-annotations-lib"],
-
-    // Framework modules are not generally shared libraries, i.e. they are not
-    // intended, and must not be allowed, to be used in a <uses-library> manifest
-    // entry.
-    shared_library: false,
-
-    // Prevent dependencies that do not specify an sdk_version from accessing the
-    // implementation library by default and force them to use stubs instead.
-    default_to_stubs: true,
-
-    // Enable api lint. This will eventually become the default for java_sdk_library
-    // but it cannot yet be turned on because some usages have not been cleaned up.
-    // TODO(b/156126315) - Remove when no longer needed.
-    api_lint: {
-        enabled: true,
-    },
-
-    // The API scope specific properties.
-    public: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
-
-    // Configure framework module specific metalava options.
-    droiddoc_options: [mainline_stubs_args],
-
-    annotations_enabled: true,
-
-    // Allow access to the stubs from anywhere
-    visibility: ["//visibility:public"],
-    stubs_library_visibility: ["//visibility:public"],
-
-    // Hide impl library and stub sources
-    impl_library_visibility: [
-        ":__pkg__",
-        "//frameworks/base", // For framework-all
-    ],
-    stubs_source_visibility: ["//visibility:private"],
-
-    defaults_visibility: ["//visibility:private"],
-
-    // Collates API usages from each module for further analysis.
-    plugins: ["java_api_finder"],
-
-    // Mainline modules should only rely on 'module_lib' APIs provided by other modules
-    // and the non updatable parts of the platform.
-    sdk_version: "module_current",
-}
-
-// Defaults for mainline module provided java_sdk_library instances.
-java_defaults {
-    name: "framework-module-defaults",
-    defaults: ["framework-module-common-defaults"],
-
-    system: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
-    module_lib: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
-    defaults_visibility: [
-        ":__subpackages__",
-        "//frameworks/base/libs/hwui",
-        "//frameworks/base/wifi",
-        "//packages/modules:__subpackages__",
-        "//packages/providers/MediaProvider:__subpackages__",
-    ],
-}
-
-// Defaults for mainline module system server provided java_sdk_library instances.
-java_defaults {
-    name: "framework-system-server-module-defaults",
-    defaults: ["framework-module-common-defaults"],
-
-    system_server: {
-        enabled: true,
-        sdk_version: "module_current",
-    },
-    defaults_visibility: [
-        ":__subpackages__",
-        "//packages/modules:__subpackages__",
-    ],
-}
-
-stubs_defaults {
-    name: "service-module-stubs-srcs-defaults",
-    args: mainline_service_stubs_args,
-    installable: false,
-    annotations_enabled: true,
-    merge_annotations_dirs: [
-        "metalava-manual",
-    ],
-    filter_packages: ["com.android."],
-    check_api: {
-        current: {
-            api_file: "api/current.txt",
-            removed_api_file: "api/removed.txt",
-        },
-        api_lint: {
-            enabled: true,
-        },
-    },
-    dist: {
-        targets: ["sdk", "win_sdk"],
-        dir: "apistubs/android/system-server/api",
-    },
-}
-
-// Empty for now, but a convenient place to add rules for all
-// module java_library system_server stub libs.
-java_defaults {
-    name: "service-module-stubs-defaults",
-    dist: {
-        targets: ["sdk", "win_sdk"],
-        dir: "apistubs/android/system-server",
-    },
-}
diff --git a/apex/OWNERS b/apex/OWNERS
index bde2bec..b3e81b9 100644
--- a/apex/OWNERS
+++ b/apex/OWNERS
@@ -1,8 +1 @@
-# Mainline modularization team
-
-andreionea@google.com
-dariofreni@google.com
-hansson@google.com
-mathewi@google.com
-pedroql@google.com
-satayev@google.com
+file:platform/packages/modules/common:/OWNERS
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index b1394c1..a3b8013 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -143,11 +143,11 @@
     method public void close();
     method public void getByUri(@NonNull android.app.appsearch.GetByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,android.app.appsearch.GenericDocument>);
     method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<android.app.appsearch.AppSearchSchema>>>);
-    method public void putDocuments(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
-    method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
-    method public void removeByQuery(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
-    method public void removeByUri(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
+    method public void put(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
+    method public void remove(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>);
+    method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
     method @NonNull public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>);
+    method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
     method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
   }
 
@@ -216,7 +216,7 @@
 
   public class GlobalSearchSession implements java.io.Closeable {
     method public void close();
-    method @NonNull public android.app.appsearch.SearchResults query(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
+    method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor);
   }
 
   public class PackageIdentifier {
@@ -226,13 +226,13 @@
   }
 
   public final class PutDocumentsRequest {
-    method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getDocuments();
+    method @NonNull public java.util.List<android.app.appsearch.GenericDocument> getGenericDocuments();
   }
 
   public static final class PutDocumentsRequest.Builder {
     ctor public PutDocumentsRequest.Builder();
-    method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull android.app.appsearch.GenericDocument...);
-    method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocument(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
+    method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull android.app.appsearch.GenericDocument...);
+    method @NonNull public android.app.appsearch.PutDocumentsRequest.Builder addGenericDocuments(@NonNull java.util.Collection<? extends android.app.appsearch.GenericDocument>);
     method @NonNull public android.app.appsearch.PutDocumentsRequest build();
   }
 
@@ -290,14 +290,14 @@
   }
 
   public final class SearchSpec {
+    method @NonNull public java.util.List<java.lang.String> getFilterNamespaces();
     method @NonNull public java.util.List<java.lang.String> getFilterPackageNames();
+    method @NonNull public java.util.List<java.lang.String> getFilterSchemas();
     method public int getMaxSnippetSize();
-    method @NonNull public java.util.List<java.lang.String> getNamespaces();
     method public int getOrder();
     method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections();
     method public int getRankingStrategy();
     method public int getResultCountPerPage();
-    method @NonNull public java.util.List<java.lang.String> getSchemaTypes();
     method public int getSnippetCount();
     method public int getSnippetCountPerProperty();
     method public int getTermMatch();
@@ -316,14 +316,14 @@
 
   public static final class SearchSpec.Builder {
     ctor public SearchSpec.Builder();
+    method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.lang.String...);
+    method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterNamespaces(@NonNull java.util.Collection<java.lang.String>);
     method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.lang.String...);
     method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterPackageNames(@NonNull java.util.Collection<java.lang.String>);
-    method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.lang.String...);
-    method @NonNull public android.app.appsearch.SearchSpec.Builder addNamespace(@NonNull java.util.Collection<java.lang.String>);
+    method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.lang.String...);
+    method @NonNull public android.app.appsearch.SearchSpec.Builder addFilterSchemas(@NonNull java.util.Collection<java.lang.String>);
     method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.lang.String...);
     method @NonNull public android.app.appsearch.SearchSpec.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>);
-    method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.lang.String...);
-    method @NonNull public android.app.appsearch.SearchSpec.Builder addSchemaType(@NonNull java.util.Collection<java.lang.String>);
     method @NonNull public android.app.appsearch.SearchSpec build();
     method @NonNull public android.app.appsearch.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_SIZE_LIMIT) int);
     method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int);
@@ -344,8 +344,8 @@
 
   public static final class SetSchemaRequest.Builder {
     ctor public SetSchemaRequest.Builder();
-    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull android.app.appsearch.AppSearchSchema...);
-    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchema(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
+    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull android.app.appsearch.AppSearchSchema...);
+    method @NonNull public android.app.appsearch.SetSchemaRequest.Builder addSchemas(@NonNull java.util.Collection<android.app.appsearch.AppSearchSchema>);
     method @NonNull public android.app.appsearch.SetSchemaRequest build();
     method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean);
     method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.AppSearchSchema.Migrator);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index 814800e..69d4e53 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -270,12 +270,12 @@
      *     they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
      * @throws RuntimeException If an error occurred during the execution.
      * @hide
-     * @deprecated use {@link AppSearchSession#putDocuments} instead.
+     * @deprecated use {@link AppSearchSession#put} instead.
      */
     public AppSearchBatchResult<String, Void> putDocuments(@NonNull PutDocumentsRequest request) {
         // TODO(b/146386470): Transmit these documents as a RemoteStream instead of sending them in
         // one big list.
-        List<GenericDocument> documents = request.getDocuments();
+        List<GenericDocument> documents = request.getGenericDocuments();
         List<Bundle> documentBundles = new ArrayList<>(documents.size());
         for (int i = 0; i < documents.size(); i++) {
             documentBundles.add(documents.get(i).getBundle());
@@ -330,7 +330,7 @@
                     DEFAULT_DATABASE_NAME,
                     request.getNamespace(),
                     uris,
-                    request.getProjectionsVisibleToPackagesInternal(),
+                    request.getProjectionsInternal(),
                     mContext.getUserId(),
                     new IAppSearchBatchResultCallback.Stub() {
                         public void onResult(AppSearchBatchResult result) {
@@ -465,7 +465,7 @@
      *     {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
      * @throws RuntimeException If an error occurred during the execution.
      * @hide
-     * @deprecated use {@link AppSearchSession#removeByUri} instead.
+     * @deprecated use {@link AppSearchSession#remove} instead.
      */
     public AppSearchBatchResult<String, Void> removeByUri(@NonNull RemoveByUriRequest request) {
         List<String> uris = new ArrayList<>(request.getUris());
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 670f8b9..8723515 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -102,7 +102,7 @@
     }
 
     /**
-     * Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
+     * Sets the schema that will be used by documents provided to the {@link #put} method.
      *
      * <p>The schema provided here is compared to the stored copy of the schema previously supplied
      * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -268,7 +268,7 @@
      *                 {@link Throwable} if an unexpected internal error occurred in AppSearch
      *                 service.
      */
-    public void putDocuments(
+    public void put(
             @NonNull PutDocumentsRequest request,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull BatchResultCallback<String, Void> callback) {
@@ -276,7 +276,7 @@
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
-        List<GenericDocument> documents = request.getDocuments();
+        List<GenericDocument> documents = request.getGenericDocuments();
         List<Bundle> documentBundles = new ArrayList<>(documents.size());
         for (int i = 0; i < documents.size(); i++) {
             documentBundles.add(documents.get(i).getBundle());
@@ -327,7 +327,7 @@
                     mDatabaseName,
                     request.getNamespace(),
                     new ArrayList<>(request.getUris()),
-                    request.getProjectionsVisibleToPackagesInternal(),
+                    request.getProjectionsInternal(),
                     mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
                         public void onResult(AppSearchBatchResult result) {
@@ -423,7 +423,7 @@
      * @return The search result of performing this operation.
      */
     @NonNull
-    public SearchResults query(
+    public SearchResults search(
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec,
             @NonNull @CallbackExecutor Executor executor) {
@@ -441,7 +441,7 @@
      * <p>A usage report represents an event in which a user interacted with or viewed a document.
      *
      * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
-     * metrics for that particular document. These metrics are used for ordering {@link #query}
+     * metrics for that particular document. These metrics are used for ordering {@link #search}
      * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and
      * {@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
      *
@@ -494,7 +494,7 @@
      *                 {@link Throwable} if an unexpected internal error occurred in AppSearch
      *                 service.
      */
-    public void removeByUri(
+    public void remove(
             @NonNull RemoveByUriRequest request,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull BatchResultCallback<String, Void> callback) {
@@ -523,7 +523,8 @@
     /**
      * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
      * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
-     * {@link SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
+     * {@link SearchSpec.Builder#addFilterNamespaces} and
+     * {@link SearchSpec.Builder#addFilterSchemas}.
      *
      * <p> An empty {@code queryExpression} matches all documents.
      *
@@ -539,7 +540,8 @@
      *                        the operation succeeds, the callback will be invoked with
      *                        {@code null}.
      */
-    public void removeByQuery(@NonNull String queryExpression,
+    public void remove(
+            @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<AppSearchResult<Void>> callback) {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
index 6bb8554..8651834 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java
@@ -134,7 +134,7 @@
      * @return The search result of performing this operation.
      */
     @NonNull
-    public SearchResults query(
+    public SearchResults search(
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec,
             @NonNull @CallbackExecutor Executor executor) {
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 2e00ff2..e94b3b2 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -141,10 +141,6 @@
         }
 
         /** Adds a property to the given type. */
-        // TODO(b/171360120): MissingGetterMatchingBuilder expects a method called getPropertys, but
-        //  we provide the (correct) method getProperties. Once the bug referenced in this TODO is
-        //  fixed, remove this SuppressLint.
-        @SuppressLint("MissingGetterMatchingBuilder")
         @NonNull
         public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 2f02808..4c11514 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -22,6 +22,7 @@
 import android.annotation.SuppressLint;
 import android.app.appsearch.util.BundleUtil;
 import android.os.Bundle;
+import android.os.Parcelable;
 import android.util.Log;
 
 import com.android.internal.util.Preconditions;
@@ -37,9 +38,9 @@
  *
  * <p>Documents are constructed via {@link GenericDocument.Builder}.
  *
- * @see AppSearchSession#putDocuments
+ * @see AppSearchSession#put
  * @see AppSearchSession#getByUri
- * @see AppSearchSession#query
+ * @see AppSearchSession#search
  */
 public class GenericDocument {
     private static final String TAG = "AppSearchGenericDocumen";
@@ -210,7 +211,7 @@
         Object property = mProperties.get(key);
         if (property instanceof ArrayList) {
             return getPropertyBytesArray(key);
-        } else if (property instanceof Bundle[]) {
+        } else if (property instanceof Parcelable[]) {
             return getPropertyDocumentArray(key);
         }
         return property;
@@ -436,7 +437,7 @@
     @Nullable
     public GenericDocument[] getPropertyDocumentArray(@NonNull String key) {
         Preconditions.checkNotNull(key);
-        Bundle[] bundles = getAndCastPropertyArray(key, Bundle[].class);
+        Parcelable[] bundles = getAndCastPropertyArray(key, Parcelable[].class);
         if (bundles == null || bundles.length == 0) {
             return null;
         }
@@ -446,7 +447,18 @@
                 Log.e(TAG, "The inner bundle is null at " + i + ", for key: " + key);
                 continue;
             }
-            documents[i] = new GenericDocument(bundles[i]);
+            if (!(bundles[i] instanceof Bundle)) {
+                Log.e(
+                        TAG,
+                        "The inner element at "
+                                + i
+                                + " is a "
+                                + bundles[i].getClass()
+                                + ", not a Bundle for key: "
+                                + key);
+                continue;
+            }
+            documents[i] = new GenericDocument((Bundle) bundles[i]);
         }
         return documents;
     }
@@ -574,8 +586,8 @@
          * @param schemaType The schema type of the {@link GenericDocument}. The passed-in {@code
          *     schemaType} must be defined using {@link AppSearchSession#setSchema} prior to
          *     inserting a document of this {@code schemaType} into the AppSearch index using {@link
-         *     AppSearchSession#putDocuments}. Otherwise, the document will be rejected by {@link
-         *     AppSearchSession#putDocuments}.
+         *     AppSearchSession#put}. Otherwise, the document will be rejected by {@link
+         *     AppSearchSession#put}.
          */
         @SuppressWarnings("unchecked")
         public Builder(@NonNull String uri, @NonNull String schemaType) {
@@ -817,7 +829,7 @@
 
         private void putInPropertyBundle(@NonNull String key, @NonNull GenericDocument[] values) {
             validateRepeatedPropertyLength(key, values.length);
-            Bundle[] documentBundles = new Bundle[values.length];
+            Parcelable[] documentBundles = new Parcelable[values.length];
             for (int i = 0; i < values.length; i++) {
                 if (values[i] == null) {
                     throw new IllegalArgumentException("The document at " + i + " is null.");
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
index 38e0046..0fcf061 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -96,7 +96,7 @@
      * @hide
      */
     @NonNull
-    public Map<String, List<String>> getProjectionsVisibleToPackagesInternal() {
+    public Map<String, List<String>> getProjectionsInternal() {
         return mTypePropertyPathsMap;
     }
 
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
index 0f141d6..05b2128 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
@@ -16,8 +16,8 @@
 
 package android.app.appsearch;
 
+
 import android.annotation.NonNull;
-import android.annotation.SuppressLint;
 
 import com.android.internal.util.Preconditions;
 
@@ -41,7 +41,7 @@
 
     /** Returns the documents that are part of this request. */
     @NonNull
-    public List<GenericDocument> getDocuments() {
+    public List<GenericDocument> getGenericDocuments() {
         return Collections.unmodifiableList(mDocuments);
     }
 
@@ -55,17 +55,15 @@
         private boolean mBuilt = false;
 
         /** Adds one or more {@link GenericDocument} objects to the request. */
-        @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
         @NonNull
-        public Builder addGenericDocument(@NonNull GenericDocument... documents) {
+        public Builder addGenericDocuments(@NonNull GenericDocument... documents) {
             Preconditions.checkNotNull(documents);
-            return addGenericDocument(Arrays.asList(documents));
+            return addGenericDocuments(Arrays.asList(documents));
         }
 
         /** Adds a collection of {@link GenericDocument} objects to the request. */
-        @SuppressLint("MissingGetterMatchingBuilder") // Merged list available from getDocuments()
         @NonNull
-        public Builder addGenericDocument(
+        public Builder addGenericDocuments(
                 @NonNull Collection<? extends GenericDocument> documents) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkNotNull(documents);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
index a04da34..2104198d 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -29,7 +29,7 @@
 /**
  * Encapsulates a request to remove documents by namespace and URI.
  *
- * @see AppSearchSession#removeByUri
+ * @see AppSearchSession#remove
  */
 public final class RemoveByUriRequest {
     private final String mNamespace;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
index 963062c..f72f8e1 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.SuppressLint;
 import android.app.appsearch.exceptions.IllegalSearchSpecException;
 import android.os.Bundle;
 import android.util.ArrayMap;
@@ -49,7 +48,7 @@
     public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
 
     static final String TERM_MATCH_TYPE_FIELD = "termMatchType";
-    static final String SCHEMA_TYPE_FIELD = "schemaType";
+    static final String SCHEMA_FIELD = "schema";
     static final String NAMESPACE_FIELD = "namespace";
     static final String PACKAGE_NAME_FIELD = "packageName";
     static final String NUM_PER_PAGE_FIELD = "numPerPage";
@@ -171,12 +170,12 @@
      * <p>If empty, the query will search over all schema types.
      */
     @NonNull
-    public List<String> getSchemaTypes() {
-        List<String> schemaTypes = mBundle.getStringArrayList(SCHEMA_TYPE_FIELD);
-        if (schemaTypes == null) {
+    public List<String> getFilterSchemas() {
+        List<String> schemas = mBundle.getStringArrayList(SCHEMA_FIELD);
+        if (schemas == null) {
             return Collections.emptyList();
         }
-        return Collections.unmodifiableList(schemaTypes);
+        return Collections.unmodifiableList(schemas);
     }
 
     /**
@@ -185,7 +184,7 @@
      * <p>If empty, the query will search over all namespaces.
      */
     @NonNull
-    public List<String> getNamespaces() {
+    public List<String> getFilterNamespaces() {
         List<String> namespaces = mBundle.getStringArrayList(NAMESPACE_FIELD);
         if (namespaces == null) {
             return Collections.emptyList();
@@ -209,24 +208,6 @@
         return Collections.unmodifiableList(packageNames);
     }
 
-    /**
-     * Returns the list of package names to search over.
-     *
-     * <p>If unset, the query will search over all packages that the caller has access to. If
-     * package names are specified which caller doesn't have access to, then those package names
-     * will be ignored.
-     *
-     * @hide
-     */
-    @NonNull
-    public List<String> getPackageNames() {
-        List<String> packageNames = mBundle.getStringArrayList(PACKAGE_NAME_FIELD);
-        if (packageNames == null) {
-            return Collections.emptyList();
-        }
-        return Collections.unmodifiableList(packageNames);
-    }
-
     /** Returns the number of results per page in the result set. */
     public int getResultCountPerPage() {
         return mBundle.getInt(NUM_PER_PAGE_FIELD, DEFAULT_NUM_PER_PAGE);
@@ -270,11 +251,10 @@
     @NonNull
     public Map<String, List<String>> getProjections() {
         Bundle typePropertyPathsBundle = mBundle.getBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD);
-        Set<String> schemaTypes = typePropertyPathsBundle.keySet();
-        Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemaTypes.size());
-        for (String schemaType : schemaTypes) {
-            typePropertyPathsMap.put(
-                    schemaType, typePropertyPathsBundle.getStringArrayList(schemaType));
+        Set<String> schemas = typePropertyPathsBundle.keySet();
+        Map<String, List<String>> typePropertyPathsMap = new ArrayMap<>(schemas.size());
+        for (String schema : schemas) {
+            typePropertyPathsMap.put(schema, typePropertyPathsBundle.getStringArrayList(schema));
         }
         return typePropertyPathsMap;
     }
@@ -283,7 +263,7 @@
     public static final class Builder {
 
         private final Bundle mBundle;
-        private final ArrayList<String> mSchemaTypes = new ArrayList<>();
+        private final ArrayList<String> mSchemas = new ArrayList<>();
         private final ArrayList<String> mNamespaces = new ArrayList<>();
         private final ArrayList<String> mPackageNames = new ArrayList<>();
         private final Bundle mProjectionTypePropertyMasks = new Bundle();
@@ -312,10 +292,10 @@
          * <p>If unset, the query will search over all schema types.
          */
         @NonNull
-        public Builder addSchemaType(@NonNull String... schemaTypes) {
-            Preconditions.checkNotNull(schemaTypes);
+        public Builder addFilterSchemas(@NonNull String... schemas) {
+            Preconditions.checkNotNull(schemas);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            return addSchemaType(Arrays.asList(schemaTypes));
+            return addFilterSchemas(Arrays.asList(schemas));
         }
 
         /**
@@ -325,10 +305,10 @@
          * <p>If unset, the query will search over all schema types.
          */
         @NonNull
-        public Builder addSchemaType(@NonNull Collection<String> schemaTypes) {
-            Preconditions.checkNotNull(schemaTypes);
+        public Builder addFilterSchemas(@NonNull Collection<String> schemas) {
+            Preconditions.checkNotNull(schemas);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mSchemaTypes.addAll(schemaTypes);
+            mSchemas.addAll(schemas);
             return this;
         }
 
@@ -339,10 +319,10 @@
          * <p>If unset, the query will search over all namespaces.
          */
         @NonNull
-        public Builder addNamespace(@NonNull String... namespaces) {
+        public Builder addFilterNamespaces(@NonNull String... namespaces) {
             Preconditions.checkNotNull(namespaces);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            return addNamespace(Arrays.asList(namespaces));
+            return addFilterNamespaces(Arrays.asList(namespaces));
         }
 
         /**
@@ -352,7 +332,7 @@
          * <p>If unset, the query will search over all namespaces.
          */
         @NonNull
-        public Builder addNamespace(@NonNull Collection<String> namespaces) {
+        public Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
             Preconditions.checkNotNull(namespaces);
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             mNamespaces.addAll(namespaces);
@@ -367,9 +347,6 @@
          * package names are specified which caller doesn't have access to, then those package names
          * will be ignored.
          */
-        // Getter is called "getFilterPackageNames" (as opposed to the suggested
-        // "getFilterPackageNameses")
-        @SuppressLint("MissingGetterMatchingBuilder")
         @NonNull
         public Builder addFilterPackageNames(@NonNull String... packageNames) {
             Preconditions.checkNotNull(packageNames);
@@ -385,9 +362,6 @@
          * package names are specified which caller doesn't have access to, then those package names
          * will be ignored.
          */
-        // Getter is called "getFilterPackageNames" (as opposed to the suggested
-        // "getFilterPackageNameses")
-        @SuppressLint("MissingGetterMatchingBuilder")
         @NonNull
         public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
             Preconditions.checkNotNull(packageNames);
@@ -537,7 +511,7 @@
          * type property paths:
          *
          * <pre>{@code
-         * {schemaType: "Email", ["subject", "sender.name", "recipients.name"]}
+         * {schema: "Email", ["subject", "sender.name", "recipients.name"]}
          * }</pre>
          *
          * <p>The above document will be returned as:
@@ -561,9 +535,9 @@
          */
         @NonNull
         public SearchSpec.Builder addProjection(
-                @NonNull String schemaType, @NonNull String... propertyPaths) {
+                @NonNull String schema, @NonNull String... propertyPaths) {
             Preconditions.checkNotNull(propertyPaths);
-            return addProjection(schemaType, Arrays.asList(propertyPaths));
+            return addProjection(schema, Arrays.asList(propertyPaths));
         }
 
         /**
@@ -583,16 +557,16 @@
          */
         @NonNull
         public SearchSpec.Builder addProjection(
-                @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
+                @NonNull String schema, @NonNull Collection<String> propertyPaths) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            Preconditions.checkNotNull(schemaType);
+            Preconditions.checkNotNull(schema);
             Preconditions.checkNotNull(propertyPaths);
             ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
             for (String propertyPath : propertyPaths) {
                 Preconditions.checkNotNull(propertyPath);
                 propertyPathsArrayList.add(propertyPath);
             }
-            mProjectionTypePropertyMasks.putStringArrayList(schemaType, propertyPathsArrayList);
+            mProjectionTypePropertyMasks.putStringArrayList(schema, propertyPathsArrayList);
             return this;
         }
 
@@ -608,7 +582,7 @@
                 throw new IllegalSearchSpecException("Missing termMatchType field.");
             }
             mBundle.putStringArrayList(NAMESPACE_FIELD, mNamespaces);
-            mBundle.putStringArrayList(SCHEMA_TYPE_FIELD, mSchemaTypes);
+            mBundle.putStringArrayList(SCHEMA_FIELD, mSchemas);
             mBundle.putStringArrayList(PACKAGE_NAME_FIELD, mPackageNames);
             mBundle.putBundle(PROJECTION_TYPE_PROPERTY_PATHS_FIELD, mProjectionTypePropertyMasks);
             mBuilt = true;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
index 1486df3..2caa94a5e 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -126,9 +126,9 @@
          * <p>Any documents of these types will be visible on system UI surfaces by default.
          */
         @NonNull
-        public Builder addSchema(@NonNull AppSearchSchema... schemas) {
+        public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
             Preconditions.checkNotNull(schemas);
-            return addSchema(Arrays.asList(schemas));
+            return addSchemas(Arrays.asList(schemas));
         }
 
         /**
@@ -137,7 +137,7 @@
          * <p>Any documents of these types will be visible on system UI surfaces by default.
          */
         @NonNull
-        public Builder addSchema(@NonNull Collection<AppSearchSchema> schemas) {
+        public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
             Preconditions.checkNotNull(schemas);
             mSchemas.addAll(schemas);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index 90a6f60..0328d54 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -179,9 +179,9 @@
 
         /** Adds migratedTypes to the list of migrated schema types. */
         @NonNull
-        public Builder addMigratedType(@NonNull String migratedType) {
+        public Builder addMigratedType(@NonNull Collection<String> migratedTypes) {
             Preconditions.checkState(!mBuilt, "Builder has already been used");
-            mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
+            mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes));
             return this;
         }
 
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index ed55f00..3bbc945 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -64,7 +64,7 @@
     public void onStart() {
         publishBinderService(Context.APP_SEARCH_SERVICE, new Stub());
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
-        mImplInstanceManager = new ImplInstanceManager(getContext());
+        mImplInstanceManager = ImplInstanceManager.getInstance(getContext());
     }
 
     private class Stub extends IAppSearchManager.Stub {
@@ -102,7 +102,8 @@
                     }
                     schemasPackageAccessible.put(entry.getKey(), packageIdentifiers);
                 }
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 impl.setSchema(
                         packageName,
                         databaseName,
@@ -133,7 +134,8 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyCallingPackage(callingUid, packageName);
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName);
                 List<Bundle> schemaBundles = new ArrayList<>(schemas.size());
                 for (int i = 0; i < schemas.size(); i++) {
@@ -166,7 +168,8 @@
                 verifyCallingPackage(callingUid, packageName);
                 AppSearchBatchResult.Builder<String, Void> resultBuilder =
                         new AppSearchBatchResult.Builder<>();
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 for (int i = 0; i < documentBundles.size(); i++) {
                     GenericDocument document = new GenericDocument(documentBundles.get(i));
                     try {
@@ -207,12 +210,18 @@
                 verifyCallingPackage(callingUid, packageName);
                 AppSearchBatchResult.Builder<String, Bundle> resultBuilder =
                         new AppSearchBatchResult.Builder<>();
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 for (int i = 0; i < uris.size(); i++) {
                     String uri = uris.get(i);
                     try {
-                        GenericDocument document = impl.getDocument(packageName, databaseName,
-                                namespace, uri, typePropertyPaths);
+                        GenericDocument document =
+                                impl.getDocument(
+                                        packageName,
+                                        databaseName,
+                                        namespace,
+                                        uri,
+                                        typePropertyPaths);
                         resultBuilder.setSuccess(uri, document.getBundle());
                     } catch (Throwable t) {
                         resultBuilder.setResult(uri, throwableToFailedResult(t));
@@ -245,7 +254,8 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyCallingPackage(callingUid, packageName);
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 SearchResultPage searchResultPage =
                         impl.query(
                                 packageName,
@@ -278,12 +288,14 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyCallingPackage(callingUid, packageName);
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
-                SearchResultPage searchResultPage = impl.globalQuery(
-                        queryExpression,
-                        new SearchSpec(searchSpecBundle),
-                        packageName,
-                        callingUid);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
+                SearchResultPage searchResultPage =
+                        impl.globalQuery(
+                                queryExpression,
+                                new SearchSpec(searchSpecBundle),
+                                packageName,
+                                callingUid);
                 invokeCallbackOnResult(
                         callback,
                         AppSearchResult.newSuccessfulResult(searchResultPage.getBundle()));
@@ -306,7 +318,8 @@
             // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally
             // opened it
             try {
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 SearchResultPage searchResultPage = impl.getNextPage(nextPageToken);
                 invokeCallbackOnResult(
                         callback,
@@ -324,7 +337,8 @@
             int callingUserId = handleIncomingUser(userId, callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 impl.invalidateNextPageToken(nextPageToken);
             } catch (Throwable t) {
                 Log.e(TAG, "Unable to invalidate the query page token", t);
@@ -350,15 +364,11 @@
             int callingUserId = handleIncomingUser(userId, callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
-                impl.reportUsage(
-                        packageName,
-                        databaseName,
-                        namespace,
-                        uri,
-                        usageTimeMillis);
-                invokeCallbackOnResult(callback,
-                        AppSearchResult.newSuccessfulResult(/*result=*/ null));
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
+                impl.reportUsage(packageName, databaseName, namespace, uri, usageTimeMillis);
+                invokeCallbackOnResult(
+                        callback, AppSearchResult.newSuccessfulResult(/*result=*/ null));
             } catch (Throwable t) {
                 invokeCallbackOnError(callback, t);
             } finally {
@@ -385,7 +395,8 @@
                 verifyCallingPackage(callingUid, packageName);
                 AppSearchBatchResult.Builder<String, Void> resultBuilder =
                         new AppSearchBatchResult.Builder<>();
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 for (int i = 0; i < uris.size(); i++) {
                     String uri = uris.get(i);
                     try {
@@ -421,7 +432,8 @@
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 verifyCallingPackage(callingUid, packageName);
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 impl.removeByQuery(
                         packageName,
                         databaseName,
@@ -441,7 +453,8 @@
             int callingUserId = handleIncomingUser(userId, callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                AppSearchImpl impl = mImplInstanceManager.getInstance(callingUserId);
+                AppSearchImpl impl =
+                        mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 impl.persistToDisk();
             } catch (Throwable t) {
                 Log.e(TAG, "Unable to persist the data to disk", t);
@@ -457,7 +470,7 @@
             int callingUserId = handleIncomingUser(userId, callingUid);
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
-                mImplInstanceManager.getInstance(callingUserId);
+                mImplInstanceManager.getAppSearchImpl(getContext(), callingUserId);
                 invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
             } catch (Throwable t) {
                 invokeCallbackOnError(callback, t);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
index fe3c2e1..97b1a8c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java
@@ -41,14 +41,33 @@
 public final class ImplInstanceManager {
     private static final String APP_SEARCH_DIR = "appSearch";
 
-    private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>();
+    private static ImplInstanceManager sImplInstanceManager;
 
-    private final Context mContext;
+    private final SparseArray<AppSearchImpl> mInstances = new SparseArray<>();
     private final String mGlobalQuerierPackage;
 
-    public ImplInstanceManager(@NonNull Context context) {
-        mContext = context;
-        mGlobalQuerierPackage = getGlobalAppSearchDataQuerierPackageName(mContext);
+    private ImplInstanceManager(@NonNull String globalQuerierPackage) {
+        mGlobalQuerierPackage = globalQuerierPackage;
+    }
+
+    /**
+     * Gets an instance of ImplInstanceManager to be used.
+     *
+     * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
+     * existing instance will be returned.
+     */
+    @NonNull
+    public static ImplInstanceManager getInstance(@NonNull Context context) {
+        if (sImplInstanceManager == null) {
+            synchronized (ImplInstanceManager.class) {
+                if (sImplInstanceManager == null) {
+                    sImplInstanceManager =
+                            new ImplInstanceManager(
+                                    getGlobalAppSearchDataQuerierPackageName(context));
+                }
+            }
+        }
+        return sImplInstanceManager;
     }
 
     /**
@@ -57,30 +76,30 @@
      * <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will
      * be created.
      *
+     * @param context The context
      * @param userId The multi-user userId of the device user calling AppSearch
      * @return An initialized {@link AppSearchImpl} for this user
      */
     @NonNull
-    public AppSearchImpl getInstance(@UserIdInt int userId)
+    public AppSearchImpl getAppSearchImpl(@NonNull Context context, @UserIdInt int userId)
             throws AppSearchException {
-        AppSearchImpl instance = sInstances.get(userId);
+        AppSearchImpl instance = mInstances.get(userId);
         if (instance == null) {
             synchronized (ImplInstanceManager.class) {
-                instance = sInstances.get(userId);
+                instance = mInstances.get(userId);
                 if (instance == null) {
-                    instance = createImpl(userId);
-                    sInstances.put(userId, instance);
+                    instance = createImpl(context, userId);
+                    mInstances.put(userId, instance);
                 }
             }
         }
         return instance;
     }
 
-    private AppSearchImpl createImpl(@UserIdInt int userId)
+    private AppSearchImpl createImpl(@NonNull Context context, @UserIdInt int userId)
             throws AppSearchException {
-        File appSearchDir = getAppSearchDir(mContext, userId);
-        return AppSearchImpl.create(
-                appSearchDir, mContext, userId, mGlobalQuerierPackage);
+        File appSearchDir = getAppSearchDir(context, userId);
+        return AppSearchImpl.create(appSearchDir, context, userId, mGlobalQuerierPackage);
     }
 
     private static File getAppSearchDir(@NonNull Context context, @UserIdInt int userId) {
@@ -96,7 +115,8 @@
      *
      * @param context Context of the system service.
      */
-    private static String getGlobalAppSearchDataQuerierPackageName(Context context) {
+    @NonNull
+    private static String getGlobalAppSearchDataQuerierPackageName(@NonNull Context context) {
         String globalAppSearchDataQuerierPackage =
                 context.getString(R.string.config_globalAppSearchDataQuerierPackage);
         try {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 592b8b9..8bff720 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -195,8 +195,7 @@
             mIcingSearchEngineLocked = new IcingSearchEngine(options);
 
             mVisibilityStoreLocked =
-                    new VisibilityStore(
-                            this, context, userId, globalQuerierPackage);
+                    new VisibilityStore(this, context, userId, globalQuerierPackage);
 
             InitializeResultProto initializeResultProto = mIcingSearchEngineLocked.initialize();
             SchemaProto schemaProto;
@@ -508,8 +507,8 @@
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
-        if (!searchSpec.getPackageNames().isEmpty()
-                && !searchSpec.getPackageNames().contains(packageName)) {
+        List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+        if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
             // Client wanted to query over some packages that weren't its own. This isn't
             // allowed through local query so we can return early with no results.
             return new SearchResultPage(Bundle.EMPTY);
@@ -553,7 +552,7 @@
             throws AppSearchException {
         mReadWriteLock.readLock().lock();
         try {
-            Set<String> packageFilters = new ArraySet<>(searchSpec.getPackageNames());
+            Set<String> packageFilters = new ArraySet<>(searchSpec.getFilterPackageNames());
             Set<String> prefixFilters = new ArraySet<>();
             Set<String> allPrefixes = mNamespaceMapLocked.keySet();
             if (packageFilters.isEmpty()) {
@@ -573,7 +572,7 @@
 
             // Find which schemas the client is allowed to query over.
             Set<String> allowedPrefixedSchemas = new ArraySet<>();
-            List<String> schemaFilters = searchSpec.getSchemaTypes();
+            List<String> schemaFilters = searchSpec.getFilterSchemas();
             for (String prefix : prefixFilters) {
                 String packageName = getPackageName(prefix);
 
@@ -752,8 +751,8 @@
             @NonNull String queryExpression,
             @NonNull SearchSpec searchSpec)
             throws AppSearchException {
-        if (!searchSpec.getPackageNames().isEmpty()
-                && !searchSpec.getPackageNames().contains(packageName)) {
+        List<String> filterPackageNames = searchSpec.getFilterPackageNames();
+        if (!filterPackageNames.isEmpty() && !filterPackageNames.contains(packageName)) {
             // We're only removing documents within the parameter `packageName`. If we're not
             // restricting our remove-query to this package name, then there's nothing for us to
             // remove.
@@ -1085,7 +1084,7 @@
         Set<String> allowedPrefixedSchemas = new ArraySet<>();
 
         // Add all the schema filters the client specified.
-        List<String> schemaFilters = searchSpec.getSchemaTypes();
+        List<String> schemaFilters = searchSpec.getFilterSchemas();
         for (int i = 0; i < schemaFilters.size(); i++) {
             allowedPrefixedSchemas.add(prefix + schemaFilters.get(i));
         }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
new file mode 100644
index 0000000..b3f8264
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchMigrationHelperImpl.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2021 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.appsearch.external.localstorage;
+
+import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchMigrationHelper;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.SearchResultPage;
+import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaResponse;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import com.android.internal.util.Preconditions;
+
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * An implementation of {@link AppSearchMigrationHelper} which query document and save post-migrated
+ * documents to locally in the app's storage space.
+ */
+class AppSearchMigrationHelperImpl implements AppSearchMigrationHelper {
+    private final AppSearchImpl mAppSearchImpl;
+    private final String mPackageName;
+    private final String mDatabaseName;
+    private final File mFile;
+    private final Map<String, Integer> mCurrentVersionMap;
+    private final Map<String, Integer> mFinalVersionMap;
+
+    AppSearchMigrationHelperImpl(
+            @NonNull AppSearchImpl appSearchImpl,
+            @NonNull Map<String, Integer> currentVersionMap,
+            @NonNull Map<String, Integer> finalVersionMap,
+            @NonNull String packageName,
+            @NonNull String databaseName)
+            throws IOException {
+        mAppSearchImpl = Preconditions.checkNotNull(appSearchImpl);
+        mCurrentVersionMap = Preconditions.checkNotNull(currentVersionMap);
+        mFinalVersionMap = Preconditions.checkNotNull(finalVersionMap);
+        mPackageName = Preconditions.checkNotNull(packageName);
+        mDatabaseName = Preconditions.checkNotNull(databaseName);
+        mFile = File.createTempFile(/*prefix=*/ "appsearch", /*suffix=*/ null);
+    }
+
+    @Override
+    public void queryAndTransform(
+            @NonNull String schemaType, @NonNull AppSearchMigrationHelper.Transformer migrator)
+            throws Exception {
+        Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
+        int currentVersion = mCurrentVersionMap.get(schemaType);
+        int finalVersion = mFinalVersionMap.get(schemaType);
+        try (FileOutputStream outputStream = new FileOutputStream(mFile)) {
+            // TODO(b/151178558) change the output stream so that we can use it in platform
+            CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
+            SearchResultPage searchResultPage =
+                    mAppSearchImpl.query(
+                            mPackageName,
+                            mDatabaseName,
+                            /*queryExpression=*/ "",
+                            new SearchSpec.Builder()
+                                    .addFilterSchemas(schemaType)
+                                    .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                    .build());
+            while (!searchResultPage.getResults().isEmpty()) {
+                for (int i = 0; i < searchResultPage.getResults().size(); i++) {
+                    GenericDocument newDocument =
+                            migrator.transform(
+                                    currentVersion,
+                                    finalVersion,
+                                    searchResultPage.getResults().get(i).getDocument());
+                    Bundle bundle = newDocument.getBundle();
+                    Parcel parcel = Parcel.obtain();
+                    parcel.writeBundle(bundle);
+                    byte[] serializedMessage = parcel.marshall();
+                    parcel.recycle();
+                    codedOutputStream.writeByteArrayNoTag(serializedMessage);
+                }
+                codedOutputStream.flush();
+                searchResultPage = mAppSearchImpl.getNextPage(searchResultPage.getNextPageToken());
+                outputStream.flush();
+            }
+        }
+    }
+
+    /**
+     * Reads {@link GenericDocument} from the temperate file and saves them to AppSearch.
+     *
+     * <p>This method should be only called once.
+     *
+     * @return the {@link AppSearchBatchResult} for migration documents.
+     */
+    @NonNull
+    public SetSchemaResponse readAndPutDocuments(SetSchemaResponse.Builder responseBuilder)
+            throws IOException, AppSearchException {
+        Preconditions.checkState(mFile.exists(), "Internal temp file does not exist.");
+        try (InputStream inputStream = new FileInputStream(mFile)) {
+            CodedInputStream codedInputStream = CodedInputStream.newInstance(inputStream);
+            while (!codedInputStream.isAtEnd()) {
+                GenericDocument document = readDocumentFromInputStream(codedInputStream);
+                try {
+                    mAppSearchImpl.putDocument(mPackageName, mDatabaseName, document);
+                } catch (Throwable t) {
+                    responseBuilder.setFailure(
+                            document.getSchemaType(),
+                            document.getNamespace(),
+                            document.getUri(),
+                            throwableToFailedResult(t));
+                }
+            }
+            mAppSearchImpl.persistToDisk();
+            return responseBuilder.build();
+        } finally {
+            mFile.delete();
+        }
+    }
+
+    void deleteTempFile() {
+        mFile.delete();
+    }
+
+    /**
+     * Reads {@link GenericDocument} from given {@link CodedInputStream}.
+     *
+     * @param codedInputStream The codedInputStream to read from
+     * @throws IOException on File operation error.
+     */
+    @NonNull
+    private static GenericDocument readDocumentFromInputStream(
+            @NonNull CodedInputStream codedInputStream) throws IOException {
+        byte[] serializedMessage = codedInputStream.readByteArray();
+
+        Parcel parcel = Parcel.obtain();
+        parcel.unmarshall(serializedMessage, 0, serializedMessage.length);
+        parcel.setDataPosition(0);
+        Bundle bundle = parcel.readBundle();
+        parcel.recycle();
+
+        return new GenericDocument(bundle);
+    }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
index 07d50ae..3b5e275 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
@@ -40,8 +40,8 @@
         Preconditions.checkNotNull(spec);
         SearchSpecProto.Builder protoBuilder =
                 SearchSpecProto.newBuilder()
-                        .addAllSchemaTypeFilters(spec.getSchemaTypes())
-                        .addAllNamespaceFilters(spec.getNamespaces());
+                        .addAllSchemaTypeFilters(spec.getFilterSchemas())
+                        .addAllNamespaceFilters(spec.getFilterNamespaces());
 
         @SearchSpec.TermMatch int termMatchCode = spec.getTermMatch();
         TermMatchType.Code termMatchCodeProto = TermMatchType.Code.forNumber(termMatchCode);
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 2774181..12699b7 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I3fd4c96bf775c2539d744c416cdbf1d3c9544f03
+Ibe06fb9c574c8718191f833bb042fa10c300e4e2
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
index f8d0d80..afa633a 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java
@@ -102,10 +102,10 @@
 
     @Override
     @NonNull
-    public ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
+    public ListenableFuture<AppSearchBatchResult<String, Void>> put(
             @NonNull PutDocumentsRequest request) {
         SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
-        mAppSearchSession.putDocuments(
+        mAppSearchSession.put(
                 request, mExecutor, new BatchResultCallbackAdapter<>(future));
         return future;
     }
@@ -122,10 +122,10 @@
 
     @Override
     @NonNull
-    public SearchResultsShim query(
+    public SearchResultsShim search(
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
         SearchResults searchResults =
-                mAppSearchSession.query(queryExpression, searchSpec, mExecutor);
+                mAppSearchSession.search(queryExpression, searchSpec, mExecutor);
         return new SearchResultsShimImpl(searchResults, mExecutor);
     }
 
@@ -139,19 +139,19 @@
 
     @Override
     @NonNull
-    public ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
+    public ListenableFuture<AppSearchBatchResult<String, Void>> remove(
             @NonNull RemoveByUriRequest request) {
         SettableFuture<AppSearchBatchResult<String, Void>> future = SettableFuture.create();
-        mAppSearchSession.removeByUri(request, mExecutor, new BatchResultCallbackAdapter<>(future));
+        mAppSearchSession.remove(request, mExecutor, new BatchResultCallbackAdapter<>(future));
         return future;
     }
 
     @Override
     @NonNull
-    public ListenableFuture<Void> removeByQuery(
+    public ListenableFuture<Void> remove(
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
         SettableFuture<AppSearchResult<Void>> future = SettableFuture.create();
-        mAppSearchSession.removeByQuery(queryExpression, searchSpec, mExecutor, future::set);
+        mAppSearchSession.remove(queryExpression, searchSpec, mExecutor, future::set);
         return Futures.transformAsync(future, this::transformResult, mExecutor);
     }
 
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
index 39ca687..eb1623e 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java
@@ -67,10 +67,10 @@
 
     @NonNull
     @Override
-    public SearchResultsShim query(
+    public SearchResultsShim search(
             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
         SearchResults searchResults =
-                mGlobalSearchSession.query(queryExpression, searchSpec, mExecutor);
+                mGlobalSearchSession.search(queryExpression, searchSpec, mExecutor);
         return new SearchResultsShimImpl(searchResults, mExecutor);
     }
 
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
index e8ea6ef..8e62c0e 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java
@@ -33,7 +33,7 @@
 public interface AppSearchSessionShim extends Closeable {
 
     /**
-     * Sets the schema that will be used by documents provided to the {@link #putDocuments} method.
+     * Sets the schema that will be used by documents provided to the {@link #put} method.
      *
      * <p>The schema provided here is compared to the stored copy of the schema previously supplied
      * to {@link #setSchema}, if any, to determine how to treat existing documents. The following
@@ -143,8 +143,7 @@
      *     they were successfully indexed, or a failed {@link AppSearchResult} otherwise.
      */
     @NonNull
-    ListenableFuture<AppSearchBatchResult<String, Void>> putDocuments(
-            @NonNull PutDocumentsRequest request);
+    ListenableFuture<AppSearchBatchResult<String, Void>> put(@NonNull PutDocumentsRequest request);
 
     /**
      * Retrieves {@link GenericDocument}s by URI.
@@ -161,7 +160,7 @@
             @NonNull GetByUriRequest request);
 
     /**
-     * Searches a document based on a given query string.
+     * Searches for documents based on a given query string.
      *
      * <p>Currently we support following features in the raw query format:
      *
@@ -200,7 +199,7 @@
      * @return The search result of performing this operation.
      */
     @NonNull
-    SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+    SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
     /**
      * Reports usage of a particular document by URI and namespace.
@@ -208,7 +207,7 @@
      * <p>A usage report represents an event in which a user interacted with or viewed a document.
      *
      * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
-     * metrics for that particular document. These metrics are used for ordering {@link #query}
+     * metrics for that particular document. These metrics are used for ordering {@link #search}
      * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
      * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
      *
@@ -231,13 +230,13 @@
      *     {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
      */
     @NonNull
-    ListenableFuture<AppSearchBatchResult<String, Void>> removeByUri(
+    ListenableFuture<AppSearchBatchResult<String, Void>> remove(
             @NonNull RemoveByUriRequest request);
 
     /**
      * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
      * match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
-     * SearchSpec.Builder#addNamespace} and {@link SearchSpec.Builder#addSchemaType}.
+     * SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}.
      *
      * <p>An empty {@code queryExpression} matches all documents.
      *
@@ -251,8 +250,7 @@
      * @return The pending result of performing this operation.
      */
     @NonNull
-    ListenableFuture<Void> removeByQuery(
-            @NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+    ListenableFuture<Void> remove(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
     /**
      * Flush all schema and document updates, additions, and deletes to disk if possible.
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
index cd867a4..b96f99e 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java
@@ -23,7 +23,7 @@
 /**
  * This class provides global access to the centralized AppSearch index maintained by the system.
  *
- * <p>Apps can retrieve indexed documents through the query API.
+ * <p>Apps can retrieve indexed documents through the {@link #search} API.
  */
 public interface GlobalSearchSessionShim extends Closeable {
     /**
@@ -66,7 +66,7 @@
      * @return The search result of performing this operation.
      */
     @NonNull
-    SearchResultsShim query(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
+    SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
 
     /** Closes the {@link GlobalSearchSessionShim}. */
     @Override
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 6eb44a7..930415f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -593,15 +593,6 @@
     }
 
     /**
-     * @see JobInfo.Builder#setExpedited(boolean)
-     * @deprecated Use {@link #isExpedited()} instead
-     */
-    @Deprecated
-    public boolean isForegroundJob() {
-        return (flags & FLAG_EXPEDITED) != 0;
-    }
-
-    /**
      * @see JobInfo.Builder#setImportantWhileForeground(boolean)
      */
     public boolean isImportantWhileForeground() {
@@ -1503,20 +1494,6 @@
         }
 
         /**
-         * @deprecated Use {@link #setExpedited(boolean)} instead.
-         */
-        @Deprecated
-        @NonNull
-        public Builder setForeground(boolean foreground) {
-            if (foreground) {
-                mFlags |= FLAG_EXPEDITED;
-            } else {
-                mFlags &= (~FLAG_EXPEDITED);
-            }
-            return this;
-        }
-
-        /**
          * Setting this to true indicates that this job is important while the scheduling app
          * is in the foreground or on the temporary whitelist for background restrictions.
          * This means that the system will relax doze restrictions on this job during this time.
diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
index ab87222..283e933 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java
@@ -74,6 +74,27 @@
     }
 
     /**
+     * Allow the temp allowlist behavior, plus allow foreground service start from background.
+     */
+    public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0;
+    /**
+     * Only allow the temp allowlist behavior, not allow foreground service start from
+     * background.
+     */
+    public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1;
+
+    /**
+     * The list of temp allowlist types.
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "TEMPORARY_ALLOW_TYPE_" }, value = {
+            TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+            TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TempAllowListType {}
+
+    /**
      * @hide
      */
     public PowerWhitelistManager(@NonNull Context context) {
diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
index 4441643..e045b0f 100644
--- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
@@ -16,7 +16,7 @@
 
 package com.android.server;
 
-import android.app.BroadcastOptions;
+import android.os.PowerWhitelistManager.TempAllowListType;
 
 import com.android.server.deviceidle.IDeviceIdleConstraint;
 
@@ -39,12 +39,12 @@
      * allowlist.
      * @param uid
      * @param duration duration in milliseconds
-     * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+     * @param type temp allowlist type defined at {@link TempAllowListType}
      * @param sync
      * @param reason
      */
     void addPowerSaveTempWhitelistAppDirect(int uid, long duration,
-            @BroadcastOptions.TempAllowListType int type, boolean sync,
+            @TempAllowListType int type, boolean sync,
             String reason);
 
     // duration in milliseconds
diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
index cc3e9c3..c332a59 100644
--- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java
@@ -105,10 +105,6 @@
     @GuardedBy("mLock")
     final SparseBooleanArray mActiveUids = new SparseBooleanArray();
 
-    /** UIDs that are in the foreground. */
-    @GuardedBy("mLock")
-    final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
-
     /**
      * System except-idle + user exemption list in the device idle controller.
      */
@@ -286,13 +282,6 @@
         }
 
         /**
-         * This is called when the foreground state changed for a UID.
-         */
-        private void onUidForegroundStateChanged(AppStateTrackerImpl sender, int uid) {
-            onUidForeground(uid, sender.isUidInForeground(uid));
-        }
-
-        /**
          * This is called when the active/idle state changed for a UID.
          */
         private void onUidActiveStateChanged(AppStateTrackerImpl sender, int uid) {
@@ -416,14 +405,6 @@
         }
 
         /**
-         * Called when a UID comes into the foreground or the background.
-         *
-         * @see #isUidInForeground(int)
-         */
-        public void onUidForeground(int uid, boolean foreground) {
-        }
-
-        /**
          * Called when an ephemeral uid goes to the background, so its alarms need to be removed.
          */
         public void removeAlarmsForUid(int uid) {
@@ -460,7 +441,6 @@
                         mExemptedBucketPackages.remove(userId, pkgName);
                         mRunAnyRestrictedPackages.remove(Pair.create(uid, pkgName));
                         mActiveUids.delete(uid);
-                        mForegroundUids.delete(uid);
                     }
                     break;
             }
@@ -496,8 +476,7 @@
                 mIActivityManager.registerUidObserver(new UidObserver(),
                         ActivityManager.UID_OBSERVER_GONE
                                 | ActivityManager.UID_OBSERVER_IDLE
-                                | ActivityManager.UID_OBSERVER_ACTIVE
-                                | ActivityManager.UID_OBSERVER_PROCSTATE,
+                                | ActivityManager.UID_OBSERVER_ACTIVE,
                         ActivityManager.PROCESS_STATE_UNKNOWN, null);
                 mAppOpsService.startWatchingMode(TARGET_OP, null,
                         new AppOpsWatcher());
@@ -698,7 +677,6 @@
     private final class UidObserver extends IUidObserver.Stub {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
-            mHandler.onUidStateChanged(uid, procState);
         }
 
         @Override
@@ -769,7 +747,6 @@
 
     private class MyHandler extends Handler {
         private static final int MSG_UID_ACTIVE_STATE_CHANGED = 0;
-        private static final int MSG_UID_FG_STATE_CHANGED = 1;
         private static final int MSG_RUN_ANY_CHANGED = 3;
         private static final int MSG_ALL_UNEXEMPTED = 4;
         private static final int MSG_ALL_EXEMPTION_LIST_CHANGED = 5;
@@ -779,7 +756,6 @@
         private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
         private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10;
 
-        private static final int MSG_ON_UID_STATE_CHANGED = 11;
         private static final int MSG_ON_UID_ACTIVE = 12;
         private static final int MSG_ON_UID_GONE = 13;
         private static final int MSG_ON_UID_IDLE = 14;
@@ -792,10 +768,6 @@
             obtainMessage(MSG_UID_ACTIVE_STATE_CHANGED, uid, 0).sendToTarget();
         }
 
-        public void notifyUidForegroundStateChanged(int uid) {
-            obtainMessage(MSG_UID_FG_STATE_CHANGED, uid, 0).sendToTarget();
-        }
-
         public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
             obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
         }
@@ -834,10 +806,6 @@
             obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
         }
 
-        public void onUidStateChanged(int uid, int procState) {
-            obtainMessage(MSG_ON_UID_STATE_CHANGED, uid, procState).sendToTarget();
-        }
-
         public void onUidActive(int uid) {
             obtainMessage(MSG_ON_UID_ACTIVE, uid, 0).sendToTarget();
         }
@@ -875,13 +843,6 @@
                     mStatLogger.logDurationStat(Stats.UID_ACTIVE_STATE_CHANGED, start);
                     return;
 
-                case MSG_UID_FG_STATE_CHANGED:
-                    for (Listener l : cloneListeners()) {
-                        l.onUidForegroundStateChanged(sender, msg.arg1);
-                    }
-                    mStatLogger.logDurationStat(Stats.UID_FG_STATE_CHANGED, start);
-                    return;
-
                 case MSG_RUN_ANY_CHANGED:
                     for (Listener l : cloneListeners()) {
                         l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
@@ -944,9 +905,6 @@
                     handleUserRemoved(msg.arg1);
                     return;
 
-                case MSG_ON_UID_STATE_CHANGED:
-                    handleUidStateChanged(msg.arg1, msg.arg2);
-                    return;
                 case MSG_ON_UID_ACTIVE:
                     handleUidActive(msg.arg1);
                     return;
@@ -971,20 +929,6 @@
             }
         }
 
-        public void handleUidStateChanged(int uid, int procState) {
-            synchronized (mLock) {
-                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                    if (removeUidFromArray(mForegroundUids, uid, false)) {
-                        mHandler.notifyUidForegroundStateChanged(uid);
-                    }
-                } else {
-                    if (addUidToArray(mForegroundUids, uid)) {
-                        mHandler.notifyUidForegroundStateChanged(uid);
-                    }
-                }
-            }
-        }
-
         public void handleUidActive(int uid) {
             synchronized (mLock) {
                 if (addUidToArray(mActiveUids, uid)) {
@@ -1007,9 +951,6 @@
                 if (removeUidFromArray(mActiveUids, uid, remove)) {
                     mHandler.notifyUidActiveStateChanged(uid);
                 }
-                if (removeUidFromArray(mForegroundUids, uid, remove)) {
-                    mHandler.notifyUidForegroundStateChanged(uid);
-                }
             }
         }
     }
@@ -1026,7 +967,6 @@
                 }
             }
             cleanUpArrayForUser(mActiveUids, removedUserId);
-            cleanUpArrayForUser(mForegroundUids, removedUserId);
             mExemptedBucketPackages.remove(removedUserId);
         }
     }
@@ -1222,22 +1162,6 @@
     }
 
     /**
-     * @return whether a UID is in the foreground or not.
-     *
-     * Note this information is based on the UID proc state callback, meaning it's updated
-     * asynchronously and may subtly be stale. If the fresh data is needed, use
-     * {@link ActivityManagerInternal#getUidProcessState} instead.
-     */
-    public boolean isUidInForeground(int uid) {
-        if (UserHandle.isCore(uid)) {
-            return true;
-        }
-        synchronized (mLock) {
-            return mForegroundUids.get(uid);
-        }
-    }
-
-    /**
      * @return whether force all apps standby is enabled or not.
      */
     public boolean isForceAllAppsStandbyEnabled() {
@@ -1315,9 +1239,6 @@
             pw.print("Active uids: ");
             dumpUids(pw, mActiveUids);
 
-            pw.print("Foreground uids: ");
-            dumpUids(pw, mForegroundUids);
-
             pw.print("Except-idle + user exemption list appids: ");
             pw.println(Arrays.toString(mPowerExemptAllAppIds));
 
@@ -1395,12 +1316,6 @@
                 }
             }
 
-            for (int i = 0; i < mForegroundUids.size(); i++) {
-                if (mForegroundUids.valueAt(i)) {
-                    proto.write(AppStateTrackerProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
-                }
-            }
-
             for (int appId : mPowerExemptAllAppIds) {
                 proto.write(AppStateTrackerProto.POWER_SAVE_EXEMPT_APP_IDS, appId);
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index c9427e9..8f7f705 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -22,7 +22,6 @@
 import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.BroadcastOptions;
-import android.app.BroadcastOptions.TempAllowListType;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -57,6 +56,7 @@
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
+import android.os.PowerWhitelistManager.TempAllowListType;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -3879,7 +3879,7 @@
      * @param adding true to add to temp allowlist, false to remove from temp allowlist.
      * @param durationMs duration in milliseconds to add to temp allowlist, only valid when
      *                   param adding is true.
-     * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+     * @param type temp allowlist type defined at {@link TempAllowListType}
      */
     private void updateTempWhitelistAppIdsLocked(int uid, boolean adding, long durationMs,
             @TempAllowListType int type) {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index aa46cfd..f6a1b8a 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -90,7 +90,6 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
-import android.util.SparseLongArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
@@ -152,7 +151,8 @@
     static final boolean DEBUG_BG_LIMIT = localLOGV || false;
     static final boolean DEBUG_STANDBY = localLOGV || false;
     static final boolean RECORD_ALARMS_IN_HISTORY = true;
-    static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
+    // TODO(b/178484639) : Turn off once alarms and reminders work is complete.
+    static final boolean RECORD_DEVICE_IDLE_ALARMS = true;
     static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
     static final int TICK_HISTORY_DEPTH = 10;
@@ -208,6 +208,7 @@
             new ArrayList<>();
     AlarmHandler mHandler;
     AppWakeupHistory mAppWakeupHistory;
+    AppWakeupHistory mAllowWhileIdleHistory;
     ClockReceiver mClockReceiver;
     final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
     IBinder.DeathRecipient mListenerDeathRecipient;
@@ -230,19 +231,6 @@
      */
     int mSystemUiUid;
 
-    /**
-     * For each uid, this is the last time we dispatched an "allow while idle" alarm,
-     * used to determine the earliest we can dispatch the next such alarm. Times are in the
-     * 'elapsed' timebase.
-     */
-    final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
-
-    /**
-     * For each uid, we store whether the last allow-while-idle alarm was dispatched while
-     * the uid was in foreground or not. We will use the allow_while_idle_short_time in such cases.
-     */
-    final SparseBooleanArray mUseAllowWhileIdleShortTime = new SparseBooleanArray();
-
     static boolean isTimeTickAlarm(Alarm a) {
         return a.uid == Process.SYSTEM_UID && TIME_TICK_TAG.equals(a.listenerTag);
     }
@@ -290,9 +278,11 @@
     private boolean mAppStandbyParole;
 
     /**
-     * A rolling window history of previous times when an alarm was sent to a package.
+     * A container to keep rolling window history of previous times when an alarm was sent to
+     * a package.
      */
-    private static class AppWakeupHistory {
+    @VisibleForTesting
+    static class AppWakeupHistory {
         private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory =
                 new ArrayMap<>();
         private long mWindowSize;
@@ -353,7 +343,6 @@
         }
 
         void dump(IndentingPrintWriter pw, long nowElapsed) {
-            pw.println("App Alarm history:");
             pw.increaseIndent();
             for (int i = 0; i < mPackageHistory.size(); i++) {
                 final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i);
@@ -389,10 +378,6 @@
         @VisibleForTesting
         static final String KEY_MAX_INTERVAL = "max_interval";
         @VisibleForTesting
-        static final String KEY_ALLOW_WHILE_IDLE_SHORT_TIME = "allow_while_idle_short_time";
-        @VisibleForTesting
-        static final String KEY_ALLOW_WHILE_IDLE_LONG_TIME = "allow_while_idle_long_time";
-        @VisibleForTesting
         static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = "allow_while_idle_whitelist_duration";
         @VisibleForTesting
@@ -422,11 +407,12 @@
         private static final String KEY_TIME_TICK_ALLOWED_WHILE_IDLE =
                 "time_tick_allowed_while_idle";
 
+        @VisibleForTesting
+        static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota";
+
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
         private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
-        private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY;
-        private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9 * 60 * 1000;
         private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10 * 1000;
         private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
         private static final int DEFAULT_MAX_ALARMS_PER_UID = 500;
@@ -447,6 +433,9 @@
         private static final boolean DEFAULT_LAZY_BATCHING = true;
         private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true;
 
+        private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 7;
+        public static final long ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour.
+
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
 
@@ -456,12 +445,6 @@
         // Maximum alarm recurrence interval
         public long MAX_INTERVAL = DEFAULT_MAX_INTERVAL;
 
-        // Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle.
-        public long ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME;
-
-        // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling.
-        public long ALLOW_WHILE_IDLE_LONG_TIME = DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME;
-
         // BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE.
         public long ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -478,6 +461,8 @@
         public boolean LAZY_BATCHING = DEFAULT_LAZY_BATCHING;
         public boolean TIME_TICK_ALLOWED_WHILE_IDLE = DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE;
 
+        public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA;
+
         private long mLastAllowWhileIdleWhitelistDuration = -1;
 
         Constants() {
@@ -523,15 +508,13 @@
                             MAX_INTERVAL = properties.getLong(
                                     KEY_MAX_INTERVAL, DEFAULT_MAX_INTERVAL);
                             break;
-                        case KEY_ALLOW_WHILE_IDLE_SHORT_TIME:
-                            ALLOW_WHILE_IDLE_SHORT_TIME = properties.getLong(
-                                    KEY_ALLOW_WHILE_IDLE_SHORT_TIME,
-                                    DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME);
-                            break;
-                        case KEY_ALLOW_WHILE_IDLE_LONG_TIME:
-                            ALLOW_WHILE_IDLE_LONG_TIME = properties.getLong(
-                                    KEY_ALLOW_WHILE_IDLE_LONG_TIME,
-                                    DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME);
+                        case KEY_ALLOW_WHILE_IDLE_QUOTA:
+                            ALLOW_WHILE_IDLE_QUOTA = properties.getInt(KEY_ALLOW_WHILE_IDLE_QUOTA,
+                                    DEFAULT_ALLOW_WHILE_IDLE_QUOTA);
+                            if (ALLOW_WHILE_IDLE_QUOTA <= 0) {
+                                Slog.w(TAG, "Cannot have allow-while-idle quota lower than 1.");
+                                ALLOW_WHILE_IDLE_QUOTA = 1;
+                            }
                             break;
                         case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION:
                             ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong(
@@ -660,14 +643,11 @@
             TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
             pw.println();
 
-            pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME);
-            pw.print("=");
-            TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
+            pw.print("allow_while_idle_window=");
+            TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw);
             pw.println();
 
-            pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME);
-            pw.print("=");
-            TimeUtils.formatDuration(ALLOW_WHILE_IDLE_LONG_TIME, pw);
+            pw.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA);
             pw.println();
 
             pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
@@ -675,9 +655,8 @@
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
             pw.println();
 
-            pw.print(KEY_MAX_ALARMS_PER_UID);
-            pw.print("=");
-            pw.println(MAX_ALARMS_PER_UID);
+            pw.print(KEY_MAX_ALARMS_PER_UID, MAX_ALARMS_PER_UID);
+            pw.println();
 
             pw.print(KEY_APP_STANDBY_WINDOW);
             pw.print("=");
@@ -685,14 +664,12 @@
             pw.println();
 
             for (int i = 0; i < KEYS_APP_STANDBY_QUOTAS.length; i++) {
-                pw.print(KEYS_APP_STANDBY_QUOTAS[i]);
-                pw.print("=");
-                pw.println(APP_STANDBY_QUOTAS[i]);
+                pw.print(KEYS_APP_STANDBY_QUOTAS[i], APP_STANDBY_QUOTAS[i]);
+                pw.println();
             }
 
-            pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA);
-            pw.print("=");
-            pw.println(APP_STANDBY_RESTRICTED_QUOTA);
+            pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA, APP_STANDBY_RESTRICTED_QUOTA);
+            pw.println();
 
             pw.print(KEY_APP_STANDBY_RESTRICTED_WINDOW);
             pw.print("=");
@@ -715,10 +692,6 @@
             proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL);
             proto.write(ConstantsProto.MAX_INTERVAL_DURATION_MS, MAX_INTERVAL);
             proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT);
-            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS,
-                    ALLOW_WHILE_IDLE_SHORT_TIME);
-            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS,
-                    ALLOW_WHILE_IDLE_LONG_TIME);
             proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS,
                     ALLOW_WHILE_IDLE_WHITELIST_DURATION);
 
@@ -1268,6 +1241,7 @@
             mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
 
             mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
+            mAllowWhileIdleHistory = new AppWakeupHistory(Constants.ALLOW_WHILE_IDLE_WINDOW);
 
             mNextWakeup = mNextNonWakeup = 0;
 
@@ -1636,25 +1610,28 @@
             return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed);
         }
 
-        final long batterSaverPolicyElapsed;
+        final long batterySaverPolicyElapsed;
         if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) {
             // Unrestricted.
-            batterSaverPolicyElapsed = nowElapsed;
+            batterySaverPolicyElapsed = nowElapsed;
         } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
             // Allowed but limited.
-            final long minDelay;
-            if (mUseAllowWhileIdleShortTime.get(alarm.creatorUid)) {
-                minDelay = mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
+            final int userId = UserHandle.getUserId(alarm.creatorUid);
+            final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+            final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
+                    alarm.sourcePackage, userId);
+            if (dispatchesInWindow < quota) {
+                // fine to go out immediately.
+                batterySaverPolicyElapsed = nowElapsed;
             } else {
-                minDelay = mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+                batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
+                        alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
             }
-            final long lastDispatch = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0);
-            batterSaverPolicyElapsed = (lastDispatch == 0) ? nowElapsed : lastDispatch + minDelay;
         } else {
             // Not allowed.
-            batterSaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY;
+            batterySaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY;
         }
-        return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterSaverPolicyElapsed);
+        return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterySaverPolicyElapsed);
     }
 
     /**
@@ -1676,9 +1653,18 @@
             deviceIdlePolicyTime = nowElapsed;
         } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
             // Allowed but limited.
-            final long lastDispatch = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0);
-            deviceIdlePolicyTime = (lastDispatch == 0) ? nowElapsed
-                    : lastDispatch + mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+            final int userId = UserHandle.getUserId(alarm.creatorUid);
+            final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA;
+            final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow(
+                    alarm.sourcePackage, userId);
+            if (dispatchesInWindow < quota) {
+                // fine to go out immediately.
+                deviceIdlePolicyTime = nowElapsed;
+            } else {
+                final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage(
+                        alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW;
+                deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed());
+            }
         } else {
             // Not allowed.
             deviceIdlePolicyTime = mPendingIdleUntil.getWhenElapsed();
@@ -1723,11 +1709,11 @@
             if (wakeupsInWindow >= quotaForBucket) {
                 final long minElapsed;
                 if (quotaForBucket <= 0) {
-                    // Just keep deferring for a day till the quota changes
-                    minElapsed = nowElapsed + MILLIS_IN_DAY;
+                    // Just keep deferring indefinitely till the quota changes.
+                    minElapsed = nowElapsed + INDEFINITE_DELAY;
                 } else {
                     // Suppose the quota for window was q, and the qth last delivery time for this
-                    // package was t(q) then the next delivery must be after t(q) + <window_size>
+                    // package was t(q) then the next delivery must be after t(q) + <window_size>.
                     final long t = mAppWakeupHistory.getNthLastWakeupForPackage(
                             sourcePackage, sourceUserId, quotaForBucket);
                     minElapsed = t + mConstants.APP_STANDBY_WINDOW;
@@ -1746,19 +1732,12 @@
             if (RECORD_DEVICE_IDLE_ALARMS) {
                 IdleDispatchEntry ent = new IdleDispatchEntry();
                 ent.uid = a.uid;
-                ent.pkg = a.operation.getCreatorPackage();
-                ent.tag = a.operation.getTag("");
-                ent.op = "SET";
+                ent.pkg = a.sourcePackage;
+                ent.tag = a.statsTag;
+                ent.op = "START IDLE";
                 ent.elapsedRealtime = mInjector.getElapsedRealtime();
                 ent.argRealtime = a.getWhenElapsed();
                 mAllowWhileIdleDispatches.add(ent);
-                if (mPendingIdleUntil == null) {
-                    IdleDispatchEntry ent2 = new IdleDispatchEntry();
-                    ent2.uid = 0;
-                    ent2.pkg = "START IDLE";
-                    ent2.elapsedRealtime = mInjector.getElapsedRealtime();
-                    mAllowWhileIdleDispatches.add(ent2);
-                }
             }
             if ((mPendingIdleUntil != a) && (mPendingIdleUntil != null)) {
                 Slog.wtfStack(TAG, "setImplLocked: idle until changed from " + mPendingIdleUntil
@@ -2182,6 +2161,7 @@
             pw.println("]");
             pw.println();
 
+            pw.println("App Alarm history:");
             mAppWakeupHistory.dump(pw, nowELAPSED);
 
             if (mPendingIdleUntil != null) {
@@ -2259,30 +2239,8 @@
                 pw.println();
             }
 
-            if (mLastAllowWhileIdleDispatch.size() > 0) {
-                pw.println("Last allow while idle dispatch times:");
-                pw.increaseIndent();
-                for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); i++) {
-                    pw.print("UID ");
-                    final int uid = mLastAllowWhileIdleDispatch.keyAt(i);
-                    UserHandle.formatUid(pw, uid);
-                    pw.print(": ");
-                    final long lastTime = mLastAllowWhileIdleDispatch.valueAt(i);
-                    TimeUtils.formatDuration(lastTime, nowELAPSED, pw);
-                    pw.println();
-                }
-                pw.decreaseIndent();
-            }
-
-            pw.print("mUseAllowWhileIdleShortTime: [");
-            for (int i = 0; i < mUseAllowWhileIdleShortTime.size(); i++) {
-                if (mUseAllowWhileIdleShortTime.valueAt(i)) {
-                    UserHandle.formatUid(pw, mUseAllowWhileIdleShortTime.keyAt(i));
-                    pw.print(" ");
-                }
-            }
-            pw.println("]");
-            pw.println();
+            pw.println("Allow while idle history:");
+            mAllowWhileIdleHistory.dump(pw, nowELAPSED);
 
             if (mLog.dump(pw, "Recent problems:")) {
                 pw.println();
@@ -2533,25 +2491,6 @@
                 f.dumpDebug(proto, AlarmManagerServiceDumpProto.OUTSTANDING_DELIVERIES);
             }
 
-            for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) {
-                final long token = proto.start(
-                        AlarmManagerServiceDumpProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES);
-                final int uid = mLastAllowWhileIdleDispatch.keyAt(i);
-                final long lastTime = mLastAllowWhileIdleDispatch.valueAt(i);
-
-                proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.UID, uid);
-                proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.TIME_MS,
-                        lastTime);
-                proto.end(token);
-            }
-
-            for (int i = 0; i < mUseAllowWhileIdleShortTime.size(); i++) {
-                if (mUseAllowWhileIdleShortTime.valueAt(i)) {
-                    proto.write(AlarmManagerServiceDumpProto.USE_ALLOW_WHILE_IDLE_SHORT_TIME,
-                            mUseAllowWhileIdleShortTime.keyAt(i));
-                }
-            }
-
             mLog.dumpDebug(proto, AlarmManagerServiceDumpProto.RECENT_PROBLEMS);
 
             final FilterStats[] topFilters = new FilterStats[10];
@@ -3049,11 +2988,6 @@
                 mPendingBackgroundAlarms.removeAt(i);
             }
         }
-        for (int i = mLastAllowWhileIdleDispatch.size() - 1; i >= 0; i--) {
-            if (UserHandle.getUserId(mLastAllowWhileIdleDispatch.keyAt(i)) == userHandle) {
-                mLastAllowWhileIdleDispatch.removeAt(i);
-            }
-        }
         if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) {
             mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
             if (mPendingIdleUntil != null) {
@@ -3215,6 +3149,16 @@
             if (mPendingIdleUntil == alarm) {
                 mPendingIdleUntil = null;
                 mAlarmStore.updateAlarmDeliveries(a -> adjustDeliveryTimeBasedOnDeviceIdle(a));
+                if (RECORD_DEVICE_IDLE_ALARMS) {
+                    IdleDispatchEntry ent = new IdleDispatchEntry();
+                    ent.uid = alarm.uid;
+                    ent.pkg = alarm.sourcePackage;
+                    ent.tag = alarm.statsTag;
+                    ent.op = "END IDLE";
+                    ent.elapsedRealtime = mInjector.getElapsedRealtime();
+                    ent.argRealtime = alarm.getWhenElapsed();
+                    mAllowWhileIdleDispatches.add(ent);
+                }
             }
             if (mNextWakeFromIdle == alarm) {
                 mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm();
@@ -3829,7 +3773,6 @@
             IntentFilter sdFilter = new IntentFilter();
             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
             sdFilter.addAction(Intent.ACTION_USER_STOPPED);
-            sdFilter.addAction(Intent.ACTION_UID_REMOVED);
             getContext().registerReceiver(this, sdFilter);
         }
 
@@ -3856,12 +3799,7 @@
                         if (userHandle >= 0) {
                             removeUserLocked(userHandle);
                             mAppWakeupHistory.removeForUser(userHandle);
-                        }
-                        return;
-                    case Intent.ACTION_UID_REMOVED:
-                        if (uid >= 0) {
-                            mLastAllowWhileIdleDispatch.delete(uid);
-                            mUseAllowWhileIdleShortTime.delete(uid);
+                            mAllowWhileIdleHistory.removeForUser(userHandle);
                         }
                         return;
                     case Intent.ACTION_PACKAGE_REMOVED:
@@ -3885,6 +3823,7 @@
                         if (uid >= 0) {
                             // package-removed and package-restarted case
                             mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
+                            mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid));
                             removeLocked(uid);
                         } else {
                             // external-applications-unavailable case
@@ -3980,23 +3919,6 @@
         }
 
         @Override
-        public void onUidForeground(int uid, boolean foreground) {
-            synchronized (mLock) {
-                if (foreground) {
-                    mUseAllowWhileIdleShortTime.put(uid, true);
-                    if (mAlarmStore.updateAlarmDeliveries(a -> {
-                        if (a.creatorUid != uid || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
-                            return false;
-                        }
-                        return adjustDeliveryTimeBasedOnBatterySaver(a);
-                    })) {
-                        rescheduleKernelAlarmsLocked();
-                    }
-                }
-            }
-        }
-
-        @Override
         public void removeAlarmsForUid(int uid) {
             synchronized (mLock) {
                 removeForStoppedLocked(uid);
@@ -4273,22 +4195,23 @@
                 notifyBroadcastAlarmPendingLocked(alarm.uid);
             }
             if (allowWhileIdle) {
-                // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
-                mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
-                if ((mAppStateTracker == null)
-                        || mAppStateTracker.isUidInForeground(alarm.creatorUid)) {
-                    mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true);
-                } else {
-                    mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false);
+                final boolean doze = (mPendingIdleUntil != null);
+                final boolean batterySaver = (mAppStateTracker != null
+                        && mAppStateTracker.isForceAllAppsStandbyEnabled());
+                if (doze || batterySaver) {
+                    // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the
+                    // device was in doze or battery saver.
+                    mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage,
+                            UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
+                    mAlarmStore.updateAlarmDeliveries(a -> {
+                        if (a.creatorUid != alarm.creatorUid
+                                || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
+                            return false;
+                        }
+                        return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a))
+                                || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a));
+                    });
                 }
-                mAlarmStore.updateAlarmDeliveries(a -> {
-                    if (a.creatorUid != alarm.creatorUid
-                            || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) {
-                        return false;
-                    }
-                    return adjustDeliveryTimeBasedOnDeviceIdle(a)
-                            | adjustDeliveryTimeBasedOnBatterySaver(a);
-                });
                 if (RECORD_DEVICE_IDLE_ALARMS) {
                     IdleDispatchEntry ent = new IdleDispatchEntry();
                     ent.uid = alarm.uid;
@@ -4300,8 +4223,6 @@
                 }
             }
             if (!isExemptFromAppStandby(alarm)) {
-                final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage,
-                        UserHandle.getUserId(alarm.creatorUid));
                 mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage,
                         UserHandle.getUserId(alarm.creatorUid), nowELAPSED);
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index dba1d4b..18856f7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.job.JobInfo;
 import android.content.BroadcastReceiver;
@@ -25,8 +27,12 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.Pair;
 import android.util.Slog;
+import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
@@ -35,25 +41,44 @@
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.util.StatLogger;
 import com.android.server.JobSchedulerBackgroundThread;
-import com.android.server.job.JobSchedulerService.Constants;
-import com.android.server.job.JobSchedulerService.MaxJobCountsPerMemoryTrimLevel;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.StateController;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Iterator;
 import java.util.List;
 
 /**
- * This class decides, given the various configuration and the system status, how many more jobs
- * can start.
+ * This class decides, given the various configuration and the system status, which jobs can start
+ * and which {@link JobServiceContext} to run each job on.
  */
 class JobConcurrencyManager {
     private static final String TAG = JobSchedulerService.TAG;
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
 
+    static final String CONFIG_KEY_PREFIX_CONCURRENCY = "concurrency_";
+    private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS =
+            CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
+    private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
+
+    // Try to give higher priority types lower values.
+    static final int WORK_TYPE_NONE = 0;
+    static final int WORK_TYPE_TOP = 1 << 0;
+    static final int WORK_TYPE_BG = 1 << 1;
+    private static final int NUM_WORK_TYPES = 2;
+
+    @IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
+            WORK_TYPE_NONE,
+            WORK_TYPE_TOP,
+            WORK_TYPE_BG
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WorkType {
+    }
+
     private final Object mLock;
     private final JobSchedulerService mService;
-    private final JobSchedulerService.Constants mConstants;
     private final Context mContext;
     private final Handler mHandler;
 
@@ -67,6 +92,53 @@
 
     private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
 
+    private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
+            new WorkConfigLimitsPerMemoryTrimLevel(
+                    new WorkTypeConfig("screen_on_normal", 8,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 2)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 6))),
+                    new WorkTypeConfig("screen_on_moderate", 8,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 4))),
+                    new WorkTypeConfig("screen_on_low", 5,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 1))),
+                    new WorkTypeConfig("screen_on_critical", 5,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 1)))
+            );
+    private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_OFF =
+            new WorkConfigLimitsPerMemoryTrimLevel(
+                    new WorkTypeConfig("screen_off_normal", 10,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 6))),
+                    new WorkTypeConfig("screen_off_moderate", 10,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 4))),
+                    new WorkTypeConfig("screen_off_low", 5,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 1))),
+                    new WorkTypeConfig("screen_off_critical", 5,
+                            // defaultMin
+                            List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 1)),
+                            // defaultMax
+                            List.of(Pair.create(WORK_TYPE_BG, 1)))
+            );
+
     /**
      * This array essentially stores the state of mActiveServices array.
      * The ith index stores the job present on the ith JobServiceContext.
@@ -79,10 +151,14 @@
 
     int[] mRecycledPreferredUidForContext = new int[MAX_JOB_CONTEXTS_COUNT];
 
-    /** Max job counts according to the current system state. */
-    private JobSchedulerService.MaxJobCounts mMaxJobCounts;
+    int[] mRecycledWorkTypeForContext = new int[MAX_JOB_CONTEXTS_COUNT];
 
-    private final JobCountTracker mJobCountTracker = new JobCountTracker();
+    private final ArraySet<JobStatus> mRunningJobs = new ArraySet<>();
+
+    private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
+
+    /** Wait for this long after screen off before adjusting the job concurrency. */
+    private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
 
     /** Current memory trim level. */
     private int mLastMemoryTrimLevel;
@@ -106,7 +182,6 @@
     JobConcurrencyManager(JobSchedulerService service) {
         mService = service;
         mLock = mService.mLock;
-        mConstants = service.mConstants;
         mContext = service.getContext();
 
         mHandler = JobSchedulerBackgroundThread.getHandler();
@@ -165,8 +240,7 @@
 
                 // Note: we can't directly do postDelayed(this::rampUpForScreenOn), because
                 // we need the exact same instance for removeCallbacks().
-                mHandler.postDelayed(mRampUpForScreenOff,
-                        mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
+                mHandler.postDelayed(mRampUpForScreenOff, mScreenOffAdjustmentDelayMs);
             }
         }
     }
@@ -174,7 +248,7 @@
     private final Runnable mRampUpForScreenOff = this::rampUpForScreenOff;
 
     /**
-     * Called in {@link Constants#SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS} after
+     * Called in {@link #mScreenOffAdjustmentDelayMs} after
      * the screen turns off, in order to increase concurrency.
      */
     private void rampUpForScreenOff() {
@@ -188,9 +262,7 @@
                 return;
             }
             final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
-            if ((mLastScreenOffRealtime
-                    + mConstants.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS)
-                    > now) {
+            if ((mLastScreenOffRealtime + mScreenOffAdjustmentDelayMs) > now) {
                 return;
             }
 
@@ -204,12 +276,6 @@
         }
     }
 
-    private boolean isFgJob(JobStatus job) {
-        // (It's super confusing PRIORITY_BOUND_FOREGROUND_SERVICE isn't FG here)
-        return job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
-                || job.shouldTreatAsExpeditedJob();
-    }
-
     @GuardedBy("mLock")
     private void refreshSystemStateLocked() {
         final long nowUptime = JobSchedulerService.sUptimeMillisClock.millis();
@@ -232,28 +298,29 @@
     }
 
     @GuardedBy("mLock")
-    private void updateMaxCountsLocked() {
+    private void updateCounterConfigLocked() {
         refreshSystemStateLocked();
 
-        final MaxJobCountsPerMemoryTrimLevel jobCounts = mEffectiveInteractiveState
-                ? mConstants.MAX_JOB_COUNTS_SCREEN_ON
-                : mConstants.MAX_JOB_COUNTS_SCREEN_OFF;
+        final WorkConfigLimitsPerMemoryTrimLevel workConfigs = mEffectiveInteractiveState
+                ? CONFIG_LIMITS_SCREEN_ON : CONFIG_LIMITS_SCREEN_OFF;
 
-
+        WorkTypeConfig workTypeConfig;
         switch (mLastMemoryTrimLevel) {
             case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
-                mMaxJobCounts = jobCounts.moderate;
+                workTypeConfig = workConfigs.moderate;
                 break;
             case ProcessStats.ADJ_MEM_FACTOR_LOW:
-                mMaxJobCounts = jobCounts.low;
+                workTypeConfig = workConfigs.low;
                 break;
             case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
-                mMaxJobCounts = jobCounts.critical;
+                workTypeConfig = workConfigs.critical;
                 break;
             default:
-                mMaxJobCounts = jobCounts.normal;
+                workTypeConfig = workConfigs.normal;
                 break;
         }
+
+        mWorkCountTracker.setConfig(workTypeConfig);
     }
 
     /**
@@ -277,31 +344,26 @@
             Slog.d(TAG, printPendingQueueLocked());
         }
 
-        final JobPackageTracker tracker = mService.mJobPackageTracker;
         final List<JobStatus> pendingJobs = mService.mPendingJobs;
         final List<JobServiceContext> activeServices = mService.mActiveServices;
-        final List<StateController> controllers = mService.mControllers;
-
-        updateMaxCountsLocked();
 
         // To avoid GC churn, we recycle the arrays.
         JobStatus[] contextIdToJobMap = mRecycledAssignContextIdToJobMap;
         boolean[] slotChanged = mRecycledSlotChanged;
         int[] preferredUidForContext = mRecycledPreferredUidForContext;
+        int[] workTypeForContext = mRecycledWorkTypeForContext;
 
+        updateCounterConfigLocked();
+        // Reset everything since we'll re-evaluate the current state.
+        mWorkCountTracker.resetCounts();
 
-        // Initialize the work variables and also count running jobs.
-        mJobCountTracker.reset(
-                mMaxJobCounts.getMaxTotal(),
-                mMaxJobCounts.getMaxBg(),
-                mMaxJobCounts.getMinBg());
-
-        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
+        for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
             final JobServiceContext js = mService.mActiveServices.get(i);
             final JobStatus status = js.getRunningJobLocked();
 
             if ((contextIdToJobMap[i] = status) != null) {
-                mJobCountTracker.incrementRunningJobCount(isFgJob(status));
+                mWorkCountTracker.incrementRunningJobCount(js.getRunningJobWorkType());
+                workTypeForContext[i] = js.getRunningJobWorkType();
             }
 
             slotChanged[i] = false;
@@ -312,41 +374,25 @@
         }
 
         // Next, update the job priorities, and also count the pending FG / BG jobs.
-        for (int i = 0; i < pendingJobs.size(); i++) {
-            final JobStatus pending = pendingJobs.get(i);
+        updateNonRunningPriorities(pendingJobs, true);
 
-            // If job is already running, go to next job.
-            int jobRunningContext = findJobContextIdFromMap(pending, contextIdToJobMap);
-            if (jobRunningContext != -1) {
-                continue;
-            }
-
-            final int priority = mService.evaluateJobPriorityLocked(pending);
-            pending.lastEvaluatedPriority = priority;
-
-            mJobCountTracker.incrementPendingJobCount(isFgJob(pending));
-        }
-
-        mJobCountTracker.onCountDone();
+        mWorkCountTracker.onCountDone();
 
         for (int i = 0; i < pendingJobs.size(); i++) {
             final JobStatus nextPending = pendingJobs.get(i);
 
-            // Unfortunately we need to repeat this relatively expensive check.
-            int jobRunningContext = findJobContextIdFromMap(nextPending, contextIdToJobMap);
-            if (jobRunningContext != -1) {
+            if (mRunningJobs.contains(nextPending)) {
                 continue;
             }
 
             // TODO(171305774): make sure HPJs aren't pre-empted and add dedicated contexts for them
 
-            final boolean isPendingFg = isFgJob(nextPending);
-
             // Find an available slot for nextPending. The context should be available OR
             // it should have lowest priority among all running jobs
             // (sharing the same Uid as nextPending)
             int minPriorityForPreemption = Integer.MAX_VALUE;
             int selectedContextId = -1;
+            int workType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
             boolean startingJob = false;
             for (int j=0; j<MAX_JOB_CONTEXTS_COUNT; j++) {
                 JobStatus job = contextIdToJobMap[j];
@@ -355,7 +401,7 @@
                     final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
                             || (preferredUid == JobServiceContext.NO_PREFERRED_UID);
 
-                    if (preferredUidOkay && mJobCountTracker.canJobStart(isPendingFg)) {
+                    if (preferredUidOkay && workType != WORK_TYPE_NONE) {
                         // This slot is free, and we haven't yet hit the limit on
                         // concurrent jobs...  we can just throw the job in to here.
                         selectedContextId = j;
@@ -391,19 +437,19 @@
             }
             if (startingJob) {
                 // Increase the counters when we're going to start a job.
-                mJobCountTracker.onStartingNewJob(isPendingFg);
+                workTypeForContext[selectedContextId] = workType;
+                mWorkCountTracker.stageJob(workType);
             }
         }
         if (DEBUG) {
             Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
         }
 
-        mJobCountTracker.logStatus();
+        if (DEBUG) {
+            Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
+        }
 
-        tracker.noteConcurrency(mJobCountTracker.getTotalRunningJobCountToNote(),
-                mJobCountTracker.getFgRunningJobCountToNote());
-
-        for (int i=0; i<MAX_JOB_CONTEXTS_COUNT; i++) {
+        for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
             boolean preservePreferredUid = false;
             if (slotChanged[i]) {
                 JobStatus js = activeServices.get(i).getRunningJobLocked();
@@ -421,30 +467,172 @@
                         Slog.d(TAG, "About to run job on context "
                                 + i + ", job: " + pendingJob);
                     }
-                    for (int ic=0; ic<controllers.size(); ic++) {
-                        controllers.get(ic).prepareForExecutionLocked(pendingJob);
-                    }
-                    if (!activeServices.get(i).executeRunnableJob(pendingJob)) {
-                        Slog.d(TAG, "Error executing " + pendingJob);
-                    }
-                    if (pendingJobs.remove(pendingJob)) {
-                        tracker.noteNonpending(pendingJob);
-                    }
+                    startJobLocked(activeServices.get(i), pendingJob, workTypeForContext[i]);
                 }
             }
             if (!preservePreferredUid) {
                 activeServices.get(i).clearPreferredUid();
             }
         }
+        mWorkCountTracker.resetStagingCount();
+        noteConcurrency();
     }
 
-    private static int findJobContextIdFromMap(JobStatus jobStatus, JobStatus[] map) {
-        for (int i=0; i<map.length; i++) {
-            if (map[i] != null && map[i].matches(jobStatus.getUid(), jobStatus.getJobId())) {
-                return i;
+    private void noteConcurrency() {
+        mService.mJobPackageTracker.noteConcurrency(mRunningJobs.size(),
+                // TODO: log per type instead of only TOP
+                mWorkCountTracker.getRunningJobCount(WORK_TYPE_TOP));
+    }
+
+    private void updateNonRunningPriorities(@NonNull final List<JobStatus> pendingJobs,
+            boolean updateCounter) {
+        for (int i = 0; i < pendingJobs.size(); i++) {
+            final JobStatus pending = pendingJobs.get(i);
+
+            // If job is already running, go to next job.
+            if (mRunningJobs.contains(pending)) {
+                continue;
+            }
+
+            pending.lastEvaluatedPriority = mService.evaluateJobPriorityLocked(pending);
+
+            if (updateCounter) {
+                mWorkCountTracker.incrementPendingJobCount(getJobWorkTypes(pending));
             }
         }
-        return -1;
+    }
+
+    private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
+            @WorkType final int workType) {
+        final List<StateController> controllers = mService.mControllers;
+        for (int ic = 0; ic < controllers.size(); ic++) {
+            controllers.get(ic).prepareForExecutionLocked(jobStatus);
+        }
+        if (!worker.executeRunnableJob(jobStatus, workType)) {
+            Slog.e(TAG, "Error executing " + jobStatus);
+            mWorkCountTracker.onStagedJobFailed(workType);
+        } else {
+            mRunningJobs.add(jobStatus);
+            mWorkCountTracker.onJobStarted(workType);
+        }
+        final List<JobStatus> pendingJobs = mService.mPendingJobs;
+        if (pendingJobs.remove(jobStatus)) {
+            mService.mJobPackageTracker.noteNonpending(jobStatus);
+        }
+    }
+
+    void onJobCompletedLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
+            @WorkType final int workType) {
+        mWorkCountTracker.onJobFinished(workType);
+        mRunningJobs.remove(jobStatus);
+        final List<JobStatus> pendingJobs = mService.mPendingJobs;
+        if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
+            updateCounterConfigLocked();
+            // Preemption case needs special care.
+            updateNonRunningPriorities(pendingJobs, false);
+
+            JobStatus highestPriorityJob = null;
+            int highPriWorkType = workType;
+            JobStatus backupJob = null;
+            int backupWorkType = WORK_TYPE_NONE;
+            for (int i = 0; i < pendingJobs.size(); i++) {
+                final JobStatus nextPending = pendingJobs.get(i);
+
+                if (mRunningJobs.contains(nextPending)) {
+                    continue;
+                }
+
+                if (worker.getPreferredUid() != nextPending.getUid()) {
+                    if (backupJob == null) {
+                        int workAsType =
+                                mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                        if (workAsType != WORK_TYPE_NONE) {
+                            backupJob = nextPending;
+                            backupWorkType = workAsType;
+                        }
+                    }
+                    continue;
+                }
+
+                if (highestPriorityJob == null
+                        || highestPriorityJob.lastEvaluatedPriority
+                        < nextPending.lastEvaluatedPriority) {
+                    highestPriorityJob = nextPending;
+                } else {
+                    continue;
+                }
+
+                // In this path, we pre-empted an existing job. We don't fully care about the
+                // reserved slots. We should just run the highest priority job we can find,
+                // though it would be ideal to use an available WorkType slot instead of
+                // overloading slots.
+                final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                if (workAsType == WORK_TYPE_NONE) {
+                    // Just use the preempted job's work type since this new one is technically
+                    // replacing it anyway.
+                    highPriWorkType = workType;
+                } else {
+                    highPriWorkType = workAsType;
+                }
+            }
+            if (highestPriorityJob != null) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Running job " + jobStatus + " as preemption");
+                }
+                mWorkCountTracker.stageJob(highPriWorkType);
+                startJobLocked(worker, highestPriorityJob, highPriWorkType);
+            } else {
+                if (DEBUG) {
+                    Slog.d(TAG, "Couldn't find preemption job for uid " + worker.getPreferredUid());
+                }
+                worker.clearPreferredUid();
+                if (backupJob != null) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Running job " + jobStatus + " instead");
+                    }
+                    mWorkCountTracker.stageJob(backupWorkType);
+                    startJobLocked(worker, backupJob, backupWorkType);
+                }
+            }
+        } else if (pendingJobs.size() > 0) {
+            updateCounterConfigLocked();
+            updateNonRunningPriorities(pendingJobs, false);
+
+            // This slot is now free and we have pending jobs. Start the highest priority job we
+            // find.
+            JobStatus highestPriorityJob = null;
+            int highPriWorkType = workType;
+            for (int i = 0; i < pendingJobs.size(); i++) {
+                final JobStatus nextPending = pendingJobs.get(i);
+
+                if (mRunningJobs.contains(nextPending)) {
+                    continue;
+                }
+
+                final int workAsType = mWorkCountTracker.canJobStart(getJobWorkTypes(nextPending));
+                if (workAsType == WORK_TYPE_NONE) {
+                    continue;
+                }
+                if (highestPriorityJob == null
+                        || highestPriorityJob.lastEvaluatedPriority
+                        < nextPending.lastEvaluatedPriority) {
+                    highestPriorityJob = nextPending;
+                    highPriWorkType = workAsType;
+                }
+            }
+
+            if (highestPriorityJob != null) {
+                // This slot is free, and we haven't yet hit the limit on
+                // concurrent jobs...  we can just throw the job in to here.
+                if (DEBUG) {
+                    Slog.d(TAG, "About to run job: " + jobStatus);
+                }
+                mWorkCountTracker.stageJob(highPriWorkType);
+                startJobLocked(worker, highestPriorityJob, highPriWorkType);
+            }
+        }
+
+        noteConcurrency();
     }
 
     @GuardedBy("mLock")
@@ -473,12 +661,42 @@
         return s.toString();
     }
 
+    void updateConfigLocked() {
+        DeviceConfig.Properties properties =
+                DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+
+        mScreenOffAdjustmentDelayMs = properties.getLong(
+                KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS);
+
+        CONFIG_LIMITS_SCREEN_ON.normal.update(properties);
+        CONFIG_LIMITS_SCREEN_ON.moderate.update(properties);
+        CONFIG_LIMITS_SCREEN_ON.low.update(properties);
+        CONFIG_LIMITS_SCREEN_ON.critical.update(properties);
+
+        CONFIG_LIMITS_SCREEN_OFF.normal.update(properties);
+        CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties);
+        CONFIG_LIMITS_SCREEN_OFF.low.update(properties);
+        CONFIG_LIMITS_SCREEN_OFF.critical.update(properties);
+    }
 
     public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
         pw.println("Concurrency:");
 
         pw.increaseIndent();
         try {
+            pw.print("Configuration:");
+            pw.increaseIndent();
+            pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
+            CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
+            CONFIG_LIMITS_SCREEN_ON.moderate.dump(pw);
+            CONFIG_LIMITS_SCREEN_ON.low.dump(pw);
+            CONFIG_LIMITS_SCREEN_ON.critical.dump(pw);
+            CONFIG_LIMITS_SCREEN_OFF.normal.dump(pw);
+            CONFIG_LIMITS_SCREEN_OFF.moderate.dump(pw);
+            CONFIG_LIMITS_SCREEN_OFF.low.dump(pw);
+            CONFIG_LIMITS_SCREEN_OFF.critical.dump(pw);
+            pw.decreaseIndent();
+
             pw.print("Screen state: current ");
             pw.print(mCurrentInteractiveState ? "ON" : "OFF");
             pw.print("  effective ");
@@ -497,7 +715,7 @@
 
             pw.println("Current max jobs:");
             pw.println("  ");
-            pw.println(mJobCountTracker);
+            pw.println(mWorkCountTracker);
 
             pw.println();
 
@@ -523,8 +741,6 @@
         proto.write(JobConcurrencyManagerProto.TIME_SINCE_LAST_SCREEN_OFF_MS,
                 nowRealtime - mLastScreenOffRealtime);
 
-        mJobCountTracker.dumpProto(proto, JobConcurrencyManagerProto.JOB_COUNT_TRACKER);
-
         proto.write(JobConcurrencyManagerProto.MEMORY_TRIM_LEVEL, mLastMemoryTrimLevel);
 
         mStatLogger.dumpProto(proto, JobConcurrencyManagerProto.STATS);
@@ -532,199 +748,364 @@
         proto.end(token);
     }
 
+    int getJobWorkTypes(@NonNull JobStatus js) {
+        int classification = 0;
+        // TODO(171305774): create dedicated work type for EJ and FGS
+        if (js.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP
+                || js.shouldTreatAsExpeditedJob()) {
+            classification |= WORK_TYPE_TOP;
+        } else {
+            classification |= WORK_TYPE_BG;
+        }
+        return classification;
+    }
+
+    @VisibleForTesting
+    static class WorkTypeConfig {
+        private static final String KEY_PREFIX_MAX_TOTAL =
+                CONFIG_KEY_PREFIX_CONCURRENCY + "max_total_";
+        private static final String KEY_PREFIX_MAX_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "max_top_";
+        private static final String KEY_PREFIX_MAX_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "max_bg_";
+        private static final String KEY_PREFIX_MIN_TOP = CONFIG_KEY_PREFIX_CONCURRENCY + "min_top_";
+        private static final String KEY_PREFIX_MIN_BG = CONFIG_KEY_PREFIX_CONCURRENCY + "min_bg_";
+        private final String mConfigIdentifier;
+
+        private int mMaxTotal;
+        private final SparseIntArray mMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final int mDefaultMaxTotal;
+        private final SparseIntArray mDefaultMinReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mDefaultMaxAllowedSlots = new SparseIntArray(NUM_WORK_TYPES);
+
+        WorkTypeConfig(@NonNull String configIdentifier, int defaultMaxTotal,
+                List<Pair<Integer, Integer>> defaultMin, List<Pair<Integer, Integer>> defaultMax) {
+            mConfigIdentifier = configIdentifier;
+            mDefaultMaxTotal = mMaxTotal = Math.min(defaultMaxTotal, MAX_JOB_CONTEXTS_COUNT);
+            int numReserved = 0;
+            for (int i = defaultMin.size() - 1; i >= 0; --i) {
+                mDefaultMinReservedSlots.put(defaultMin.get(i).first, defaultMin.get(i).second);
+                numReserved += defaultMin.get(i).second;
+            }
+            if (mDefaultMaxTotal < 0 || numReserved > mDefaultMaxTotal) {
+                // We only create new configs on boot, so this should trigger during development
+                // (before the code gets checked in), so this makes sure the hard-coded defaults
+                // make sense. DeviceConfig values will be handled gracefully in update().
+                throw new IllegalArgumentException("Invalid default config: t=" + defaultMaxTotal
+                        + " min=" + defaultMin + " max=" + defaultMax);
+            }
+            for (int i = defaultMax.size() - 1; i >= 0; --i) {
+                mDefaultMaxAllowedSlots.put(defaultMax.get(i).first, defaultMax.get(i).second);
+            }
+            update(new DeviceConfig.Properties.Builder(
+                    DeviceConfig.NAMESPACE_JOB_SCHEDULER).build());
+        }
+
+        void update(@NonNull DeviceConfig.Properties properties) {
+            // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
+            mMaxTotal = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
+                    properties.getInt(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mDefaultMaxTotal)));
+
+            mMaxAllowedSlots.clear();
+            // Ensure they're in the range [1, total].
+            final int maxTop = Math.max(1, Math.min(mMaxTotal,
+                    properties.getInt(KEY_PREFIX_MAX_TOP + mConfigIdentifier,
+                            mDefaultMaxAllowedSlots.get(WORK_TYPE_TOP, mMaxTotal))));
+            mMaxAllowedSlots.put(WORK_TYPE_TOP, maxTop);
+            final int maxBg = Math.max(1, Math.min(mMaxTotal,
+                    properties.getInt(KEY_PREFIX_MAX_BG + mConfigIdentifier,
+                            mDefaultMaxAllowedSlots.get(WORK_TYPE_BG, mMaxTotal))));
+            mMaxAllowedSlots.put(WORK_TYPE_BG, maxBg);
+
+            int remaining = mMaxTotal;
+            mMinReservedSlots.clear();
+            // Ensure top is in the range [1, min(maxTop, total)]
+            final int minTop = Math.max(1, Math.min(Math.min(maxTop, mMaxTotal),
+                    properties.getInt(KEY_PREFIX_MIN_TOP + mConfigIdentifier,
+                            mDefaultMinReservedSlots.get(WORK_TYPE_TOP))));
+            mMinReservedSlots.put(WORK_TYPE_TOP, minTop);
+            remaining -= minTop;
+            // Ensure bg is in the range [0, min(maxBg, remaining)]
+            final int minBg = Math.max(0, Math.min(Math.min(maxBg, remaining),
+                    properties.getInt(KEY_PREFIX_MIN_BG + mConfigIdentifier,
+                            mDefaultMinReservedSlots.get(WORK_TYPE_BG))));
+            mMinReservedSlots.put(WORK_TYPE_BG, minBg);
+        }
+
+        int getMaxTotal() {
+            return mMaxTotal;
+        }
+
+        int getMax(@WorkType int workType) {
+            return mMaxAllowedSlots.get(workType, mMaxTotal);
+        }
+
+        int getMinReserved(@WorkType int workType) {
+            return mMinReservedSlots.get(workType);
+        }
+
+        void dump(IndentingPrintWriter pw) {
+            pw.print(KEY_PREFIX_MAX_TOTAL + mConfigIdentifier, mMaxTotal).println();
+            pw.print(KEY_PREFIX_MIN_TOP + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_TOP))
+                    .println();
+            pw.print(KEY_PREFIX_MAX_TOP + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_TOP))
+                    .println();
+            pw.print(KEY_PREFIX_MIN_BG + mConfigIdentifier, mMinReservedSlots.get(WORK_TYPE_BG))
+                    .println();
+            pw.print(KEY_PREFIX_MAX_BG + mConfigIdentifier, mMaxAllowedSlots.get(WORK_TYPE_BG))
+                    .println();
+        }
+    }
+
+    /** {@link WorkTypeConfig} for each memory trim level. */
+    static class WorkConfigLimitsPerMemoryTrimLevel {
+        public final WorkTypeConfig normal;
+        public final WorkTypeConfig moderate;
+        public final WorkTypeConfig low;
+        public final WorkTypeConfig critical;
+
+        WorkConfigLimitsPerMemoryTrimLevel(WorkTypeConfig normal, WorkTypeConfig moderate,
+                WorkTypeConfig low, WorkTypeConfig critical) {
+            this.normal = normal;
+            this.moderate = moderate;
+            this.low = low;
+            this.critical = critical;
+        }
+    }
+
     /**
-     * This class decides, taking into account {@link #mMaxJobCounts} and how mny jos are running /
-     * pending, how many more job can start.
+     * This class decides, taking into account the current {@link WorkTypeConfig} and how many jobs
+     * are running/pending, how many more job can start.
      *
      * Extracted for testing and logging.
      */
     @VisibleForTesting
-    static class JobCountTracker {
-        private int mConfigNumMaxTotalJobs;
-        private int mConfigNumMaxBgJobs;
-        private int mConfigNumMinBgJobs;
+    static class WorkCountTracker {
+        private int mConfigMaxTotal;
+        private final SparseIntArray mConfigNumReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mConfigAbsoluteMaxSlots = new SparseIntArray(NUM_WORK_TYPES);
 
-        private int mNumRunningFgJobs;
-        private int mNumRunningBgJobs;
+        /**
+         * Numbers may be lower in this than in {@link #mConfigNumReservedSlots} if there aren't
+         * enough ready jobs of a type to take up all of the desired reserved slots.
+         */
+        private final SparseIntArray mNumActuallyReservedSlots = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mNumPendingJobs = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mNumRunningJobs = new SparseIntArray(NUM_WORK_TYPES);
+        private final SparseIntArray mNumStartingJobs = new SparseIntArray(NUM_WORK_TYPES);
+        private int mNumUnspecialized = 0;
+        private int mNumUnspecializedRemaining = 0;
 
-        private int mNumPendingFgJobs;
-        private int mNumPendingBgJobs;
+        void setConfig(@NonNull WorkTypeConfig workTypeConfig) {
+            mConfigMaxTotal = workTypeConfig.getMaxTotal();
+            mConfigNumReservedSlots.put(WORK_TYPE_TOP,
+                    workTypeConfig.getMinReserved(WORK_TYPE_TOP));
+            mConfigNumReservedSlots.put(WORK_TYPE_BG, workTypeConfig.getMinReserved(WORK_TYPE_BG));
+            mConfigAbsoluteMaxSlots.put(WORK_TYPE_TOP, workTypeConfig.getMax(WORK_TYPE_TOP));
+            mConfigAbsoluteMaxSlots.put(WORK_TYPE_BG, workTypeConfig.getMax(WORK_TYPE_BG));
 
-        private int mNumStartingFgJobs;
-        private int mNumStartingBgJobs;
-
-        private int mNumReservedForBg;
-        private int mNumActualMaxFgJobs;
-        private int mNumActualMaxBgJobs;
-
-        void reset(int numTotalMaxJobs, int numMaxBgJobs, int numMinBgJobs) {
-            mConfigNumMaxTotalJobs = numTotalMaxJobs;
-            mConfigNumMaxBgJobs = numMaxBgJobs;
-            mConfigNumMinBgJobs = numMinBgJobs;
-
-            mNumRunningFgJobs = 0;
-            mNumRunningBgJobs = 0;
-
-            mNumPendingFgJobs = 0;
-            mNumPendingBgJobs = 0;
-
-            mNumStartingFgJobs = 0;
-            mNumStartingBgJobs = 0;
-
-            mNumReservedForBg = 0;
-            mNumActualMaxFgJobs = 0;
-            mNumActualMaxBgJobs = 0;
-        }
-
-        void incrementRunningJobCount(boolean isFg) {
-            if (isFg) {
-                mNumRunningFgJobs++;
-            } else {
-                mNumRunningBgJobs++;
+            mNumUnspecialized = mConfigMaxTotal;
+            mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_TOP);
+            mNumUnspecialized -= mConfigNumReservedSlots.get(WORK_TYPE_BG);
+            mNumUnspecializedRemaining = mConfigMaxTotal;
+            for (int i = mNumRunningJobs.size() - 1; i >= 0; --i) {
+                mNumUnspecializedRemaining -= Math.max(mNumRunningJobs.valueAt(i),
+                        mConfigNumReservedSlots.get(mNumRunningJobs.keyAt(i)));
             }
         }
 
-        void incrementPendingJobCount(boolean isFg) {
-            if (isFg) {
-                mNumPendingFgJobs++;
-            } else {
-                mNumPendingBgJobs++;
+        void resetCounts() {
+            mNumActuallyReservedSlots.clear();
+            mNumPendingJobs.clear();
+            mNumRunningJobs.clear();
+            resetStagingCount();
+        }
+
+        void resetStagingCount() {
+            mNumStartingJobs.clear();
+        }
+
+        void incrementRunningJobCount(@WorkType int workType) {
+            mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
+        }
+
+        void incrementPendingJobCount(int workTypes) {
+            // We don't know which type we'll classify the job as when we run it yet, so make sure
+            // we have space in all applicable slots.
+            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
+                mNumPendingJobs.put(WORK_TYPE_TOP, mNumPendingJobs.get(WORK_TYPE_TOP) + 1);
+            }
+            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
+                mNumPendingJobs.put(WORK_TYPE_BG, mNumPendingJobs.get(WORK_TYPE_BG) + 1);
             }
         }
 
-        void onStartingNewJob(boolean isFg) {
-            if (isFg) {
-                mNumStartingFgJobs++;
-            } else {
-                mNumStartingBgJobs++;
+        void stageJob(@WorkType int workType) {
+            final int newNumStartingJobs = mNumStartingJobs.get(workType) + 1;
+            mNumStartingJobs.put(workType, newNumStartingJobs);
+            mNumPendingJobs.put(workType, Math.max(0, mNumPendingJobs.get(workType) - 1));
+            if (newNumStartingJobs + mNumRunningJobs.get(workType)
+                    > mNumActuallyReservedSlots.get(workType)) {
+                mNumUnspecializedRemaining--;
             }
         }
 
+        void onStagedJobFailed(@WorkType int workType) {
+            final int oldNumStartingJobs = mNumStartingJobs.get(workType);
+            if (oldNumStartingJobs == 0) {
+                Slog.e(TAG, "# staged jobs for " + workType + " went negative.");
+                // We are in a bad state. We will eventually recover when the pending list is
+                // regenerated.
+                return;
+            }
+            mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
+            maybeAdjustReservations(workType);
+        }
+
+        private void maybeAdjustReservations(@WorkType int workType) {
+            // Always make sure we reserve the minimum number of slots in case new jobs become ready
+            // soon.
+            final int numRemainingForType = Math.max(mConfigNumReservedSlots.get(workType),
+                    mNumRunningJobs.get(workType) + mNumStartingJobs.get(workType)
+                            + mNumPendingJobs.get(workType));
+            if (numRemainingForType < mNumActuallyReservedSlots.get(workType)) {
+                // We've run all jobs for this type. Let another type use it now.
+                mNumActuallyReservedSlots.put(workType, numRemainingForType);
+                int assignWorkType = WORK_TYPE_NONE;
+                for (int i = 0; i < mNumActuallyReservedSlots.size(); ++i) {
+                    int wt = mNumActuallyReservedSlots.keyAt(i);
+                    if (assignWorkType == WORK_TYPE_NONE || wt < assignWorkType) {
+                        // Try to give this slot to the highest priority one within its limits.
+                        int total = mNumRunningJobs.get(wt) + mNumStartingJobs.get(wt)
+                                + mNumPendingJobs.get(wt);
+                        if (mNumActuallyReservedSlots.valueAt(i) < mConfigAbsoluteMaxSlots.get(wt)
+                                && total > mNumActuallyReservedSlots.valueAt(i)) {
+                            assignWorkType = wt;
+                        }
+                    }
+                }
+                if (assignWorkType != WORK_TYPE_NONE) {
+                    mNumActuallyReservedSlots.put(assignWorkType,
+                            mNumActuallyReservedSlots.get(assignWorkType) + 1);
+                } else {
+                    mNumUnspecializedRemaining++;
+                }
+            }
+        }
+
+        void onJobStarted(@WorkType int workType) {
+            mNumRunningJobs.put(workType, mNumRunningJobs.get(workType) + 1);
+            final int oldNumStartingJobs = mNumStartingJobs.get(workType);
+            if (oldNumStartingJobs == 0) {
+                Slog.e(TAG, "# stated jobs for " + workType + " went negative.");
+                // We are in a bad state. We will eventually recover when the pending list is
+                // regenerated. For now, only modify the running count.
+            } else {
+                mNumStartingJobs.put(workType, oldNumStartingJobs - 1);
+            }
+        }
+
+        void onJobFinished(@WorkType int workType) {
+            final int newNumRunningJobs = mNumRunningJobs.get(workType) - 1;
+            if (newNumRunningJobs < 0) {
+                // We are in a bad state. We will eventually recover when the pending list is
+                // regenerated.
+                Slog.e(TAG, "# running jobs for " + workType + " went negative.");
+                return;
+            }
+            mNumRunningJobs.put(workType, newNumRunningJobs);
+            maybeAdjustReservations(workType);
+        }
+
         void onCountDone() {
-            // Note some variables are used only here but are made class members in order to have
-            // them on logcat / dumpsys.
+            // Calculate how many slots to reserve for each work type. "Unspecialized" slots will
+            // be reserved for higher importance types first (ie. top before bg).
+            mNumUnspecialized = mConfigMaxTotal;
+            final int numTop = mNumRunningJobs.get(WORK_TYPE_TOP)
+                    + mNumPendingJobs.get(WORK_TYPE_TOP);
+            int resTop = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_TOP), numTop);
+            mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop);
+            mNumUnspecialized -= resTop;
+            final int numBg = mNumRunningJobs.get(WORK_TYPE_BG) + mNumPendingJobs.get(WORK_TYPE_BG);
+            int resBg = Math.min(mConfigNumReservedSlots.get(WORK_TYPE_BG), numBg);
+            mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg);
+            mNumUnspecialized -= resBg;
 
-            // How many slots should we allocate to BG jobs at least?
-            // That's basically "getMinBg()", but if there are less jobs, decrease it.
-            // (e.g. even if min-bg is 2, if there's only 1 running+pending job, this has to be 1.)
-            final int reservedForBg = Math.min(
-                    mConfigNumMinBgJobs,
-                    mNumRunningBgJobs + mNumPendingBgJobs);
-
-            // However, if there are FG jobs already running, we have to adjust it.
-            mNumReservedForBg = Math.min(reservedForBg,
-                    mConfigNumMaxTotalJobs - mNumRunningFgJobs);
-
-            // Max FG is [total - [number needed for BG jobs]]
-            // [number needed for BG jobs] is the bigger one of [running BG] or [reserved BG]
-            final int maxFg =
-                    mConfigNumMaxTotalJobs - Math.max(mNumRunningBgJobs, mNumReservedForBg);
-
-            // The above maxFg is the theoretical max. If there are less FG jobs, the actual
-            // max FG will be lower accordingly.
-            mNumActualMaxFgJobs = Math.min(
-                    maxFg,
-                    mNumRunningFgJobs + mNumPendingFgJobs);
-
-            // Max BG is [total - actual max FG], but cap at [config max BG].
-            final int maxBg = Math.min(
-                    mConfigNumMaxBgJobs,
-                    mConfigNumMaxTotalJobs - mNumActualMaxFgJobs);
-
-            // If there are less BG jobs than maxBg, then reduce the actual max BG accordingly.
-            // This isn't needed for the logic to work, but this will give consistent output
-            // on logcat and dumpsys.
-            mNumActualMaxBgJobs = Math.min(
-                    maxBg,
-                    mNumRunningBgJobs + mNumPendingBgJobs);
-        }
-
-        boolean canJobStart(boolean isFg) {
-            if (isFg) {
-                return mNumRunningFgJobs + mNumStartingFgJobs < mNumActualMaxFgJobs;
-            } else {
-                return mNumRunningBgJobs + mNumStartingBgJobs < mNumActualMaxBgJobs;
+            mNumUnspecializedRemaining = mNumUnspecialized;
+            // Account for already running jobs after we've assigned the minimum number of slots.
+            int unspecializedAssigned;
+            int extraRunning = (mNumRunningJobs.get(WORK_TYPE_TOP) - resTop);
+            if (extraRunning > 0) {
+                unspecializedAssigned = Math.max(0,
+                        Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP) - resTop,
+                                extraRunning));
+                resTop += unspecializedAssigned;
+                mNumUnspecializedRemaining -= extraRunning;
             }
-        }
-
-        public int getNumStartingFgJobs() {
-            return mNumStartingFgJobs;
-        }
-
-        public int getNumStartingBgJobs() {
-            return mNumStartingBgJobs;
-        }
-
-        int getTotalRunningJobCountToNote() {
-            return mNumRunningFgJobs + mNumRunningBgJobs
-                    + mNumStartingFgJobs + mNumStartingBgJobs;
-        }
-
-        int getFgRunningJobCountToNote() {
-            return mNumRunningFgJobs + mNumStartingFgJobs;
-        }
-
-        void logStatus() {
-            if (DEBUG) {
-                Slog.d(TAG, "assignJobsToContexts: " + this);
+            extraRunning = (mNumRunningJobs.get(WORK_TYPE_BG) - resBg);
+            if (extraRunning > 0) {
+                unspecializedAssigned = Math.max(0,
+                        Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG) - resBg, extraRunning));
+                resBg += unspecializedAssigned;
+                mNumUnspecializedRemaining -= extraRunning;
             }
+
+            // Assign remaining unspecialized based on ranking.
+            unspecializedAssigned = Math.max(0,
+                    Math.min(mNumUnspecializedRemaining,
+                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP), numTop) - resTop));
+            mNumActuallyReservedSlots.put(WORK_TYPE_TOP, resTop + unspecializedAssigned);
+            mNumUnspecializedRemaining -= unspecializedAssigned;
+            unspecializedAssigned = Math.max(0,
+                    Math.min(mNumUnspecializedRemaining,
+                            Math.min(mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG), numBg) - resBg));
+            mNumActuallyReservedSlots.put(WORK_TYPE_BG, resBg + unspecializedAssigned);
+            mNumUnspecializedRemaining -= unspecializedAssigned;
+        }
+
+        int canJobStart(int workTypes) {
+            if ((workTypes & WORK_TYPE_TOP) == WORK_TYPE_TOP) {
+                final int maxAllowed = Math.min(
+                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_TOP),
+                        mNumActuallyReservedSlots.get(WORK_TYPE_TOP) + mNumUnspecializedRemaining);
+                if (mNumRunningJobs.get(WORK_TYPE_TOP) + mNumStartingJobs.get(WORK_TYPE_TOP)
+                        < maxAllowed) {
+                    return WORK_TYPE_TOP;
+                }
+            }
+            if ((workTypes & WORK_TYPE_BG) == WORK_TYPE_BG) {
+                final int maxAllowed = Math.min(
+                        mConfigAbsoluteMaxSlots.get(WORK_TYPE_BG),
+                        mNumActuallyReservedSlots.get(WORK_TYPE_BG) + mNumUnspecializedRemaining);
+                if (mNumRunningJobs.get(WORK_TYPE_BG) + mNumStartingJobs.get(WORK_TYPE_BG)
+                        < maxAllowed) {
+                    return WORK_TYPE_BG;
+                }
+            }
+            return WORK_TYPE_NONE;
+        }
+
+        int getRunningJobCount(@WorkType final int workType) {
+            return mNumRunningJobs.get(workType, 0);
         }
 
         public String toString() {
-            final int totalFg = mNumRunningFgJobs + mNumStartingFgJobs;
-            final int totalBg = mNumRunningBgJobs + mNumStartingBgJobs;
-            return String.format(
-                    "Config={tot=%d bg min/max=%d/%d}"
-                            + " Running[FG/BG (total)]: %d / %d (%d)"
-                            + " Pending: %d / %d (%d)"
-                            + " Actual max: %d%s / %d%s (%d%s)"
-                            + " Res BG: %d"
-                            + " Starting: %d / %d (%d)"
-                            + " Total: %d%s / %d%s (%d%s)",
-                    mConfigNumMaxTotalJobs, mConfigNumMinBgJobs, mConfigNumMaxBgJobs,
+            StringBuilder sb = new StringBuilder();
 
-                    mNumRunningFgJobs, mNumRunningBgJobs, mNumRunningFgJobs + mNumRunningBgJobs,
+            sb.append("Config={");
+            sb.append("tot=").append(mConfigMaxTotal);
+            sb.append(" mins=");
+            sb.append(mConfigNumReservedSlots);
+            sb.append(" maxs=");
+            sb.append(mConfigAbsoluteMaxSlots);
+            sb.append("}");
 
-                    mNumPendingFgJobs, mNumPendingBgJobs, mNumPendingFgJobs + mNumPendingBgJobs,
+            sb.append(", act res=").append(mNumActuallyReservedSlots);
+            sb.append(", Pending=").append(mNumPendingJobs);
+            sb.append(", Running=").append(mNumRunningJobs);
+            sb.append(", Staged=").append(mNumStartingJobs);
+            sb.append(", # unspecialized remaining=").append(mNumUnspecializedRemaining);
 
-                    mNumActualMaxFgJobs, (totalFg <= mConfigNumMaxTotalJobs) ? "" : "*",
-                    mNumActualMaxBgJobs, (totalBg <= mConfigNumMaxBgJobs) ? "" : "*",
-                    mNumActualMaxFgJobs + mNumActualMaxBgJobs,
-                    (mNumActualMaxFgJobs + mNumActualMaxBgJobs <= mConfigNumMaxTotalJobs)
-                            ? "" : "*",
-
-                    mNumReservedForBg,
-
-                    mNumStartingFgJobs, mNumStartingBgJobs, mNumStartingFgJobs + mNumStartingBgJobs,
-
-                    totalFg, (totalFg <= mNumActualMaxFgJobs) ? "" : "*",
-                    totalBg, (totalBg <= mNumActualMaxBgJobs) ? "" : "*",
-                    totalFg + totalBg, (totalFg + totalBg <= mConfigNumMaxTotalJobs) ? "" : "*"
-            );
-        }
-
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_TOTAL_JOBS, mConfigNumMaxTotalJobs);
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MAX_BG_JOBS, mConfigNumMaxBgJobs);
-            proto.write(JobCountTrackerProto.CONFIG_NUM_MIN_BG_JOBS, mConfigNumMinBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_RUNNING_FG_JOBS, mNumRunningFgJobs);
-            proto.write(JobCountTrackerProto.NUM_RUNNING_BG_JOBS, mNumRunningBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_PENDING_FG_JOBS, mNumPendingFgJobs);
-            proto.write(JobCountTrackerProto.NUM_PENDING_BG_JOBS, mNumPendingBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_ACTUAL_MAX_FG_JOBS, mNumActualMaxFgJobs);
-            proto.write(JobCountTrackerProto.NUM_ACTUAL_MAX_BG_JOBS, mNumActualMaxBgJobs);
-
-            proto.write(JobCountTrackerProto.NUM_RESERVED_FOR_BG, mNumReservedForBg);
-
-            proto.write(JobCountTrackerProto.NUM_STARTING_FG_JOBS, mNumStartingFgJobs);
-            proto.write(JobCountTrackerProto.NUM_STARTING_BG_JOBS, mNumStartingBgJobs);
-
-            proto.end(token);
+            return sb.toString();
         }
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 97ba815..7ce867c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -369,12 +369,6 @@
                         case Constants.KEY_MODERATE_USE_FACTOR:
                             mConstants.updateUseFactorConstantsLocked();
                             break;
-                        case Constants.KEY_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS:
-                            if (!concurrencyUpdated) {
-                                mConstants.updateConcurrencyConstantsLocked();
-                                concurrencyUpdated = true;
-                            }
-                            break;
                         case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS:
                         case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS:
                             mConstants.updateBackoffConstantsLocked();
@@ -384,10 +378,9 @@
                             mConstants.updateConnectivityConstantsLocked();
                             break;
                         default:
-                            // Too many max_job_* strings to list.
-                            if (name.startsWith(Constants.KEY_PREFIX_MAX_JOB)
+                            if (name.startsWith(JobConcurrencyManager.CONFIG_KEY_PREFIX_CONCURRENCY)
                                     && !concurrencyUpdated) {
-                                mConstants.updateConcurrencyConstantsLocked();
+                                mConcurrencyManager.updateConfigLocked();
                                 concurrencyUpdated = true;
                             } else {
                                 for (int ctrlr = 0; ctrlr < mControllers.size(); ctrlr++) {
@@ -414,119 +407,6 @@
                 mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
     }
 
-    static class MaxJobCounts {
-        private final int mTotalDefault;
-        private final String mTotalKey;
-        private final int mMaxBgDefault;
-        private final String mMaxBgKey;
-        private final int mMinBgDefault;
-        private final String mMinBgKey;
-        private int mTotal;
-        private int mMaxBg;
-        private int mMinBg;
-
-        MaxJobCounts(int totalDefault, String totalKey,
-                int maxBgDefault, String maxBgKey, int minBgDefault, String minBgKey) {
-            mTotalKey = totalKey;
-            mTotal = mTotalDefault = totalDefault;
-            mMaxBgKey = maxBgKey;
-            mMaxBg = mMaxBgDefault = maxBgDefault;
-            mMinBgKey = minBgKey;
-            mMinBg = mMinBgDefault = minBgDefault;
-        }
-
-        public void update() {
-            mTotal = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    mTotalKey, mTotalDefault);
-            mMaxBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    mMaxBgKey, mMaxBgDefault);
-            mMinBg = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    mMinBgKey, mMinBgDefault);
-
-            // Ensure total in the range [1, MAX_JOB_CONTEXTS_COUNT].
-            mTotal = Math.min(Math.max(1, mTotal), MAX_JOB_CONTEXTS_COUNT);
-
-            // Ensure maxBg in the range [1, total].
-            mMaxBg = Math.min(Math.max(1, mMaxBg), mTotal);
-
-            // Ensure minBg in the range [0, min(maxBg, total - 1)]
-            mMinBg = Math.min(Math.max(0, mMinBg), Math.min(mMaxBg, mTotal - 1));
-        }
-
-        /** Total number of jobs to run simultaneously. */
-        public int getMaxTotal() {
-            return mTotal;
-        }
-
-        /** Max number of BG (== owned by non-TOP apps) jobs to run simultaneously. */
-        public int getMaxBg() {
-            return mMaxBg;
-        }
-
-        /**
-         * We try to run at least this many BG (== owned by non-TOP apps) jobs, when there are any
-         * pending, rather than always running the TOTAL number of FG jobs.
-         */
-        public int getMinBg() {
-            return mMinBg;
-        }
-
-        public void dump(PrintWriter pw, String prefix) {
-            pw.print(prefix);
-            pw.print(mTotalKey);
-            pw.print("=");
-            pw.print(mTotal);
-            pw.println();
-
-            pw.print(prefix);
-            pw.print(mMaxBgKey);
-            pw.print("=");
-            pw.print(mMaxBg);
-            pw.println();
-
-            pw.print(prefix);
-            pw.print(mMinBgKey);
-            pw.print("=");
-            pw.print(mMinBg);
-            pw.println();
-        }
-
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-            proto.write(MaxJobCountsProto.TOTAL_JOBS, mTotal);
-            proto.write(MaxJobCountsProto.MAX_BG, mMaxBg);
-            proto.write(MaxJobCountsProto.MIN_BG, mMinBg);
-            proto.end(token);
-        }
-    }
-
-    /** {@link MaxJobCounts} for each memory trim level. */
-    static class MaxJobCountsPerMemoryTrimLevel {
-        public final MaxJobCounts normal;
-        public final MaxJobCounts moderate;
-        public final MaxJobCounts low;
-        public final MaxJobCounts critical;
-
-        MaxJobCountsPerMemoryTrimLevel(
-                MaxJobCounts normal,
-                MaxJobCounts moderate, MaxJobCounts low,
-                MaxJobCounts critical) {
-            this.normal = normal;
-            this.moderate = moderate;
-            this.low = low;
-            this.critical = critical;
-        }
-
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-            normal.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.NORMAL);
-            moderate.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.MODERATE);
-            low.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.LOW);
-            critical.dumpProto(proto, MaxJobCountsPerMemoryTrimLevelProto.CRITICAL);
-            proto.end(token);
-        }
-    }
-
     /**
      * All times are in milliseconds. Any access to this class or its fields should be done while
      * holding the JobSchedulerService.mLock lock.
@@ -552,9 +432,6 @@
         private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
                 "aq_schedule_return_failure";
 
-        private static final String KEY_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
-                "screen_off_job_concurrency_increase_delay_ms";
-
         private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
         private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS;
         private static final float DEFAULT_HEAVY_USE_FACTOR = .9f;
@@ -568,7 +445,6 @@
         private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
         private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
         private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
-        private static final long DEFAULT_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS = 30_000;
 
         /**
          * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early.
@@ -590,53 +466,6 @@
          */
         float MODERATE_USE_FACTOR = DEFAULT_MODERATE_USE_FACTOR;
 
-        /** Prefix for all of the max_job constants. */
-        private static final String KEY_PREFIX_MAX_JOB = "max_job_";
-
-        // Max job counts for screen on / off, for each memory trim level.
-        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_ON =
-                new MaxJobCountsPerMemoryTrimLevel(
-                        new MaxJobCounts(
-                                8, KEY_PREFIX_MAX_JOB + "total_on_normal",
-                                6, KEY_PREFIX_MAX_JOB + "max_bg_on_normal",
-                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_normal"),
-                        new MaxJobCounts(
-                                8, KEY_PREFIX_MAX_JOB + "total_on_moderate",
-                                4, KEY_PREFIX_MAX_JOB + "max_bg_on_moderate",
-                                2, KEY_PREFIX_MAX_JOB + "min_bg_on_moderate"),
-                        new MaxJobCounts(
-                                5, KEY_PREFIX_MAX_JOB + "total_on_low",
-                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_low",
-                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_low"),
-                        new MaxJobCounts(
-                                5, KEY_PREFIX_MAX_JOB + "total_on_critical",
-                                1, KEY_PREFIX_MAX_JOB + "max_bg_on_critical",
-                                1, KEY_PREFIX_MAX_JOB + "min_bg_on_critical"));
-
-        final MaxJobCountsPerMemoryTrimLevel MAX_JOB_COUNTS_SCREEN_OFF =
-                new MaxJobCountsPerMemoryTrimLevel(
-                        new MaxJobCounts(
-                                10, KEY_PREFIX_MAX_JOB + "total_off_normal",
-                                6, KEY_PREFIX_MAX_JOB + "max_bg_off_normal",
-                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_normal"),
-                        new MaxJobCounts(
-                                10, KEY_PREFIX_MAX_JOB + "total_off_moderate",
-                                4, KEY_PREFIX_MAX_JOB + "max_bg_off_moderate",
-                                2, KEY_PREFIX_MAX_JOB + "min_bg_off_moderate"),
-                        new MaxJobCounts(
-                                5, KEY_PREFIX_MAX_JOB + "total_off_low",
-                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_low",
-                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_low"),
-                        new MaxJobCounts(
-                                5, KEY_PREFIX_MAX_JOB + "total_off_critical",
-                                1, KEY_PREFIX_MAX_JOB + "max_bg_off_critical",
-                                1, KEY_PREFIX_MAX_JOB + "min_bg_off_critical"));
-
-
-        /** Wait for this long after screen off before increasing the job concurrency. */
-        long SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS =
-                DEFAULT_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS;
-
         /**
          * The minimum backoff time to allow for linear backoff.
          */
@@ -700,23 +529,6 @@
                     DEFAULT_MODERATE_USE_FACTOR);
         }
 
-        void updateConcurrencyConstantsLocked() {
-            MAX_JOB_COUNTS_SCREEN_ON.normal.update();
-            MAX_JOB_COUNTS_SCREEN_ON.moderate.update();
-            MAX_JOB_COUNTS_SCREEN_ON.low.update();
-            MAX_JOB_COUNTS_SCREEN_ON.critical.update();
-
-            MAX_JOB_COUNTS_SCREEN_OFF.normal.update();
-            MAX_JOB_COUNTS_SCREEN_OFF.moderate.update();
-            MAX_JOB_COUNTS_SCREEN_OFF.low.update();
-            MAX_JOB_COUNTS_SCREEN_OFF.critical.update();
-
-            SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS = DeviceConfig.getLong(
-                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    KEY_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS,
-                    DEFAULT_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
-        }
-
         private void updateBackoffConstantsLocked() {
             MIN_LINEAR_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_MIN_LINEAR_BACKOFF_TIME_MS,
@@ -766,19 +578,6 @@
             pw.print(KEY_HEAVY_USE_FACTOR, HEAVY_USE_FACTOR).println();
             pw.print(KEY_MODERATE_USE_FACTOR, MODERATE_USE_FACTOR).println();
 
-            MAX_JOB_COUNTS_SCREEN_ON.normal.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_ON.moderate.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_ON.low.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_ON.critical.dump(pw, "");
-
-            MAX_JOB_COUNTS_SCREEN_OFF.normal.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_OFF.moderate.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_OFF.low.dump(pw, "");
-            MAX_JOB_COUNTS_SCREEN_OFF.critical.dump(pw, "");
-
-            pw.print(KEY_SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS,
-                    SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS).println();
-
             pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
             pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
             pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
@@ -803,12 +602,6 @@
             proto.write(ConstantsProto.HEAVY_USE_FACTOR, HEAVY_USE_FACTOR);
             proto.write(ConstantsProto.MODERATE_USE_FACTOR, MODERATE_USE_FACTOR);
 
-            MAX_JOB_COUNTS_SCREEN_ON.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_ON);
-            MAX_JOB_COUNTS_SCREEN_OFF.dumpProto(proto, ConstantsProto.MAX_JOB_COUNTS_SCREEN_OFF);
-
-            proto.write(ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS,
-                    SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS);
-
             proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS);
             proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS);
             proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
@@ -1633,8 +1426,8 @@
                 // Create the "runners".
                 for (int i = 0; i < MAX_JOB_CONTEXTS_COUNT; i++) {
                     mActiveServices.add(
-                            new JobServiceContext(this, mBatteryStats, mJobPackageTracker,
-                                    getContext().getMainLooper()));
+                            new JobServiceContext(this, mConcurrencyManager, mBatteryStats,
+                                    mJobPackageTracker, getContext().getMainLooper()));
                 }
                 // Attach jobs to their controllers.
                 mJobs.forEachJob((job) -> {
@@ -1917,9 +1710,6 @@
             if (DEBUG) {
                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
             }
-            // We still want to check for jobs to execute, because this job may have
-            // scheduled a new job under the same job id, and now we can run it.
-            mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
             return;
         }
 
@@ -1941,7 +1731,6 @@
         }
         jobStatus.unprepareLocked();
         reportActiveLocked();
-        mHandler.obtainMessage(MSG_CHECK_JOB_GREEDY).sendToTarget();
     }
 
     // StateChangedListener implementations.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 26b5abe..d15bae0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
@@ -106,6 +107,7 @@
     private final Handler mCallbackHandler;
     /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
     private final JobCompletedListener mCompletedListener;
+    private final JobConcurrencyManager mJobConcurrencyManager;
     /** Used for service binding, etc. */
     private final Context mContext;
     private final Object mLock;
@@ -124,9 +126,12 @@
      *
      * Any reads (dereferences) not done from the handler thread must be synchronized on
      * {@link #mLock}.
-     * Writes can only be done from the handler thread, or {@link #executeRunnableJob(JobStatus)}.
+     * Writes can only be done from the handler thread,
+     * or {@link #executeRunnableJob(JobStatus, int)}.
      */
     private JobStatus mRunningJob;
+    @JobConcurrencyManager.WorkType
+    private int mRunningJobWorkType;
     private JobCallback mRunningCallback;
     /** Used to store next job to run when current job is to be preempted. */
     private int mPreferredUid;
@@ -179,32 +184,29 @@
         }
     }
 
-    JobServiceContext(JobSchedulerService service, IBatteryStats batteryStats,
-            JobPackageTracker tracker, Looper looper) {
-        this(service.getContext(), service.getLock(), batteryStats, tracker, service, looper);
-    }
-
-    @VisibleForTesting
-    JobServiceContext(Context context, Object lock, IBatteryStats batteryStats,
-            JobPackageTracker tracker, JobCompletedListener completedListener, Looper looper) {
-        mContext = context;
-        mLock = lock;
+    JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
+            IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) {
+        mContext = service.getContext();
+        mLock = service.getLock();
         mBatteryStats = batteryStats;
         mJobPackageTracker = tracker;
         mCallbackHandler = new JobServiceHandler(looper);
-        mCompletedListener = completedListener;
+        mJobConcurrencyManager = concurrencyManager;
+        mCompletedListener = service;
         mAvailable = true;
         mVerb = VERB_FINISHED;
         mPreferredUid = NO_PREFERRED_UID;
     }
 
     /**
-     * Give a job to this context for execution. Callers must first check {@link #getRunningJobLocked()}
+     * Give a job to this context for execution. Callers must first check {@link
+     * #getRunningJobLocked()}
      * and ensure it is null to make sure this is a valid context.
+     *
      * @param job The status of the job that we are going to run.
      * @return True if the job is valid and is running. False if the job cannot be executed.
      */
-    boolean executeRunnableJob(JobStatus job) {
+    boolean executeRunnableJob(JobStatus job, @JobConcurrencyManager.WorkType int workType) {
         synchronized (mLock) {
             if (!mAvailable) {
                 Slog.e(TAG, "Starting new runnable but context is unavailable > Error.");
@@ -214,6 +216,7 @@
             mPreferredUid = NO_PREFERRED_UID;
 
             mRunningJob = job;
+            mRunningJobWorkType = workType;
             mRunningCallback = new JobCallback();
             final boolean isDeadlineExpired =
                     job.hasDeadlineConstraint() &&
@@ -282,6 +285,7 @@
                     Slog.d(TAG, job.getServiceComponent().getShortClassName() + " unavailable.");
                 }
                 mRunningJob = null;
+                mRunningJobWorkType = WORK_TYPE_NONE;
                 mRunningCallback = null;
                 mParams = null;
                 mExecutionStartTimeElapsed = 0L;
@@ -326,6 +330,11 @@
         return mRunningJob;
     }
 
+    @JobConcurrencyManager.WorkType
+    int getRunningJobWorkType() {
+        return mRunningJobWorkType;
+    }
+
     /**
      * Used only for debugging. Will return <code>"&lt;null&gt;"</code> if there is no job running.
      */
@@ -828,9 +837,11 @@
         if (mWakeLock != null) {
             mWakeLock.release();
         }
+        final int workType = mRunningJobWorkType;
         mContext.unbindService(JobServiceContext.this);
         mWakeLock = null;
         mRunningJob = null;
+        mRunningJobWorkType = WORK_TYPE_NONE;
         mRunningCallback = null;
         mParams = null;
         mVerb = VERB_FINISHED;
@@ -839,6 +850,7 @@
         mAvailable = true;
         removeOpTimeOutLocked();
         mCompletedListener.onJobCompletedLocked(completedJob, reschedule);
+        mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
     }
 
     private void applyStoppedReasonLocked(String reason) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index e8a2817..d249f2a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.job.controllers;
 
-import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 
@@ -332,7 +331,7 @@
         if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
             final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
-            if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) {
+            if (bandwidth > 0) {
                 // Divide by 8 to convert bits to bytes.
                 final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
                         / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
@@ -350,7 +349,7 @@
         if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
             final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
-            if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) {
+            if (bandwidth > 0) {
                 // Divide by 8 to convert bits to bytes.
                 final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
                         / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
@@ -380,18 +379,16 @@
 
     private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
             NetworkCapabilities capabilities, Constants constants) {
-        final NetworkCapabilities required;
         // A restricted job that's out of quota MUST use an unmetered network.
         if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
                 && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
-            required = new NetworkCapabilities(
+            final NetworkCapabilities required = new NetworkCapabilities.Builder(
                     jobStatus.getJob().getRequiredNetwork().networkCapabilities)
-                    .addCapability(NET_CAPABILITY_NOT_METERED);
+                    .addCapability(NET_CAPABILITY_NOT_METERED).build();
+            return required.satisfiedByNetworkCapabilities(capabilities);
         } else {
-            required = jobStatus.getJob().getRequiredNetwork().networkCapabilities;
+            return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities);
         }
-
-        return required.satisfiedByNetworkCapabilities(capabilities);
     }
 
     private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network,
@@ -402,9 +399,9 @@
         }
 
         // See if we match after relaxing any unmetered request
-        final NetworkCapabilities relaxed = new NetworkCapabilities(
+        final NetworkCapabilities relaxed = new NetworkCapabilities.Builder(
                 jobStatus.getJob().getRequiredNetwork().networkCapabilities)
-                        .removeCapability(NET_CAPABILITY_NOT_METERED);
+                        .removeCapability(NET_CAPABILITY_NOT_METERED).build();
         if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
             // TODO: treat this as "maybe" response; need to check quotas
             return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 09dc7d2..539c3c9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -305,6 +305,7 @@
     public Network network;
     public ServiceInfo serviceInfo;
 
+    /** The evaluated priority of the job when it started running. */
     public int lastEvaluatedPriority;
 
     // If non-null, this is work that has been enqueued for the job.
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl
similarity index 73%
copy from core/java/com/android/internal/net/VpnInfo.aidl
copy to apex/media/aidl/private/android/media/IMediaCommunicationService.aidl
index 6fc97be..3d50d14 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/apex/media/aidl/private/android/media/IMediaCommunicationService.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
+/**
+ * Copyright 2020 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
+ *     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,
@@ -13,7 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.media;
 
-package com.android.internal.net;
+/** {@hide} */
+interface IMediaCommunicationService {
+}
 
-parcelable VpnInfo;
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 60dea07..5773e4d 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -38,6 +38,7 @@
     static_libs: [
         "exoplayer2-extractor",
         "mediatranscoding_aidl_interface-java",
+        "modules-utils-build",
     ],
     jarjar_rules: "jarjar_rules.txt",
 
@@ -52,6 +53,7 @@
     visibility: [
         "//frameworks/av/apex:__subpackages__",
         "//frameworks/base", // For framework-all
+        "//frameworks/base/apex/media/service",
     ],
 }
 
@@ -80,6 +82,7 @@
         "java/android/media/Session2CommandGroup.java",
         "java/android/media/Session2Link.java",
         "java/android/media/Session2Token.java",
+        "java/android/media/MediaCommunicationManager.java",
     ],
     path: "java",
 }
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index 2543a9c..8b9990f 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -28,6 +28,9 @@
     ctor public ApplicationMediaCapabilities.FormatNotFoundException(@NonNull String);
   }
 
+  public class MediaCommunicationManager {
+  }
+
   public class MediaController2 implements java.lang.AutoCloseable {
     method public void cancelSessionCommand(@NonNull Object);
     method public void close();
diff --git a/apex/media/framework/jarjar_rules.txt b/apex/media/framework/jarjar_rules.txt
index d89d9d3..eb71fdd 100644
--- a/apex/media/framework/jarjar_rules.txt
+++ b/apex/media/framework/jarjar_rules.txt
@@ -1 +1,2 @@
+rule com.android.modules.utils.** android.media.internal.utils.@1
 rule com.google.android.exoplayer2.** android.media.internal.exo.@1
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index e1a8596..aefeab6 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -36,30 +36,46 @@
 import java.util.Set;
 
 /**
- * ApplicationMediaCapabilities is an immutable class that encapsulates an application's
- * capabilities for handling newer video codec format and media features.
- *
- * The ApplicationMediaCapabilities class is used by the platform to represent an application's
- * media capabilities as defined in their manifest(TODO: Add link) in order to determine
- * whether modern media files need to be transcoded for that application (TODO: Add link).
- *
- * ApplicationMediaCapabilities objects can also be built by applications at runtime for use with
- * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)} to provide more
- * control over the transcoding that is built into the platform. ApplicationMediaCapabilities
- * provided by applications at runtime like this override the default manifest capabilities for that
- * media access.
- *
- * <h3> Video Codec Support</h3>
- * Newer video codes include HEVC, VP9 and AV1. Application only needs to indicate their support
- * for newer format with this class as they are assumed to support older format like h.264.
- *
- * <h4>Capability of handling HDR(high dynamic range) video</h4>
- * There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform,
- * application will only need to specify individual types they supported.
+ ApplicationMediaCapabilities is an immutable class that encapsulates an application's capabilities
+ for handling newer video codec format and media features.
+
+ <p>
+ Android 12 introduces seamless media transcoding feature. By default, Android assumes apps can
+ support playback of all media formats. Apps that would like to request that media be transcoded
+ into a more compatible format should declare their media capabilities in a media_capabilities
+ .xml resource file and add it as a property tag in the AndroidManifest.xml file. Here is a example:
+ <pre>
+ {@code
+ <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+     <format android:name="HEVC" supported="true"/>
+     <format android:name="HDR10" supported="false"/>
+     <format android:name="HDR10Plus" supported="false"/>
+ </media-capabilities>
+ }
+ </pre>
+ The ApplicationMediaCapabilities class is generated from this xml and used by the platform to
+ represent an application's media capabilities in order to determine whether modern media files need
+ to be transcoded for that application.
+ </p>
+
+ <p>
+ ApplicationMediaCapabilities objects can also be built by applications at runtime for use with
+ {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)} to provide more
+ control over the transcoding that is built into the platform. ApplicationMediaCapabilities
+ provided by applications at runtime like this override the default manifest capabilities for that
+ media access.The object could be build either through {@link #createFromXml(XmlPullParser)} or
+ through the builder class {@link ApplicationMediaCapabilities.Builder}
+
+ <h3> Video Codec Support</h3>
+ <p>
+ Newer video codes include HEVC, VP9 and AV1. Application only needs to indicate their support
+ for newer format with this class as they are assumed to support older format like h.264.
+
+ <h3>Capability of handling HDR(high dynamic range) video</h3>
+ <p>
+ There are four types of HDR video(Dolby-Vision, HDR10, HDR10+, HLG) supported by the platform,
+ application will only need to specify individual types they supported.
  */
-// TODO(huang): Correct openTypedAssetFileDescriptor with the new API after it is added.
-// TODO(hkuang): Add a link to seamless transcoding detail when it is published
-// TODO(hkuang): Add code sample on how to build a capability object with MediaCodecList
 public final class ApplicationMediaCapabilities implements Parcelable {
     private static final String TAG = "ApplicationMediaCapabilities";
 
@@ -105,9 +121,9 @@
      */
     public boolean isVideoMimeTypeSupported(
             @NonNull String videoMime) throws FormatNotFoundException {
-        if (mUnsupportedVideoMimeTypes.contains(videoMime)) {
+        if (mUnsupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
             return false;
-        } else if (mSupportedVideoMimeTypes.contains(videoMime)) {
+        } else if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {
             return true;
         } else {
             throw new FormatNotFoundException(videoMime);
@@ -262,11 +278,27 @@
 
     /**
      * Creates {@link ApplicationMediaCapabilities} from an xml.
+     *
+     * The xml's syntax is the same as the media_capabilities.xml used by the AndroidManifest.xml.
+     * <p> Here is an example:
+     *
+     * <pre>
+     * {@code
+     * <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+     *     <format android:name="HEVC" supported="true"/>
+     *     <format android:name="HDR10" supported="false"/>
+     *     <format android:name="HDR10Plus" supported="false"/>
+     * </media-capabilities>
+     * }
+     * </pre>
+     * <p>
+     *
      * @param xmlParser The underlying {@link XmlPullParser} that will read the xml.
      * @return An ApplicationMediaCapabilities object.
      * @throws UnsupportedOperationException if the capabilities in xml config are invalid or
      * incompatible.
      */
+    // TODO: Add developer.android.com link for the format of the xml.
     @NonNull
     public static ApplicationMediaCapabilities createFromXml(@NonNull XmlPullParser xmlParser) {
         ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder();
@@ -430,7 +462,7 @@
                         mIsSlowMotionSupported = isSupported;
                         break;
                     default:
-                        throw new UnsupportedOperationException("Invalid format name " + name);
+                        Log.w(TAG, "Invalid format name " + name);
                 }
                 // Save the name and isSupported into the map for validate later.
                 mFormatSupportedMap.put(name, isSupported);
diff --git a/apex/media/framework/java/android/media/MediaCommunicationManager.java b/apex/media/framework/java/android/media/MediaCommunicationManager.java
new file mode 100644
index 0000000..b8065ef
--- /dev/null
+++ b/apex/media/framework/java/android/media/MediaCommunicationManager.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 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 android.media;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.Context;
+
+import com.android.modules.utils.build.SdkLevel;
+
+/**
+ * Provides support for interacting with {@link android.media.MediaSession2 MediaSession2s}
+ * that applications have published to express their ongoing media playback state.
+ */
+// TODO: Add notifySession2Created() and sendMessage().
+@SystemService(Context.MEDIA_COMMUNICATION_SERVICE)
+public class MediaCommunicationManager {
+    private static final String TAG = "MediaCommunicationManager";
+
+    private final Context mContext;
+    private final IMediaCommunicationService mService;
+
+    /**
+     * @hide
+     */
+    public MediaCommunicationManager(@NonNull Context context) {
+        if (!SdkLevel.isAtLeastS()) {
+            throw new UnsupportedOperationException("Android version must be S or greater.");
+        }
+        mContext = context;
+        mService = IMediaCommunicationService.Stub.asInterface(
+                MediaFrameworkInitializer.getMediaServiceManager()
+                .getMediaCommunicationServiceRegisterer()
+                .get());
+    }
+}
diff --git a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java b/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
index 813ad7b..9332835 100644
--- a/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
+++ b/apex/media/framework/java/android/media/MediaFrameworkInitializer.java
@@ -19,10 +19,11 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.SystemApi.Client;
-import android.media.MediaTranscodeManager;
 import android.app.SystemServiceRegistry;
 import android.content.Context;
 
+import com.android.modules.utils.build.SdkLevel;
+
 /**
  * Class for performing registration for all media services on com.android.media apex.
  *
@@ -74,5 +75,12 @@
                 MediaTranscodeManager.class,
                 context -> new MediaTranscodeManager(context)
         );
+        if (SdkLevel.isAtLeastS()) {
+            SystemServiceRegistry.registerContextAwareService(
+                    Context.MEDIA_COMMUNICATION_SERVICE,
+                    MediaCommunicationManager.class,
+                    context -> new MediaCommunicationManager(context)
+            );
+        }
     }
 }
diff --git a/apex/media/service/Android.bp b/apex/media/service/Android.bp
new file mode 100644
index 0000000..5b24cfa
--- /dev/null
+++ b/apex/media/service/Android.bp
@@ -0,0 +1,41 @@
+// Copyright 2020 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.
+filegroup {
+    name: "service-media-s-sources",
+    srcs: [
+      "java/**/*.java",
+    ],
+    path: "java",
+    visibility: ["//frameworks/base/services"], // TODO(b/177640454): Should be private.
+}
+
+java_sdk_library {
+    name: "service-media-s",
+    permitted_packages: [
+        "com.android.server.media",
+    ],
+    defaults: ["framework-system-server-module-defaults"],
+    srcs: [
+        ":service-media-s-sources",
+    ],
+    libs: [
+        "updatable-media",
+    ],
+    sdk_version: "system_server_current",
+    min_sdk_version: "29", // TODO: We may need to bump this at some point.
+    apex_available: [
+        "com.android.media",
+    ],
+}
+
diff --git a/apex/permission/service/api/current.txt b/apex/media/service/api/current.txt
similarity index 100%
rename from apex/permission/service/api/current.txt
rename to apex/media/service/api/current.txt
diff --git a/apex/permission/service/api/removed.txt b/apex/media/service/api/removed.txt
similarity index 100%
rename from apex/permission/service/api/removed.txt
rename to apex/media/service/api/removed.txt
diff --git a/apex/permission/service/api/current.txt b/apex/media/service/api/system-server-current.txt
similarity index 100%
copy from apex/permission/service/api/current.txt
copy to apex/media/service/api/system-server-current.txt
diff --git a/apex/permission/service/api/system-server-removed.txt b/apex/media/service/api/system-server-removed.txt
similarity index 100%
rename from apex/permission/service/api/system-server-removed.txt
rename to apex/media/service/api/system-server-removed.txt
diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
new file mode 100644
index 0000000..0468fdf
--- /dev/null
+++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 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.media;
+
+import android.content.Context;
+import android.media.IMediaCommunicationService;
+
+import com.android.server.SystemService;
+
+/**
+ * A system service that managers {@link android.media.MediaSession2} creations
+ * and their ongoing media playback state.
+ * @hide
+ */
+public class MediaCommunicationService extends SystemService {
+
+    public MediaCommunicationService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.MEDIA_COMMUNICATION_SERVICE, new Stub());
+    }
+
+    private class Stub extends IMediaCommunicationService.Stub {
+    }
+}
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
deleted file mode 100644
index be51143..0000000
--- a/apex/permission/Android.bp
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (C) 2019 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.
-
-apex {
-    name: "com.android.permission",
-    defaults: ["com.android.permission-defaults"],
-    manifest: "apex_manifest.json",
-}
-
-apex_defaults {
-    name: "com.android.permission-defaults",
-    updatable: true,
-    min_sdk_version: "30",
-    key: "com.android.permission.key",
-    certificate: ":com.android.permission.certificate",
-    java_libs: [
-        "framework-permission",
-        "framework-permission-s",
-        "service-permission",
-    ],
-    apps: ["PermissionController"],
-}
-
-apex_key {
-    name: "com.android.permission.key",
-    public_key: "com.android.permission.avbpubkey",
-    private_key: "com.android.permission.pem",
-}
-
-android_app_certificate {
-    name: "com.android.permission.certificate",
-    certificate: "com.android.permission",
-}
-
-filegroup {
-    name: "permission-jarjar-rules",
-    srcs: ["jarjar-rules.txt"],
-}
diff --git a/apex/permission/OWNERS b/apex/permission/OWNERS
deleted file mode 100644
index 957e10a..0000000
--- a/apex/permission/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-svetoslavganov@google.com
-moltmann@google.com
-eugenesusla@google.com
-zhanghai@google.com
-evanseverson@google.com
-ntmyren@google.com
diff --git a/apex/permission/TEST_MAPPING b/apex/permission/TEST_MAPPING
deleted file mode 100644
index 6e67ce9..0000000
--- a/apex/permission/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit" : [
-    {
-      "name" : "PermissionApexTests"
-    }
-  ]
-}
diff --git a/apex/permission/apex_manifest.json b/apex/permission/apex_manifest.json
deleted file mode 100644
index 6350d54..0000000
--- a/apex/permission/apex_manifest.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "name": "com.android.permission",
-  "version": 309999999
-}
diff --git a/apex/permission/com.android.permission.avbpubkey b/apex/permission/com.android.permission.avbpubkey
deleted file mode 100644
index 9eaf852..0000000
--- a/apex/permission/com.android.permission.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/apex/permission/com.android.permission.pem b/apex/permission/com.android.permission.pem
deleted file mode 100644
index 3d584be..0000000
--- a/apex/permission/com.android.permission.pem
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIJKgIBAAKCAgEA6snt4eqoz85xiL9Sf6w1S1b9FgSHK05zYTh2JYPvQKQ3yeZp
-E6avJ6FN6XcbmkDzSd658BvUGDBSPhOlzuUO4BsoKBuLMxP6TxIQXFKidzDqY0vQ
-4qkS++bdIhUjwBP3OSZ3Czu0BiihK8GC75Abr//EyCyObGIGGfHEGANiOgrpP4X5
-+OmLzQLCjk4iE1kg+U6cRSRI/XLaoWC0TvIIuzxznrQ6r5GmzgTOwyBWyIB+bj73
-bmsweHTU+w9Y7kGOx4hO3XCLIhoBWEw0EbuW9nZmQ4sZls5Jo/CbyJlCclF11yVo
-SCf2LG/T+9pah5NOmDQ1kPbU+0iKZIV4YFHGTIhyGDE/aPOuUT05ziCGDifgHr0u
-SG1x/RLqsVh/POvNxnvP9cQFMQ08BvbEJaTTgB785iwKsvdqCfmng/SAyxSetmzP
-StXVB3fh1OoZ8vunRbQYxnmUxycVqaA96zmBx2wLvbvzKo7pZFDE6nbhnT5+MRAM
-z/VIK89W26uB4gj8sBFslqZjT0jPqsAZuvDm7swOtMwIcEolyGJuFLqlhN7UwMz2
-9y8+IpYixR+HvD1TZI9NtmuCmv3kPrWgoMZg6yvaBayTIr8RdYzi6FO/C1lLiraz
-48dH3sXWRa8cgw6VcSUwYrEBIc3sotdsupO1iOjcFybIwaee0YTZJfjvbqkCAwEA
-AQKCAgEArRnfdpaJi1xLPGTCMDsIt9kUku0XswgN7PmxsYsKFAB+2S40/jYAIRm9
-1YjpItsMA8RgFfSOdJ77o6TctCMQyo17F8bm4+uwuic5RLfv7Cx2QmsdQF8jDfFx
-y7UGPJD7znjbf76uxXOjEB2FqZX3s9TAgkzHXIUQtoQW7RVhkCWHPjxKxgd5+NY2
-FrDoUpd9xhD9CcTsw1+wbRZdGW88nL6/B50dP2AFORM2VYo8MWr6y9FEn3YLsGOC
-uu7fxBk1aUrHyl81VRkTMMROB1zkuiUk1FtzrEm+5U15rXXBFYOVe9+qeLhtuOlh
-wueDoz0pzvF/JLe24uTik6YL0Ae6SD0pFXQ2KDrdH3cUHLok3r76/yGzaDNTFjS2
-2WbQ8dEJV8veNHk8gjGpFTJIsBUlcZpmUCDHlfvVMb3+2ahQ+28piQUt5t3zqJdZ
-NDqsOHzY6BRPc+Wm85Xii/lWiQceZSee/b1Enu+nchsyXhSenBfC6bIGZReyMI0K
-KKKuVhyR6OSOiR5ZdZ/NyXGqsWy05fn/h0X9hnpETsNaNYNKWvpHLfKll+STJpf7
-AZquJPIclQyiq5NONx6kfPztoCLkKV/zOgIj3Sx5oSZq+5gpO91nXWVwkTbqK1d1
-004q2Mah6UQyAk1XGQc2pHx7ouVcWawjU30vZ4C015Hv2lm/gVkCggEBAPltATYS
-OqOSL1YAtIHPiHxMjNAgUdglq8JiJFXVfkocGU9eNub3Ed3sSWu6GB9Myu/sSKje
-bJ5DZqxJnvB2Fqmu9I9OunLGFSD0aXs4prwsQ1Rm5FcbImtrxcciASdkoo8Pj0z4
-vk2r2NZD3VtER5Uh+YjSDkxcS9gBStXUpCL6gj69UpOxMmWqZVjyHatVB4lEvYJl
-N82uT7N7QVNL1DzcZ9z4C4r7ks1Pm7ka12s5m/oaAlAMdVeofiPJe1xA9zRToSr4
-tIbMkOeXFLVRLuji/7XsOgal5Rl59p+OwLshX5cswPVOMrH6zt+hbsJ5q8M5dqnX
-VAOBK7KNQ/EKZwcCggEBAPD6KVvyCim46n5EbcEqCkO7gevwZkw/9vLwmM5YsxTh
-z9FQkPO0iB7mwbX8w04I91Pre4NdfcgMG0pP1b13Sb4KHBchqW1a+TCs3kSGC6gn
-1SxmXHnA9jRxAkrWlGkoAQEz+aP61cXiiy2tXpQwJ8xQCKprfoqWZwhkCtEVU6CE
-S7v9cscOHIqgNxx4WoceMmq4EoihHAZzHxTcNVbByckMjb2XQJ0iNw3lDP4ddvc+
-a4HzHfHkhzeQ5ZNc8SvWU8z80aSCOKRsSD3aUTZzxhZ4O2tZSW7v7p+FpvVee7bC
-g8YCfszTdpVUMlLRLjScimAcovcFLSvtyupinxWg4M8CggEAN9YGEmOsSte7zwXj
-YrfhtumwEBtcFwX/2Ej+F1Tuq4p0xAa0RaoDjumJWhtTsRYQy/raHSuFpzwxbNoi
-QXQ+CIhI6RfXtz/OlQ0B2/rHoJJMFEXgUfuaDfAXW0eqeHYXyezSyIlamKqipPyW
-Pgsf9yue39keKEv1EorfhNTQVaA8rezV4oglXwrxGyNALw2e3UTNI7ai8mFWKDis
-XAg6n9E7UwUYGGnO6DUtCBgRJ0jDOQ6/e8n+LrxiWIKPIgzNCiK6jpMUXqTGv4Fb
-umdNGAdQ9RnHt5tFmRlrczaSwJFtA7uaCpAR2zPpQbiywchZAiAIB2dTwGEXNiZX
-kksg2wKCAQEA6pNad3qhkgPDoK6T+Jkn7M82paoaqtcJWWwEE7oceZNnbWZz9Agl
-CY+vuawXonrv5+0vCq2Tp4zBdBFLC2h3jFrjBVFrUFxifpOIukOSTVqZFON/2bWQ
-9XOcu6UuSz7522Xw+UNPnZXtzcUacD6AP08ZYGvLfrTyDyTzspyED5k48ALEHCkM
-d5WGkFxII4etpF0TDZVnZo/iDbhe49k4yFFEGO6Ho26PESOLBkNAb2V/2bwDxlij
-l9+g21Z6HiZA5SamHPH2mXgeyrcen1cL2QupK9J6vVcqfnboE6qp2zp2c+Yx8MlY
-gfy4EA44YFaSDQVTTgrn8f9Eq+zc130H2QKCAQEAqOKgv68nIPdDSngNyCVyWego
-boFiDaEJoBBg8FrBjTJ6wFLrNAnXmbvfTtgNmNAzF1cUPJZlIIsHgGrMCfpehbXq
-WQQIw+E+yFbTGLxseGRfsLrV0CsgnAoOVeod+yIHmqc3livaUbrWhL1V2f6Ue+sE
-7YLp/iP43NaMfA4kYk2ep7+ZJoEVkCjHJJaHWgAG3RynPJHkTJlSgu7wLYvGc9uE
-ZsEFUM46lX02t7rrtMfasVGrUy1c2xOxFb4v1vG6iEZ7+YWeq5o3AkxUwEGn+mG4
-/3p+k4AaTXJDXgyZ0Sv6CkGuPHenAYG4cswcUUEf/G4Ag77x6LBNMgycJBxUJA==
------END RSA PRIVATE KEY-----
diff --git a/apex/permission/com.android.permission.pk8 b/apex/permission/com.android.permission.pk8
deleted file mode 100644
index d51673d..0000000
--- a/apex/permission/com.android.permission.pk8
+++ /dev/null
Binary files differ
diff --git a/apex/permission/com.android.permission.x509.pem b/apex/permission/com.android.permission.x509.pem
deleted file mode 100644
index 4b146c9..0000000
--- a/apex/permission/com.android.permission.x509.pem
+++ /dev/null
@@ -1,35 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIGKzCCBBOgAwIBAgIUezo3fQeVZsmLpm/dkpGWJ/G/MN8wDQYJKoZIhvcNAQEL
-BQAwgaMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
-DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
-b2lkMR8wHQYDVQQDDBZjb20uYW5kcm9pZC5wZXJtaXNzaW9uMSIwIAYJKoZIhvcN
-AQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMCAXDTE5MTAwOTIxMzExOVoYDzQ3NTcw
-OTA0MjEzMTE5WjCBozELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx
-FjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB0FuZHJvaWQxEDAOBgNV
-BAsMB0FuZHJvaWQxHzAdBgNVBAMMFmNvbS5hbmRyb2lkLnBlcm1pc3Npb24xIjAg
-BgkqhkiG9w0BCQEWE2FuZHJvaWRAYW5kcm9pZC5jb20wggIiMA0GCSqGSIb3DQEB
-AQUAA4ICDwAwggIKAoICAQCxefguRJ7E6tBCTEOeU2HJEGs6AQQapLz9hMed0aaJ
-Qr7aTQiYJEk+sG4+jPYbjpxa8JDDzJHp+4g7DjfSb+dvT9n84A8lWaI/yRXTZTQN
-Hu5m/bgHhi0LbySpiaFyodXBKUAnOhZyGPtYjtBFywFylueub8ryc1Z6UxxU7udH
-1mkIr7sE48Qkq5SyjFROE96iFmYA+vS/JXOfS0NBHiMB4GBxx4V7kXpvrTI7hhZG
-HiyhKvNh7wyHIhO9nDEw1rwtAH6CsL3YkQEVBeAU98m+0Au+qStLYkKHh2l8zT4W
-7sVK1VSqfB+VqOUmeIGdzlBfqMsoXD+FJz6KnIdUHIwjFDjL7Xr+hd+7xve+Q3S+
-U3Blk/U6atY8PM09wNfilG+SvwcKk5IgriDcu3rWKgIFxbUUaxLrDW7pLlu6wt/d
-GGtKK+Bc0jF+9Z901Tl33i5xhc5yOktT0btkKs7lSeE6VzP/Nk5g0SuzixmuRoh9
-f5Ge41N2ZCEHNXx3wZeVZwHIIPfYrL7Yql1Xoxbfs4ETFk6ChzVQcvjfDQQuK58J
-uNc+TOCoI/qflXwGCwpuHl0ier8V5Z4tpMUl5rWyVR/QGRtLPvs2lLuxczDw1OXq
-wEVtCMn9aNnd4y7R9PZ52hi53HAvDjpWefrLYi+Q04J6iGFQ1qAFBClK9DquBvmR
-swIDAQABo1MwUTAdBgNVHQ4EFgQULpfus5s5SrqLkoUKyPXA0D1iHPMwHwYDVR0j
-BBgwFoAULpfus5s5SrqLkoUKyPXA0D1iHPMwDwYDVR0TAQH/BAUwAwEB/zANBgkq
-hkiG9w0BAQsFAAOCAgEAjxQG5EFv8V/9yV2glI53VOmlWMjfEgvUjd39s/XLyPlr
-OzPOKSB0NFo8To3l4l+MsManxPK8y0OyfEVKbWVz9onv0ovo5MVokBmV/2G0jmsV
-B4e9yjOq+DmqIvY/Qh63Ywb97sTgcFI8620MhQDbh2IpEGv4ZNV0H6rgXmgdSCBw
-1EjBoYfFpN5aMgZjeyzZcq+d1IapdWqdhuEJQkMvoYS4WIumNIJlEXPQRoq/F5Ih
-nszdbKI/jVyiGFa2oeZ3rja1Y6GCRU8TYEoKx1pjS8uQDOEDTwsG/QnUe9peEj0V
-SsCkIidJWTomAmq9Tub9vpBe1zuTpuRAwxwR0qwgSxozV1Mvow1dJ19oFtHX0yD6
-ZjCpRn5PW9kMvSWSlrcrFs1NJf0j1Cvf7bHpkEDqLqpMnnh9jaFQq3nzDY+MWcIR
-jDcgQpI+AiE2/qtauZnFEVhbce49nCnk9+5bpTTIZJdzqeaExe5KXHwEtZLaEDh4
-atLY9LuEvPsjmDIMOR6hycD9FvwGXhJOQBjESIWFwigtSb1Yud9n6201jw3MLJ4k
-+WhkbmZgWy+xc+Mdm5H3XyB1lvHaHGkxu+QB9KyQuVQKwbUVcbwZIfTFPN6Zr/dS
-ZXJqAbBhG/dBgF0LazuLaPVpibi+a3Y+tb9b8eXGkz4F97PWZIEDkELQ+9KOvhc=
------END CERTIFICATE-----
diff --git a/apex/permission/framework-s/Android.bp b/apex/permission/framework-s/Android.bp
deleted file mode 100644
index e71cc43..0000000
--- a/apex/permission/framework-s/Android.bp
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (C) 2021 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.
-
-filegroup {
-    name: "framework-permission-s-sources",
-    srcs: [
-        "java/**/*.java",
-        "java/**/*.aidl",
-    ],
-    path: "java",
-    visibility: ["//frameworks/base"],
-}
-
-java_library {
-    name: "framework-permission-s-shared",
-    srcs: [":framework-permission-s-shared-srcs"],
-    libs: [
-        "framework-annotations-lib",
-        "unsupportedappusage",
-    ],
-    apex_available: [
-        "com.android.permission",
-        "test_com.android.permission",
-    ],
-    installable: false,
-    min_sdk_version: "30",
-    sdk_version: "module_current",
-}
-
-java_sdk_library {
-    name: "framework-permission-s",
-    defaults: ["framework-module-defaults"],
-    srcs: [
-        ":framework-permission-s-sources",
-    ],
-    libs: [
-        "framework-annotations-lib"
-    ],
-    static_libs: [
-        "framework-permission-s-shared",
-    ],
-    apex_available: [
-        "com.android.permission",
-        "test_com.android.permission",
-    ],
-    hostdex: true,
-    // Restrict access to implementation library.
-    impl_library_visibility: [
-        "//frameworks/base/apex/permission:__subpackages__",
-        "//packages/modules/Permission:__subpackages__",
-    ],
-    installable: true,
-    jarjar_rules: ":permission-jarjar-rules",
-    min_sdk_version: "30",
-    permitted_packages: [
-        "android.permission",
-        "android.app.role",
-        // For com.android.permission.jarjar.
-        "com.android.permission",
-    ],
-}
diff --git a/apex/permission/framework-s/api/current.txt b/apex/permission/framework-s/api/current.txt
deleted file mode 100644
index 4ecc989..0000000
--- a/apex/permission/framework-s/api/current.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-// Signature format: 2.0
-package android.app.role {
-
-  public final class RoleManager {
-    method @NonNull public android.content.Intent createRequestRoleIntent(@NonNull String);
-    method public boolean isRoleAvailable(@NonNull String);
-    method public boolean isRoleHeld(@NonNull String);
-    field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
-    field public static final String ROLE_BROWSER = "android.app.role.BROWSER";
-    field public static final String ROLE_CALL_REDIRECTION = "android.app.role.CALL_REDIRECTION";
-    field public static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING";
-    field public static final String ROLE_DIALER = "android.app.role.DIALER";
-    field public static final String ROLE_EMERGENCY = "android.app.role.EMERGENCY";
-    field public static final String ROLE_HOME = "android.app.role.HOME";
-    field public static final String ROLE_SMS = "android.app.role.SMS";
-  }
-
-}
-
diff --git a/apex/permission/framework-s/api/module-lib-current.txt b/apex/permission/framework-s/api/module-lib-current.txt
deleted file mode 100644
index d7c9a23..0000000
--- a/apex/permission/framework-s/api/module-lib-current.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-// Signature format: 2.0
-package android.app.role {
-
-  public final class RoleManager {
-    method @Nullable public String getBrowserRoleHolder(int);
-    method @Nullable public String getSmsRoleHolder(int);
-    method @Nullable @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public boolean setBrowserRoleHolder(@Nullable String, int);
-  }
-
-}
-
diff --git a/apex/permission/framework-s/api/module-lib-removed.txt b/apex/permission/framework-s/api/module-lib-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework-s/api/module-lib-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/framework-s/api/removed.txt b/apex/permission/framework-s/api/removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework-s/api/removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/framework-s/api/system-current.txt b/apex/permission/framework-s/api/system-current.txt
deleted file mode 100644
index 6778d48..0000000
--- a/apex/permission/framework-s/api/system-current.txt
+++ /dev/null
@@ -1,43 +0,0 @@
-// Signature format: 2.0
-package android.app.role {
-
-  public interface OnRoleHoldersChangedListener {
-    method public void onRoleHoldersChanged(@NonNull String, @NonNull android.os.UserHandle);
-  }
-
-  @Deprecated public abstract class RoleControllerService extends android.app.Service {
-    ctor @Deprecated public RoleControllerService();
-    method @Deprecated @WorkerThread public abstract boolean onAddRoleHolder(@NonNull String, @NonNull String, int);
-    method @Deprecated @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method @Deprecated @WorkerThread public abstract boolean onClearRoleHolders(@NonNull String, int);
-    method @Deprecated @WorkerThread public abstract boolean onGrantDefaultRoles();
-    method @Deprecated public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String);
-    method @Deprecated public boolean onIsApplicationVisibleForRole(@NonNull String, @NonNull String);
-    method @Deprecated public abstract boolean onIsRoleVisible(@NonNull String);
-    method @Deprecated @WorkerThread public abstract boolean onRemoveRoleHolder(@NonNull String, @NonNull String, int);
-    field @Deprecated public static final String SERVICE_INTERFACE = "android.app.role.RoleControllerService";
-  }
-
-  public class RoleFrameworkInitializer {
-    method public static void registerServiceWrappers();
-  }
-
-  public final class RoleManager {
-    method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void addOnRoleHoldersChangedListenerAsUser(@NonNull java.util.concurrent.Executor, @NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @Deprecated @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
-    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
-    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isApplicationVisibleForRole(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
-    method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
-    method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
-    field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
-  }
-
-}
-
diff --git a/apex/permission/framework-s/api/system-removed.txt b/apex/permission/framework-s/api/system-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework-s/api/system-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/framework-s/java/android/app/role/IOnRoleHoldersChangedListener.aidl b/apex/permission/framework-s/java/android/app/role/IOnRoleHoldersChangedListener.aidl
deleted file mode 100644
index 6cf961f..0000000
--- a/apex/permission/framework-s/java/android/app/role/IOnRoleHoldersChangedListener.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.app.role;
-
-/**
- * @hide
- */
-oneway interface IOnRoleHoldersChangedListener {
-
-    void onRoleHoldersChanged(String roleName, int userId);
-}
diff --git a/apex/permission/framework-s/java/android/app/role/IRoleController.aidl b/apex/permission/framework-s/java/android/app/role/IRoleController.aidl
deleted file mode 100644
index 8a43d7f..0000000
--- a/apex/permission/framework-s/java/android/app/role/IRoleController.aidl
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.app.role;
-
-import android.os.RemoteCallback;
-
-/**
- * @hide
- */
-oneway interface IRoleController {
-
-    void grantDefaultRoles(in RemoteCallback callback);
-
-    void onAddRoleHolder(in String roleName, in String packageName, int flags,
-            in RemoteCallback callback);
-
-    void onRemoveRoleHolder(in String roleName, in String packageName, int flags,
-            in RemoteCallback callback);
-
-    void onClearRoleHolders(in String roleName, int flags, in RemoteCallback callback);
-
-    void isApplicationQualifiedForRole(in String roleName, in String packageName,
-            in RemoteCallback callback);
-
-    void isApplicationVisibleForRole(in String roleName, in String packageName,
-            in RemoteCallback callback);
-
-    void isRoleVisible(in String roleName, in RemoteCallback callback);
-}
diff --git a/apex/permission/framework-s/java/android/app/role/IRoleManager.aidl b/apex/permission/framework-s/java/android/app/role/IRoleManager.aidl
deleted file mode 100644
index 5fc25f0..0000000
--- a/apex/permission/framework-s/java/android/app/role/IRoleManager.aidl
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.app.role;
-
-import android.app.role.IOnRoleHoldersChangedListener;
-import android.os.Bundle;
-import android.os.RemoteCallback;
-
-/**
- * @hide
- */
-interface IRoleManager {
-
-    boolean isRoleAvailable(in String roleName);
-
-    boolean isRoleHeld(in String roleName, in String packageName);
-
-    List<String> getRoleHoldersAsUser(in String roleName, int userId);
-
-    void addRoleHolderAsUser(in String roleName, in String packageName, int flags, int userId,
-            in RemoteCallback callback);
-
-    void removeRoleHolderAsUser(in String roleName, in String packageName, int flags, int userId,
-            in RemoteCallback callback);
-
-    void clearRoleHoldersAsUser(in String roleName, int flags, int userId,
-            in RemoteCallback callback);
-
-    void addOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, int userId);
-
-    void removeOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener,
-            int userId);
-
-    void setRoleNamesFromController(in List<String> roleNames);
-
-    boolean addRoleHolderFromController(in String roleName, in String packageName);
-
-    boolean removeRoleHolderFromController(in String roleName, in String packageName);
-
-    List<String> getHeldRolesFromController(in String packageName);
-
-    String getBrowserRoleHolder(int userId);
-
-    boolean setBrowserRoleHolder(String packageName, int userId);
-
-    String getSmsRoleHolder(int userId);
-}
diff --git a/apex/permission/framework-s/java/android/app/role/OnRoleHoldersChangedListener.java b/apex/permission/framework-s/java/android/app/role/OnRoleHoldersChangedListener.java
deleted file mode 100644
index 5958deb..0000000
--- a/apex/permission/framework-s/java/android/app/role/OnRoleHoldersChangedListener.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.app.role;
-
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.os.UserHandle;
-
-/**
- * Listener for role holder changes.
- *
- * @hide
- */
-@SystemApi
-public interface OnRoleHoldersChangedListener {
-
-    /**
-     * Called when the holders of roles are changed.
-     *
-     * @param roleName the name of the role whose holders are changed
-     * @param user the user for this role holder change
-     */
-    void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user);
-}
diff --git a/apex/permission/framework-s/java/android/app/role/RoleControllerManager.java b/apex/permission/framework-s/java/android/app/role/RoleControllerManager.java
deleted file mode 100644
index 93a7ae0..0000000
--- a/apex/permission/framework-s/java/android/app/role/RoleControllerManager.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.app.role;
-
-import android.Manifest;
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteCallback;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.ServiceConnector;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * Interface for communicating with the role controller.
- *
- * @hide
- */
-public class RoleControllerManager {
-
-    private static final String LOG_TAG = RoleControllerManager.class.getSimpleName();
-
-    private static final long REQUEST_TIMEOUT_MILLIS = 15 * 1000;
-
-    private static volatile ComponentName sRemoteServiceComponentName;
-
-    private static final Object sRemoteServicesLock = new Object();
-
-    /**
-     * Global remote services (per user) used by all {@link RoleControllerManager managers}.
-     */
-    @GuardedBy("sRemoteServicesLock")
-    private static final SparseArray<ServiceConnector<IRoleController>> sRemoteServices =
-            new SparseArray<>();
-
-    @NonNull
-    private final ServiceConnector<IRoleController> mRemoteService;
-
-    /**
-     * Initialize the remote service component name once so that we can avoid acquiring the
-     * PackageManagerService lock in constructor.
-     *
-     * @see #createWithInitializedRemoteServiceComponentName(Handler, Context)
-     *
-     * @hide
-     */
-    public static void initializeRemoteServiceComponentName(@NonNull Context context) {
-        sRemoteServiceComponentName = getRemoteServiceComponentName(context);
-    }
-
-    /**
-     * Create a {@link RoleControllerManager} instance with the initialized remote service component
-     * name so that we can avoid acquiring the PackageManagerService lock in constructor.
-     *
-     * @see #initializeRemoteServiceComponentName(Context)
-     *
-     * @hide
-     */
-    @NonNull
-    public static RoleControllerManager createWithInitializedRemoteServiceComponentName(
-            @NonNull Handler handler, @NonNull Context context) {
-        return new RoleControllerManager(sRemoteServiceComponentName, handler, context);
-    }
-
-    private RoleControllerManager(@NonNull ComponentName remoteServiceComponentName,
-            @NonNull Handler handler, @NonNull Context context) {
-        synchronized (sRemoteServicesLock) {
-            int userId = context.getUser().getIdentifier();
-            ServiceConnector<IRoleController> remoteService = sRemoteServices.get(userId);
-            if (remoteService == null) {
-                remoteService = new ServiceConnector.Impl<IRoleController>(context,
-                        new Intent(RoleControllerService.SERVICE_INTERFACE)
-                                .setComponent(remoteServiceComponentName),
-                        0 /* bindingFlags */, userId, IRoleController.Stub::asInterface) {
-
-                    @Override
-                    protected Handler getJobHandler() {
-                        return handler;
-                    }
-                };
-                sRemoteServices.put(userId, remoteService);
-            }
-            mRemoteService = remoteService;
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public RoleControllerManager(@NonNull Context context) {
-        this(getRemoteServiceComponentName(context), new Handler(Looper.getMainLooper()), context);
-    }
-
-    @NonNull
-    private static ComponentName getRemoteServiceComponentName(@NonNull Context context) {
-        Intent intent = new Intent(RoleControllerService.SERVICE_INTERFACE);
-        PackageManager packageManager = context.getPackageManager();
-        intent.setPackage(packageManager.getPermissionControllerPackageName());
-        ServiceInfo serviceInfo = packageManager.resolveService(intent, 0).serviceInfo;
-        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
-    }
-
-    /**
-     * @see RoleControllerService#onGrantDefaultRoles()
-     *
-     * @hide
-     */
-    public void grantDefaultRoles(@NonNull @CallbackExecutor Executor executor,
-            @NonNull Consumer<Boolean> callback) {
-        AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
-            AndroidFuture<Bundle> future = new AndroidFuture<>();
-            service.grantDefaultRoles(new RemoteCallback(future::complete));
-            return future;
-        });
-        propagateCallback(operation, "grantDefaultRoles", executor, callback);
-    }
-
-    /**
-     * @see RoleControllerService#onAddRoleHolder(String, String, int)
-     *
-     * @hide
-     */
-    public void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
-            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
-        AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
-            AndroidFuture<Bundle> future = new AndroidFuture<>();
-            service.onAddRoleHolder(roleName, packageName, flags,
-                    new RemoteCallback(future::complete));
-            return future;
-        });
-        propagateCallback(operation, "onAddRoleHolder", callback);
-    }
-
-    /**
-     * @see RoleControllerService#onRemoveRoleHolder(String, String, int)
-     *
-     * @hide
-     */
-    public void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
-            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
-        AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
-            AndroidFuture<Bundle> future = new AndroidFuture<>();
-            service.onRemoveRoleHolder(roleName, packageName, flags,
-                    new RemoteCallback(future::complete));
-            return future;
-        });
-        propagateCallback(operation, "onRemoveRoleHolder", callback);
-    }
-
-    /**
-     * @see RoleControllerService#onClearRoleHolders(String, int)
-     *
-     * @hide
-     */
-    public void onClearRoleHolders(@NonNull String roleName,
-            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
-        AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
-            AndroidFuture<Bundle> future = new AndroidFuture<>();
-            service.onClearRoleHolders(roleName, flags,
-                    new RemoteCallback(future::complete));
-            return future;
-        });
-        propagateCallback(operation, "onClearRoleHolders", callback);
-    }
-
-    /**
-     * @see RoleControllerService#onIsApplicationVisibleForRole(String, String)
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    public void isApplicationVisibleForRole(@NonNull String roleName, @NonNull String packageName,
-            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-        AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
-            AndroidFuture<Bundle> future = new AndroidFuture<>();
-            service.isApplicationVisibleForRole(roleName, packageName,
-                    new RemoteCallback(future::complete));
-            return future;
-        });
-        propagateCallback(operation, "isApplicationVisibleForRole", executor, callback);
-    }
-
-    /**
-     * @see RoleControllerService#onIsRoleVisible(String)
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    public void isRoleVisible(@NonNull String roleName,
-            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-        AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
-            AndroidFuture<Bundle> future = new AndroidFuture<>();
-            service.isRoleVisible(roleName, new RemoteCallback(future::complete));
-            return future;
-        });
-        propagateCallback(operation, "isRoleVisible", executor, callback);
-    }
-
-    private void propagateCallback(AndroidFuture<Bundle> operation, String opName,
-            @CallbackExecutor @NonNull Executor executor,
-            Consumer<Boolean> destination) {
-        operation.orTimeout(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
-                .whenComplete((res, err) -> executor.execute(() -> {
-                    final long token = Binder.clearCallingIdentity();
-                    try {
-                        if (err != null) {
-                            Log.e(LOG_TAG, "Error calling " + opName + "()", err);
-                            destination.accept(false);
-                        } else {
-                            destination.accept(res != null);
-                        }
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                }));
-    }
-
-    private void propagateCallback(AndroidFuture<Bundle> operation, String opName,
-            RemoteCallback destination) {
-        operation.orTimeout(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
-                .whenComplete((res, err) -> {
-                    final long token = Binder.clearCallingIdentity();
-                    try {
-                        if (err != null) {
-                            Log.e(LOG_TAG, "Error calling " + opName + "()", err);
-                            destination.sendResult(null);
-                        } else {
-                            destination.sendResult(res);
-                        }
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                });
-    }
-}
diff --git a/apex/permission/framework-s/java/android/app/role/RoleControllerService.java b/apex/permission/framework-s/java/android/app/role/RoleControllerService.java
deleted file mode 100644
index cf78729..0000000
--- a/apex/permission/framework-s/java/android/app/role/RoleControllerService.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2019 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 android.app.role;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.WorkerThread;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteCallback;
-import android.os.UserHandle;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Abstract base class for the role controller service.
- * <p>
- * Subclass should implement the business logic for role management, including enforcing role
- * requirements and granting or revoking relevant privileges of roles. This class can only be
- * implemented by the permission controller app which is registered in {@code PackageManager}.
- *
- * @deprecated The role controller service is an internal implementation detail inside role, and it
- *             may be replaced by other mechanisms in the future and no longer be called.
- *
- * @hide
- */
-@Deprecated
-@SystemApi
-public abstract class RoleControllerService extends Service {
-
-    /**
-     * The {@link Intent} that must be declared as handled by the service.
-     */
-    public static final String SERVICE_INTERFACE = "android.app.role.RoleControllerService";
-
-    private HandlerThread mWorkerThread;
-    private Handler mWorkerHandler;
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-
-        mWorkerThread = new HandlerThread(RoleControllerService.class.getSimpleName());
-        mWorkerThread.start();
-        mWorkerHandler = new Handler(mWorkerThread.getLooper());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-
-        mWorkerThread.quitSafely();
-    }
-
-    @Nullable
-    @Override
-    public final IBinder onBind(@Nullable Intent intent) {
-        return new IRoleController.Stub() {
-
-            @Override
-            public void grantDefaultRoles(RemoteCallback callback) {
-                enforceCallerSystemUid("grantDefaultRoles");
-
-                Objects.requireNonNull(callback, "callback cannot be null");
-
-                mWorkerHandler.post(() -> RoleControllerService.this.grantDefaultRoles(callback));
-            }
-
-            @Override
-            public void onAddRoleHolder(String roleName, String packageName, int flags,
-                    RemoteCallback callback) {
-                enforceCallerSystemUid("onAddRoleHolder");
-
-                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-                Preconditions.checkStringNotEmpty(packageName,
-                        "packageName cannot be null or empty");
-                Objects.requireNonNull(callback, "callback cannot be null");
-
-                mWorkerHandler.post(() -> RoleControllerService.this.onAddRoleHolder(roleName,
-                        packageName, flags, callback));
-            }
-
-            @Override
-            public void onRemoveRoleHolder(String roleName, String packageName, int flags,
-                    RemoteCallback callback) {
-                enforceCallerSystemUid("onRemoveRoleHolder");
-
-                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-                Preconditions.checkStringNotEmpty(packageName,
-                        "packageName cannot be null or empty");
-                Objects.requireNonNull(callback, "callback cannot be null");
-
-                mWorkerHandler.post(() -> RoleControllerService.this.onRemoveRoleHolder(roleName,
-                        packageName, flags, callback));
-            }
-
-            @Override
-            public void onClearRoleHolders(String roleName, int flags, RemoteCallback callback) {
-                enforceCallerSystemUid("onClearRoleHolders");
-
-                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-                Objects.requireNonNull(callback, "callback cannot be null");
-
-                mWorkerHandler.post(() -> RoleControllerService.this.onClearRoleHolders(roleName,
-                        flags, callback));
-            }
-
-            private void enforceCallerSystemUid(@NonNull String methodName) {
-                if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-                    throw new SecurityException("Only the system process can call " + methodName
-                            + "()");
-                }
-            }
-
-            @Override
-            public void isApplicationQualifiedForRole(String roleName, String packageName,
-                    RemoteCallback callback) {
-                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
-
-                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-                Preconditions.checkStringNotEmpty(packageName,
-                        "packageName cannot be null or empty");
-                Objects.requireNonNull(callback, "callback cannot be null");
-
-                boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName);
-                callback.sendResult(qualified ? Bundle.EMPTY : null);
-            }
-
-            @Override
-            public void isApplicationVisibleForRole(String roleName, String packageName,
-                    RemoteCallback callback) {
-                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
-
-                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-                Preconditions.checkStringNotEmpty(packageName,
-                        "packageName cannot be null or empty");
-                Objects.requireNonNull(callback, "callback cannot be null");
-
-                boolean visible = onIsApplicationVisibleForRole(roleName, packageName);
-                callback.sendResult(visible ? Bundle.EMPTY : null);
-            }
-
-            @Override
-            public void isRoleVisible(String roleName, RemoteCallback callback) {
-                enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
-
-                Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-                Objects.requireNonNull(callback, "callback cannot be null");
-
-                boolean visible = onIsRoleVisible(roleName);
-                callback.sendResult(visible ? Bundle.EMPTY : null);
-            }
-        };
-    }
-
-    private void grantDefaultRoles(@NonNull RemoteCallback callback) {
-        boolean successful = onGrantDefaultRoles();
-        callback.sendResult(successful ? Bundle.EMPTY : null);
-    }
-
-    private void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
-            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
-        boolean successful = onAddRoleHolder(roleName, packageName, flags);
-        callback.sendResult(successful ? Bundle.EMPTY : null);
-    }
-
-    private void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
-            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
-        boolean successful = onRemoveRoleHolder(roleName, packageName, flags);
-        callback.sendResult(successful ? Bundle.EMPTY : null);
-    }
-
-    private void onClearRoleHolders(@NonNull String roleName,
-            @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
-        boolean successful = onClearRoleHolders(roleName, flags);
-        callback.sendResult(successful ? Bundle.EMPTY : null);
-    }
-
-    /**
-     * Called by system to grant default permissions and roles.
-     * <p>
-     * This is typically when creating a new user or upgrading either system or
-     * permission controller package
-     *
-     * @return whether this call was successful
-     */
-    @WorkerThread
-    public abstract boolean onGrantDefaultRoles();
-
-    /**
-     * Add a specific application to the holders of a role. If the role is exclusive, the previous
-     * holder will be replaced.
-     * <p>
-     * Implementation should enforce the role requirements and grant or revoke the relevant
-     * privileges of roles.
-     *
-     * @param roleName the name of the role to add the role holder for
-     * @param packageName the package name of the application to add to the role holders
-     * @param flags optional behavior flags
-     *
-     * @return whether this call was successful
-     *
-     * @see RoleManager#addRoleHolderAsUser(String, String, int, UserHandle, Executor,
-     *      RemoteCallback)
-     */
-    @WorkerThread
-    public abstract boolean onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
-            @RoleManager.ManageHoldersFlags int flags);
-
-    /**
-     * Remove a specific application from the holders of a role.
-     *
-     * @param roleName the name of the role to remove the role holder for
-     * @param packageName the package name of the application to remove from the role holders
-     * @param flags optional behavior flags
-     *
-     * @return whether this call was successful
-     *
-     * @see RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, Executor,
-     *      RemoteCallback)
-     */
-    @WorkerThread
-    public abstract boolean onRemoveRoleHolder(@NonNull String roleName,
-            @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags);
-
-    /**
-     * Remove all holders of a role.
-     *
-     * @param roleName the name of the role to remove role holders for
-     * @param flags optional behavior flags
-     *
-     * @return whether this call was successful
-     *
-     * @see RoleManager#clearRoleHoldersAsUser(String, int, UserHandle, Executor, RemoteCallback)
-     */
-    @WorkerThread
-    public abstract boolean onClearRoleHolders(@NonNull String roleName,
-            @RoleManager.ManageHoldersFlags int flags);
-
-    /**
-     * Check whether an application is qualified for a role.
-     *
-     * @param roleName name of the role to check for
-     * @param packageName package name of the application to check for
-     *
-     * @return whether the application is qualified for the role
-     *
-     * @deprecated Implement {@link #onIsApplicationVisibleForRole(String, String)} instead.
-     */
-    @Deprecated
-    public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
-            @NonNull String packageName);
-
-    /**
-     * Check whether an application is visible for a role.
-     *
-     * While an application can be qualified for a role, it can still stay hidden from user (thus
-     * not visible). If an application is visible for a role, we may show things related to the role
-     * for it, e.g. showing an entry pointing to the role settings in its application info page.
-     *
-     * @param roleName name of the role to check for
-     * @param packageName package name of the application to check for
-     *
-     * @return whether the application is visible for the role
-     */
-    public boolean onIsApplicationVisibleForRole(@NonNull String roleName,
-            @NonNull String packageName) {
-        return onIsApplicationQualifiedForRole(roleName, packageName);
-    }
-
-    /**
-     * Check whether a role should be visible to user.
-     *
-     * @param roleName name of the role to check for
-     *
-     * @return whether the role should be visible to user
-     */
-    public abstract boolean onIsRoleVisible(@NonNull String roleName);
-}
diff --git a/apex/permission/framework-s/java/android/app/role/RoleFrameworkInitializer.java b/apex/permission/framework-s/java/android/app/role/RoleFrameworkInitializer.java
deleted file mode 100644
index 7a97770..0000000
--- a/apex/permission/framework-s/java/android/app/role/RoleFrameworkInitializer.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 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 android.app.role;
-
-import android.annotation.SystemApi;
-import android.app.SystemServiceRegistry;
-import android.content.Context;
-
-/**
- * Class holding initialization code for role in the permission module.
- *
- * @hide
- */
-@SystemApi
-public class RoleFrameworkInitializer {
-    private RoleFrameworkInitializer() {}
-
-    /**
-     * Called by {@link SystemServiceRegistry}'s static initializer and registers
-     * {@link RoleManager} to {@link Context}, so that {@link Context#getSystemService} can return
-     * it.
-     *
-     * <p>If this is called from other places, it throws a {@link IllegalStateException).
-     */
-    public static void registerServiceWrappers() {
-        SystemServiceRegistry.registerContextAwareService(Context.ROLE_SERVICE, RoleManager.class,
-                (context, serviceBinder) -> new RoleManager(context,
-                        IRoleManager.Stub.asInterface(serviceBinder)));
-    }
-}
diff --git a/apex/permission/framework-s/java/android/app/role/RoleManager.java b/apex/permission/framework-s/java/android/app/role/RoleManager.java
deleted file mode 100644
index ceccc4c..0000000
--- a/apex/permission/framework-s/java/android/app/role/RoleManager.java
+++ /dev/null
@@ -1,773 +0,0 @@
-/*
- * Copyright (C) 2018 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 android.app.role;
-
-import android.Manifest;
-import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.annotation.SystemService;
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Process;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * This class provides information about and manages roles.
- * <p>
- * A role is a unique name within the system associated with certain privileges. The list of
- * available roles might change with a system app update, so apps should not make assumption about
- * the availability of roles. Instead, they should always query if the role is available using
- * {@link #isRoleAvailable(String)} before trying to do anything with it. Some predefined role names
- * are available as constants in this class, and a list of possibly available roles can be found in
- * the <a href="{@docRoot}reference/androidx/core/role/package-summary.html">AndroidX Role
- * library</a>.
- * <p>
- * There can be multiple applications qualifying for a role, but only a subset of them can become
- * role holders. To qualify for a role, an application must meet certain requirements, including
- * defining certain components in its manifest. These requirements can be found in the AndroidX
- * Libraries. Then the application will need user consent to become a role holder, which can be
- * requested using {@link android.app.Activity#startActivityForResult(Intent, int)} with the
- * {@code Intent} obtained from {@link #createRequestRoleIntent(String)}.
- * <p>
- * Upon becoming a role holder, the application may be granted certain privileges that are role
- * specific. When the application loses its role, these privileges will also be revoked.
- */
-@SystemService(Context.ROLE_SERVICE)
-public final class RoleManager {
-
-    private static final String LOG_TAG = RoleManager.class.getSimpleName();
-
-    /**
-     * The name of the assistant app role.
-     *
-     * @see android.service.voice.VoiceInteractionService
-     */
-    public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
-
-    /**
-     * The name of the browser role.
-     *
-     * @see Intent#CATEGORY_APP_BROWSER
-     */
-    public static final String ROLE_BROWSER = "android.app.role.BROWSER";
-
-    /**
-     * The name of the dialer role.
-     *
-     * @see Intent#ACTION_DIAL
-     * @see android.telecom.InCallService
-     */
-    public static final String ROLE_DIALER = "android.app.role.DIALER";
-
-    /**
-     * The name of the SMS role.
-     *
-     * @see Intent#CATEGORY_APP_MESSAGING
-     */
-    public static final String ROLE_SMS = "android.app.role.SMS";
-
-    /**
-     * The name of the emergency role
-     */
-    public static final String ROLE_EMERGENCY = "android.app.role.EMERGENCY";
-
-    /**
-     * The name of the home role.
-     *
-     * @see Intent#CATEGORY_HOME
-     */
-    public static final String ROLE_HOME = "android.app.role.HOME";
-
-    /**
-     * The name of the call redirection role.
-     * <p>
-     * A call redirection app provides a means to re-write the phone number for an outgoing call to
-     * place the call through a call redirection service.
-     *
-     * @see android.telecom.CallRedirectionService
-     */
-    public static final String ROLE_CALL_REDIRECTION = "android.app.role.CALL_REDIRECTION";
-
-    /**
-     * The name of the call screening and caller id role.
-     *
-     * @see android.telecom.CallScreeningService
-     */
-    public static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING";
-
-    /**
-     * @hide
-     */
-    @IntDef(flag = true, value = { MANAGE_HOLDERS_FLAG_DONT_KILL_APP })
-    public @interface ManageHoldersFlags {}
-
-    /**
-     * Flag parameter for {@link #addRoleHolderAsUser}, {@link #removeRoleHolderAsUser} and
-     * {@link #clearRoleHoldersAsUser} to indicate that apps should not be killed when changing
-     * their role holder status.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1;
-
-    /**
-     * The action used to request user approval of a role for an application.
-     *
-     * @hide
-     */
-    public static final String ACTION_REQUEST_ROLE = "android.app.role.action.REQUEST_ROLE";
-
-    /**
-     * The permission required to manage records of role holders in {@link RoleManager} directly.
-     *
-     * @hide
-     */
-    public static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
-            "com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
-
-    @NonNull
-    private final Context mContext;
-
-    @NonNull
-    private final IRoleManager mService;
-
-    @GuardedBy("mListenersLock")
-    @NonNull
-    private final SparseArray<ArrayMap<OnRoleHoldersChangedListener,
-            OnRoleHoldersChangedListenerDelegate>> mListeners = new SparseArray<>();
-    @NonNull
-    private final Object mListenersLock = new Object();
-
-    @GuardedBy("mRoleControllerManagerLock")
-    @Nullable
-    private RoleControllerManager mRoleControllerManager;
-    private final Object mRoleControllerManagerLock = new Object();
-
-    /**
-     * Create a new instance of this class.
-     *
-     * @param context the {@link Context}
-     * @param service the {@link IRoleManager} service
-     *
-     * @hide
-     */
-    public RoleManager(@NonNull Context context, @NonNull IRoleManager service) {
-        mContext = context;
-        mService = service;
-    }
-
-    /**
-     * Returns an {@code Intent} suitable for passing to
-     * {@link android.app.Activity#startActivityForResult(Intent, int)} which prompts the user to
-     * grant a role to this application.
-     * <p>
-     * If the role is granted, the {@code resultCode} will be
-     * {@link android.app.Activity#RESULT_OK}, otherwise it will be
-     * {@link android.app.Activity#RESULT_CANCELED}.
-     *
-     * @param roleName the name of requested role
-     *
-     * @return the {@code Intent} to prompt user to grant the role
-     */
-    @NonNull
-    public Intent createRequestRoleIntent(@NonNull String roleName) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        Intent intent = new Intent(ACTION_REQUEST_ROLE);
-        intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName());
-        intent.putExtra(Intent.EXTRA_ROLE_NAME, roleName);
-        return intent;
-    }
-
-    /**
-     * Check whether a role is available in the system.
-     *
-     * @param roleName the name of role to checking for
-     *
-     * @return whether the role is available in the system
-     */
-    public boolean isRoleAvailable(@NonNull String roleName) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        try {
-            return mService.isRoleAvailable(roleName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Check whether the calling application is holding a particular role.
-     *
-     * @param roleName the name of the role to check for
-     *
-     * @return whether the calling application is holding the role
-     */
-    public boolean isRoleHeld(@NonNull String roleName) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        try {
-            return mService.isRoleHeld(roleName, mContext.getPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Get package names of the applications holding the role.
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@code android.permission.MANAGE_ROLE_HOLDERS}.
-     *
-     * @param roleName the name of the role to get the role holder for
-     *
-     * @return a list of package names of the role holders, or an empty list if none.
-     *
-     * @see #getRoleHoldersAsUser(String, UserHandle)
-     *
-     * @hide
-     */
-    @NonNull
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @SystemApi
-    public List<String> getRoleHolders(@NonNull String roleName) {
-        return getRoleHoldersAsUser(roleName, Process.myUserHandle());
-    }
-
-    /**
-     * Get package names of the applications holding the role.
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
-     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
-     *
-     * @param roleName the name of the role to get the role holder for
-     * @param user the user to get the role holder for
-     *
-     * @return a list of package names of the role holders, or an empty list if none.
-     *
-     * @see #addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
-     * @see #removeRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
-     * @see #clearRoleHoldersAsUser(String, int, UserHandle, Executor, Consumer)
-     *
-     * @hide
-     */
-    @NonNull
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @SystemApi
-    public List<String> getRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        Objects.requireNonNull(user, "user cannot be null");
-        try {
-            return mService.getRoleHoldersAsUser(roleName, user.getIdentifier());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Add a specific application to the holders of a role. If the role is exclusive, the previous
-     * holder will be replaced.
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
-     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
-     *
-     * @param roleName the name of the role to add the role holder for
-     * @param packageName the package name of the application to add to the role holders
-     * @param flags optional behavior flags
-     * @param user the user to add the role holder for
-     * @param executor the {@code Executor} to run the callback on.
-     * @param callback the callback for whether this call is successful
-     *
-     * @see #getRoleHoldersAsUser(String, UserHandle)
-     * @see #removeRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
-     * @see #clearRoleHoldersAsUser(String, int, UserHandle, Executor, Consumer)
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @SystemApi
-    public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
-            @ManageHoldersFlags int flags, @NonNull UserHandle user,
-            @CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-        Objects.requireNonNull(user, "user cannot be null");
-        Objects.requireNonNull(executor, "executor cannot be null");
-        Objects.requireNonNull(callback, "callback cannot be null");
-        try {
-            mService.addRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),
-                    createRemoteCallback(executor, callback));
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Remove a specific application from the holders of a role.
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
-     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
-     *
-     * @param roleName the name of the role to remove the role holder for
-     * @param packageName the package name of the application to remove from the role holders
-     * @param flags optional behavior flags
-     * @param user the user to remove the role holder for
-     * @param executor the {@code Executor} to run the callback on.
-     * @param callback the callback for whether this call is successful
-     *
-     * @see #getRoleHoldersAsUser(String, UserHandle)
-     * @see #addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
-     * @see #clearRoleHoldersAsUser(String, int, UserHandle, Executor, Consumer)
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @SystemApi
-    public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
-            @ManageHoldersFlags int flags, @NonNull UserHandle user,
-            @CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-        Objects.requireNonNull(user, "user cannot be null");
-        Objects.requireNonNull(executor, "executor cannot be null");
-        Objects.requireNonNull(callback, "callback cannot be null");
-        try {
-            mService.removeRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),
-                    createRemoteCallback(executor, callback));
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Remove all holders of a role.
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
-     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
-     *
-     * @param roleName the name of the role to remove role holders for
-     * @param flags optional behavior flags
-     * @param user the user to remove role holders for
-     * @param executor the {@code Executor} to run the callback on.
-     * @param callback the callback for whether this call is successful
-     *
-     * @see #getRoleHoldersAsUser(String, UserHandle)
-     * @see #addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
-     * @see #removeRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @SystemApi
-    public void clearRoleHoldersAsUser(@NonNull String roleName, @ManageHoldersFlags int flags,
-            @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor,
-            @NonNull Consumer<Boolean> callback) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        Objects.requireNonNull(user, "user cannot be null");
-        Objects.requireNonNull(executor, "executor cannot be null");
-        Objects.requireNonNull(callback, "callback cannot be null");
-        try {
-            mService.clearRoleHoldersAsUser(roleName, flags, user.getIdentifier(),
-                    createRemoteCallback(executor, callback));
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @NonNull
-    private static RemoteCallback createRemoteCallback(@NonNull Executor executor,
-            @NonNull Consumer<Boolean> callback) {
-        return new RemoteCallback(result -> executor.execute(() -> {
-            boolean successful = result != null;
-            final long token = Binder.clearCallingIdentity();
-            try {
-                callback.accept(successful);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }));
-    }
-
-    /**
-     * Add a listener to observe role holder changes
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user
-     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
-     *
-     * @param executor the {@code Executor} to call the listener on.
-     * @param listener the listener to be added
-     * @param user the user to add the listener for
-     *
-     * @see #removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener, UserHandle)
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS)
-    @SystemApi
-    public void addOnRoleHoldersChangedListenerAsUser(@CallbackExecutor @NonNull Executor executor,
-            @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
-        Objects.requireNonNull(executor, "executor cannot be null");
-        Objects.requireNonNull(listener, "listener cannot be null");
-        Objects.requireNonNull(user, "user cannot be null");
-        int userId = user.getIdentifier();
-        synchronized (mListenersLock) {
-            ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
-                    mListeners.get(userId);
-            if (listeners == null) {
-                listeners = new ArrayMap<>();
-                mListeners.put(userId, listeners);
-            } else {
-                if (listeners.containsKey(listener)) {
-                    return;
-                }
-            }
-            OnRoleHoldersChangedListenerDelegate listenerDelegate =
-                    new OnRoleHoldersChangedListenerDelegate(executor, listener);
-            try {
-                mService.addOnRoleHoldersChangedListenerAsUser(listenerDelegate, userId);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-            listeners.put(listener, listenerDelegate);
-        }
-    }
-
-    /**
-     * Remove a listener observing role holder changes
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user
-     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
-     *
-     * @param listener the listener to be removed
-     * @param user the user to remove the listener for
-     *
-     * @see #addOnRoleHoldersChangedListenerAsUser(Executor, OnRoleHoldersChangedListener,
-     *                                             UserHandle)
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS)
-    @SystemApi
-    public void removeOnRoleHoldersChangedListenerAsUser(
-            @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
-        Objects.requireNonNull(listener, "listener cannot be null");
-        Objects.requireNonNull(user, "user cannot be null");
-        int userId = user.getIdentifier();
-        synchronized (mListenersLock) {
-            ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
-                    mListeners.get(userId);
-            if (listeners == null) {
-                return;
-            }
-            OnRoleHoldersChangedListenerDelegate listenerDelegate = listeners.get(listener);
-            if (listenerDelegate == null) {
-                return;
-            }
-            try {
-                mService.removeOnRoleHoldersChangedListenerAsUser(listenerDelegate,
-                        user.getIdentifier());
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-            listeners.remove(listener);
-            if (listeners.isEmpty()) {
-                mListeners.remove(userId);
-            }
-        }
-    }
-
-    /**
-     * Set the names of all the available roles. Should only be called from
-     * {@link android.app.role.RoleControllerService}.
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@link #PERMISSION_MANAGE_ROLES_FROM_CONTROLLER}.
-     *
-     * @param roleNames the names of all the available roles
-     *
-     * @deprecated This is only usable by the role controller service, which is an internal
-     *             implementation detail inside role.
-     *
-     * @hide
-     */
-    @Deprecated
-    @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
-    @SystemApi
-    public void setRoleNamesFromController(@NonNull List<String> roleNames) {
-        Objects.requireNonNull(roleNames, "roleNames cannot be null");
-        try {
-            mService.setRoleNamesFromController(roleNames);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Add a specific application to the holders of a role, only modifying records inside
-     * {@link RoleManager}. Should only be called from
-     * {@link android.app.role.RoleControllerService}.
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@link #PERMISSION_MANAGE_ROLES_FROM_CONTROLLER}.
-     *
-     * @param roleName the name of the role to add the role holder for
-     * @param packageName the package name of the application to add to the role holders
-     *
-     * @return whether the operation was successful, and will also be {@code true} if a matching
-     *         role holder is already found.
-     *
-     * @see #getRoleHolders(String)
-     * @see #removeRoleHolderFromController(String, String)
-     *
-     * @deprecated This is only usable by the role controller service, which is an internal
-     *             implementation detail inside role.
-     *
-     * @hide
-     */
-    @Deprecated
-    @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
-    @SystemApi
-    public boolean addRoleHolderFromController(@NonNull String roleName,
-            @NonNull String packageName) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-        try {
-            return mService.addRoleHolderFromController(roleName, packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Remove a specific application from the holders of a role, only modifying records inside
-     * {@link RoleManager}. Should only be called from
-     * {@link android.app.role.RoleControllerService}.
-     * <p>
-     * <strong>Note:</strong> Using this API requires holding
-     * {@link #PERMISSION_MANAGE_ROLES_FROM_CONTROLLER}.
-     *
-     * @param roleName the name of the role to remove the role holder for
-     * @param packageName the package name of the application to remove from the role holders
-     *
-     * @return whether the operation was successful, and will also be {@code true} if no matching
-     *         role holder was found to remove.
-     *
-     * @see #getRoleHolders(String)
-     * @see #addRoleHolderFromController(String, String)
-     *
-     * @deprecated This is only usable by the role controller service, which is an internal
-     *             implementation detail inside role.
-     *
-     * @hide
-     */
-    @Deprecated
-    @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
-    @SystemApi
-    public boolean removeRoleHolderFromController(@NonNull String roleName,
-            @NonNull String packageName) {
-        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-        Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-        try {
-            return mService.removeRoleHolderFromController(roleName, packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns the list of all roles that the given package is currently holding
-     *
-     * @param packageName the package name
-     * @return the list of role names
-     *
-     * @deprecated This is only usable by the role controller service, which is an internal
-     *             implementation detail inside role.
-     *
-     * @hide
-     */
-    @Deprecated
-    @NonNull
-    @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
-    @SystemApi
-    public List<String> getHeldRolesFromController(@NonNull String packageName) {
-        Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-        try {
-            return mService.getHeldRolesFromController(packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Get the role holder of {@link #ROLE_BROWSER} without requiring
-     * {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}, as in
-     * {@link android.content.pm.PackageManager#getDefaultBrowserPackageNameAsUser(int)}
-     *
-     * @param userId the user ID
-     * @return the package name of the default browser, or {@code null} if none
-     *
-     * @hide
-     */
-    @Nullable
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public String getBrowserRoleHolder(@UserIdInt int userId) {
-        try {
-            return mService.getBrowserRoleHolder(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Set the role holder of {@link #ROLE_BROWSER} requiring
-     * {@link Manifest.permission.SET_PREFERRED_APPLICATIONS} instead of
-     * {@link Manifest.permission#MANAGE_ROLE_HOLDERS}, as in
-     * {@link android.content.pm.PackageManager#setDefaultBrowserPackageNameAsUser(String, int)}
-     *
-     * @param packageName the package name of the default browser, or {@code null} if none
-     * @param userId the user ID
-     * @return whether the default browser was set successfully
-     *
-     * @hide
-     */
-    @Nullable
-    @RequiresPermission(Manifest.permission.SET_PREFERRED_APPLICATIONS)
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public boolean setBrowserRoleHolder(@Nullable String packageName, @UserIdInt int userId) {
-        try {
-            return mService.setBrowserRoleHolder(packageName, userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Allows getting the role holder for {@link #ROLE_SMS} without requiring
-     * {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}, as in
-     * {@link android.provider.Telephony.Sms#getDefaultSmsPackage(Context)}.
-     *
-     * @param userId the user ID to get the default SMS package for
-     * @return the package name of the default SMS app, or {@code null} if none
-     *
-     * @hide
-     */
-    @Nullable
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public String getSmsRoleHolder(@UserIdInt int userId) {
-        try {
-            return mService.getSmsRoleHolder(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Check whether a role should be visible to user.
-     *
-     * @param roleName name of the role to check for
-     * @param executor the executor to execute callback on
-     * @param callback the callback to receive whether the role should be visible to user
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @SystemApi
-    public void isRoleVisible(@NonNull String roleName,
-            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-        getRoleControllerManager().isRoleVisible(roleName, executor, callback);
-    }
-
-    /**
-     * Check whether an application is visible for a role.
-     *
-     * While an application can be qualified for a role, it can still stay hidden from user (thus
-     * not visible). If an application is visible for a role, we may show things related to the role
-     * for it, e.g. showing an entry pointing to the role settings in its application info page.
-     *
-     * @param roleName the name of the role to check for
-     * @param packageName the package name of the application to check for
-     * @param executor the executor to execute callback on
-     * @param callback the callback to receive whether the application is visible for the role
-     *
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
-    @SystemApi
-    public void isApplicationVisibleForRole(@NonNull String roleName, @NonNull String packageName,
-            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
-        getRoleControllerManager().isApplicationVisibleForRole(roleName, packageName, executor,
-                callback);
-    }
-
-    @NonNull
-    private RoleControllerManager getRoleControllerManager() {
-        synchronized (mRoleControllerManagerLock) {
-            if (mRoleControllerManager == null) {
-                mRoleControllerManager = new RoleControllerManager(mContext);
-            }
-            return mRoleControllerManager;
-        }
-    }
-
-    private static class OnRoleHoldersChangedListenerDelegate
-            extends IOnRoleHoldersChangedListener.Stub {
-
-        @NonNull
-        private final Executor mExecutor;
-        @NonNull
-        private final OnRoleHoldersChangedListener mListener;
-
-        OnRoleHoldersChangedListenerDelegate(@NonNull Executor executor,
-                @NonNull OnRoleHoldersChangedListener listener) {
-            mExecutor = executor;
-            mListener = listener;
-        }
-
-        @Override
-        public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                mExecutor.execute(() ->
-                        mListener.onRoleHoldersChanged(roleName, UserHandle.of(userId)));
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-}
diff --git a/apex/permission/framework-s/java/android/app/role/TEST_MAPPING b/apex/permission/framework-s/java/android/app/role/TEST_MAPPING
deleted file mode 100644
index f8f140d..0000000
--- a/apex/permission/framework-s/java/android/app/role/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-    "presubmit": [
-        {
-            "name": "CtsRoleTestCases",
-            "options": [
-                {
-                    "include-filter": "android.app.role.cts.RoleManagerTest"
-                }
-            ]
-        }
-    ]
-}
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
deleted file mode 100644
index 2150cb4..0000000
--- a/apex/permission/framework/Android.bp
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2020 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.
-
-filegroup {
-    name: "framework-permission-sources",
-    srcs: [
-        "java/**/*.java",
-        "java/**/*.aidl",
-    ],
-    path: "java",
-    visibility: ["//frameworks/base"],
-}
-
-java_sdk_library {
-    name: "framework-permission",
-    defaults: ["framework-module-defaults"],
-
-    // Restrict access to implementation library.
-    impl_library_visibility: ["//frameworks/base/apex/permission:__subpackages__"],
-
-    srcs: [
-        ":framework-permission-sources",
-    ],
-
-    apex_available: [
-        "com.android.permission",
-        "test_com.android.permission",
-    ],
-    min_sdk_version: "30",
-    permitted_packages: [
-        "android.permission",
-        "android.app.role",
-    ],
-    hostdex: true,
-    installable: true,
-}
diff --git a/apex/permission/framework/api/current.txt b/apex/permission/framework/api/current.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework/api/current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/framework/api/module-lib-current.txt b/apex/permission/framework/api/module-lib-current.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework/api/module-lib-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/framework/api/module-lib-removed.txt b/apex/permission/framework/api/module-lib-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework/api/module-lib-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/framework/api/removed.txt b/apex/permission/framework/api/removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework/api/removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/framework/api/system-current.txt b/apex/permission/framework/api/system-current.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework/api/system-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/framework/api/system-removed.txt b/apex/permission/framework/api/system-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/permission/framework/api/system-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/permission/jarjar-rules.txt b/apex/permission/jarjar-rules.txt
deleted file mode 100644
index 4729ed1..0000000
--- a/apex/permission/jarjar-rules.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-rule android.os.HandlerExecutor com.android.permission.jarjar.@0
-rule android.util.IndentingPrintWriter com.android.permission.jarjar.@0
-rule com.android.internal.** com.android.permission.jarjar.@0
-rule com.android.modules.** com.android.permission.jarjar.@0
-rule com.android.role.*Proto com.android.permission.jarjar.@0
diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp
deleted file mode 100644
index b7f8082..0000000
--- a/apex/permission/service/Android.bp
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (C) 2020 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.
-
-filegroup {
-    name: "service-permission-sources",
-    srcs: [
-        "java/**/*.java",
-    ],
-    path: "java",
-    visibility: ["//frameworks/base/services"],
-}
-
-filegroup {
-    name: "service-permission-protos",
-    srcs: [
-        "proto/**/*.proto",
-    ],
-    visibility: ["//frameworks/base"],
-}
-
-gensrcs {
-    name: "service-permission-javastream-protos",
-    depfile: true,
-
-    tools: [
-        "aprotoc",
-        "protoc-gen-javastream",
-        "soong_zip",
-    ],
-
-    cmd: "mkdir -p $(genDir)/$(in) " +
-        "&& $(location aprotoc) " +
-        "  --plugin=$(location protoc-gen-javastream) " +
-        "  --dependency_out=$(depfile) " +
-        "  --javastream_out=$(genDir)/$(in) " +
-        "  -Iexternal/protobuf/src " +
-        "  -I . " +
-        "  $(in) " +
-        "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
-
-    srcs: [
-        ":service-permission-protos",
-    ],
-    output_extension: "srcjar",
-}
-
-java_library {
-    name: "service-permission-shared",
-    srcs: [":service-permission-shared-srcs"],
-    libs: [
-        "framework-annotations-lib",
-        "framework-permission-s-shared",
-    ],
-    apex_available: [
-        "com.android.permission",
-        "test_com.android.permission",
-    ],
-    installable: false,
-    min_sdk_version: "30",
-    sdk_version: "system_server_current",
-}
-
-java_sdk_library {
-    name: "service-permission",
-    defaults: ["framework-system-server-module-defaults"],
-    impl_library_visibility: [
-        "//frameworks/base/apex/permission/tests",
-        "//frameworks/base/services/tests/mockingservicestests",
-        "//frameworks/base/services/tests/servicestests",
-    ],
-    srcs: [
-        ":service-permission-sources",
-        ":service-permission-javastream-protos",
-    ],
-    libs: [
-        "framework-permission",
-        "framework-permission-s.impl",
-        "framework-permission-s-shared",
-    ],
-    static_libs: [
-        "modules-utils-os",
-        "service-permission-shared",
-    ],
-    jarjar_rules: ":permission-jarjar-rules",
-    min_sdk_version: "30",
-    sdk_version: "system_server_current",
-    apex_available: [
-        "com.android.permission",
-        "test_com.android.permission",
-    ],
-    installable: true,
-    // We don't have last-api tracking files for the public part of this jar's API.
-    unsafe_ignore_missing_latest_api: true,
-}
diff --git a/apex/permission/service/api/system-server-current.txt b/apex/permission/service/api/system-server-current.txt
deleted file mode 100644
index b1869c2c..0000000
--- a/apex/permission/service/api/system-server-current.txt
+++ /dev/null
@@ -1,54 +0,0 @@
-// Signature format: 2.0
-package com.android.permission.persistence {
-
-  public interface RuntimePermissionsPersistence {
-    method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance();
-    method public void deleteForUser(@NonNull android.os.UserHandle);
-    method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle);
-    method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle);
-  }
-
-  public final class RuntimePermissionsState {
-    ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>);
-    method @Nullable public String getFingerprint();
-    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions();
-    method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions();
-    method public int getVersion();
-    field public static final int NO_VERSION = -1; // 0xffffffff
-  }
-
-  public static final class RuntimePermissionsState.PermissionState {
-    ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int);
-    method public int getFlags();
-    method @NonNull public String getName();
-    method public boolean isGranted();
-  }
-
-}
-
-package com.android.role {
-
-  public interface RoleManagerLocal {
-    method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRolesAndHolders(int);
-  }
-
-}
-
-package com.android.role.persistence {
-
-  public interface RolesPersistence {
-    method @NonNull public static com.android.role.persistence.RolesPersistence createInstance();
-    method public void deleteForUser(@NonNull android.os.UserHandle);
-    method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle);
-    method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle);
-  }
-
-  public final class RolesState {
-    ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>);
-    method @Nullable public String getPackagesHash();
-    method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles();
-    method public int getVersion();
-  }
-
-}
-
diff --git a/apex/permission/service/java/com/android/permission/compat/UserHandleCompat.java b/apex/permission/service/java/com/android/permission/compat/UserHandleCompat.java
deleted file mode 100644
index 7c711d3..0000000
--- a/apex/permission/service/java/com/android/permission/compat/UserHandleCompat.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.compat;
-
-import android.annotation.UserIdInt;
-import android.os.UserHandle;
-
-/**
- * Helper for accessing features in {@link UserHandle}.
- */
-public final class UserHandleCompat {
-    /**
-     * A user ID to indicate all users on the device.
-     */
-    public static final int USER_ALL = UserHandle.ALL.getIdentifier();
-
-    /**
-     * A user ID to indicate the "system" user of the device.
-     */
-    public static final int USER_SYSTEM = UserHandle.SYSTEM.getIdentifier();
-
-    private UserHandleCompat() {}
-
-    /**
-     * Get the user ID of a given UID.
-     *
-     * @param uid the UID
-     * @return the user ID
-     */
-    @UserIdInt
-    public static int getUserId(int uid) {
-        return UserHandle.getUserHandleForUid(uid).getIdentifier();
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/compat/package-info.java b/apex/permission/service/java/com/android/permission/compat/package-info.java
deleted file mode 100644
index c89cc8e..0000000
--- a/apex/permission/service/java/com/android/permission/compat/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-/**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
- */
-@android.annotation.Hide
-package com.android.permission.compat;
diff --git a/apex/permission/service/java/com/android/permission/persistence/IoUtils.java b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java
deleted file mode 100644
index 569a78c..0000000
--- a/apex/permission/service/java/com/android/permission/persistence/IoUtils.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2020 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.permission.persistence;
-
-import android.annotation.NonNull;
-
-/**
- * Utility class for IO.
- *
- * @hide
- */
-public class IoUtils {
-
-    private IoUtils() {}
-
-    /**
-     * Close 'closeable' ignoring any exceptions.
-     */
-    public static void closeQuietly(@NonNull AutoCloseable closeable) {
-        try {
-            closeable.close();
-        } catch (Exception ignored) {
-            // Ignored.
-        }
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java
deleted file mode 100644
index aedba29..0000000
--- a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 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.permission.persistence;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.SystemApi.Client;
-import android.os.UserHandle;
-
-/**
- * Persistence for runtime permissions.
- *
- * TODO(b/147914847): Remove @hide when it becomes the default.
- * @hide
- */
-@SystemApi(client = Client.SYSTEM_SERVER)
-public interface RuntimePermissionsPersistence {
-
-    /**
-     * Read the runtime permissions from persistence.
-     *
-     * This will perform I/O operations synchronously.
-     *
-     * @param user the user to read for
-     * @return the runtime permissions read
-     */
-    @Nullable
-    RuntimePermissionsState readForUser(@NonNull UserHandle user);
-
-    /**
-     * Write the runtime permissions to persistence.
-     *
-     * This will perform I/O operations synchronously.
-     *
-     * @param runtimePermissions the runtime permissions to write
-     * @param user the user to write for
-     */
-    void writeForUser(@NonNull RuntimePermissionsState runtimePermissions,
-            @NonNull UserHandle user);
-
-    /**
-     * Delete the runtime permissions from persistence.
-     *
-     * This will perform I/O operations synchronously.
-     *
-     * @param user the user to delete for
-     */
-    void deleteForUser(@NonNull UserHandle user);
-
-    /**
-     * Create a new instance of {@link RuntimePermissionsPersistence} implementation.
-     *
-     * @return the new instance.
-     */
-    @NonNull
-    static RuntimePermissionsPersistence createInstance() {
-        return new RuntimePermissionsPersistenceImpl();
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
deleted file mode 100644
index e43f59a..0000000
--- a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2020 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.permission.persistence;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ApexEnvironment;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.Xml;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Persistence implementation for runtime permissions.
- *
- * TODO(b/147914847): Remove @hide when it becomes the default.
- * @hide
- */
-public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPersistence {
-
-    private static final String LOG_TAG = RuntimePermissionsPersistenceImpl.class.getSimpleName();
-
-    private static final String APEX_MODULE_NAME = "com.android.permission";
-
-    private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml";
-
-    private static final String TAG_PACKAGE = "package";
-    private static final String TAG_PERMISSION = "permission";
-    private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions";
-    private static final String TAG_SHARED_USER = "shared-user";
-
-    private static final String ATTRIBUTE_FINGERPRINT = "fingerprint";
-    private static final String ATTRIBUTE_FLAGS = "flags";
-    private static final String ATTRIBUTE_GRANTED = "granted";
-    private static final String ATTRIBUTE_NAME = "name";
-    private static final String ATTRIBUTE_VERSION = "version";
-
-    @Nullable
-    @Override
-    public RuntimePermissionsState readForUser(@NonNull UserHandle user) {
-        File file = getFile(user);
-        try (FileInputStream inputStream = new AtomicFile(file).openRead()) {
-            XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(inputStream, null);
-            return parseXml(parser);
-        } catch (FileNotFoundException e) {
-            Log.i(LOG_TAG, "runtime-permissions.xml not found");
-            return null;
-        } catch (XmlPullParserException | IOException e) {
-            throw new IllegalStateException("Failed to read runtime-permissions.xml: " + file , e);
-        }
-    }
-
-    @NonNull
-    private static RuntimePermissionsState parseXml(@NonNull XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        int type;
-        int depth;
-        int innerDepth = parser.getDepth() + 1;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
-            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            if (parser.getName().equals(TAG_RUNTIME_PERMISSIONS)) {
-                return parseRuntimePermissions(parser);
-            }
-        }
-        throw new IllegalStateException("Missing <" + TAG_RUNTIME_PERMISSIONS
-                + "> in runtime-permissions.xml");
-    }
-
-    @NonNull
-    private static RuntimePermissionsState parseRuntimePermissions(@NonNull XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        String versionValue = parser.getAttributeValue(null, ATTRIBUTE_VERSION);
-        int version = versionValue != null ? Integer.parseInt(versionValue)
-                : RuntimePermissionsState.NO_VERSION;
-        String fingerprint = parser.getAttributeValue(null, ATTRIBUTE_FINGERPRINT);
-
-        Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
-                new ArrayMap<>();
-        Map<String, List<RuntimePermissionsState.PermissionState>> sharedUserPermissions =
-                new ArrayMap<>();
-        int type;
-        int depth;
-        int innerDepth = parser.getDepth() + 1;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
-            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            switch (parser.getName()) {
-                case TAG_PACKAGE: {
-                    String packageName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
-                    List<RuntimePermissionsState.PermissionState> permissions = parsePermissions(
-                            parser);
-                    packagePermissions.put(packageName, permissions);
-                    break;
-                }
-                case TAG_SHARED_USER: {
-                    String sharedUserName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
-                    List<RuntimePermissionsState.PermissionState> permissions = parsePermissions(
-                            parser);
-                    sharedUserPermissions.put(sharedUserName, permissions);
-                    break;
-                }
-            }
-        }
-
-        return new RuntimePermissionsState(version, fingerprint, packagePermissions,
-                sharedUserPermissions);
-    }
-
-    @NonNull
-    private static List<RuntimePermissionsState.PermissionState> parsePermissions(
-            @NonNull XmlPullParser parser) throws IOException, XmlPullParserException {
-        List<RuntimePermissionsState.PermissionState> permissions = new ArrayList<>();
-        int type;
-        int depth;
-        int innerDepth = parser.getDepth() + 1;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
-            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            if (parser.getName().equals(TAG_PERMISSION)) {
-                String name = parser.getAttributeValue(null, ATTRIBUTE_NAME);
-                boolean granted = Boolean.parseBoolean(parser.getAttributeValue(null,
-                        ATTRIBUTE_GRANTED));
-                int flags = Integer.parseInt(parser.getAttributeValue(null,
-                        ATTRIBUTE_FLAGS), 16);
-                RuntimePermissionsState.PermissionState permission =
-                        new RuntimePermissionsState.PermissionState(name, granted, flags);
-                permissions.add(permission);
-            }
-        }
-        return permissions;
-    }
-
-    @Override
-    public void writeForUser(@NonNull RuntimePermissionsState runtimePermissions,
-            @NonNull UserHandle user) {
-        File file = getFile(user);
-        AtomicFile atomicFile = new AtomicFile(file);
-        FileOutputStream outputStream = null;
-        try {
-            outputStream = atomicFile.startWrite();
-
-            XmlSerializer serializer = Xml.newSerializer();
-            serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-            serializer.startDocument(null, true);
-
-            serializeRuntimePermissions(serializer, runtimePermissions);
-
-            serializer.endDocument();
-            atomicFile.finishWrite(outputStream);
-        } catch (Exception e) {
-            Log.wtf(LOG_TAG, "Failed to write runtime-permissions.xml, restoring backup: " + file,
-                    e);
-            atomicFile.failWrite(outputStream);
-        } finally {
-            IoUtils.closeQuietly(outputStream);
-        }
-    }
-
-    private static void serializeRuntimePermissions(@NonNull XmlSerializer serializer,
-            @NonNull RuntimePermissionsState runtimePermissions) throws IOException {
-        serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
-
-        int version = runtimePermissions.getVersion();
-        serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
-        String fingerprint = runtimePermissions.getFingerprint();
-        if (fingerprint != null) {
-            serializer.attribute(null, ATTRIBUTE_FINGERPRINT, fingerprint);
-        }
-
-        for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
-                : runtimePermissions.getPackagePermissions().entrySet()) {
-            String packageName = entry.getKey();
-            List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
-
-            serializer.startTag(null, TAG_PACKAGE);
-            serializer.attribute(null, ATTRIBUTE_NAME, packageName);
-            serializePermissions(serializer, permissions);
-            serializer.endTag(null, TAG_PACKAGE);
-        }
-
-        for (Map.Entry<String, List<RuntimePermissionsState.PermissionState>> entry
-                : runtimePermissions.getSharedUserPermissions().entrySet()) {
-            String sharedUserName = entry.getKey();
-            List<RuntimePermissionsState.PermissionState> permissions = entry.getValue();
-
-            serializer.startTag(null, TAG_SHARED_USER);
-            serializer.attribute(null, ATTRIBUTE_NAME, sharedUserName);
-            serializePermissions(serializer, permissions);
-            serializer.endTag(null, TAG_SHARED_USER);
-        }
-
-        serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
-    }
-
-    private static void serializePermissions(@NonNull XmlSerializer serializer,
-            @NonNull List<RuntimePermissionsState.PermissionState> permissions) throws IOException {
-        int permissionsSize = permissions.size();
-        for (int i = 0; i < permissionsSize; i++) {
-            RuntimePermissionsState.PermissionState permissionState = permissions.get(i);
-
-            serializer.startTag(null, TAG_PERMISSION);
-            serializer.attribute(null, ATTRIBUTE_NAME, permissionState.getName());
-            serializer.attribute(null, ATTRIBUTE_GRANTED, Boolean.toString(
-                    permissionState.isGranted() && (permissionState.getFlags()
-                            & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0));
-            serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString(
-                    permissionState.getFlags()));
-            serializer.endTag(null, TAG_PERMISSION);
-        }
-    }
-
-    @Override
-    public void deleteForUser(@NonNull UserHandle user) {
-        getFile(user).delete();
-    }
-
-    @NonNull
-    private static File getFile(@NonNull UserHandle user) {
-        ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME);
-        File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user);
-        return new File(dataDirectory, RUNTIME_PERMISSIONS_FILE_NAME);
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java
deleted file mode 100644
index c6bfc6d..0000000
--- a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2020 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.permission.persistence;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.SystemApi.Client;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-/**
- * State of all runtime permissions.
- *
- * TODO(b/147914847): Remove @hide when it becomes the default.
- * @hide
- */
-@SystemApi(client = Client.SYSTEM_SERVER)
-public final class RuntimePermissionsState {
-
-    /**
-     * Special value for {@link #mVersion} to indicate that no version was read.
-     */
-    public static final int NO_VERSION = -1;
-
-    /**
-     * The version of the runtime permissions.
-     */
-    private final int mVersion;
-
-    /**
-     * The fingerprint of the runtime permissions.
-     */
-    @Nullable
-    private final String mFingerprint;
-
-    /**
-     * The runtime permissions by packages.
-     */
-    @NonNull
-    private final Map<String, List<PermissionState>> mPackagePermissions;
-
-    /**
-     * The runtime permissions by shared users.
-     */
-    @NonNull
-    private final Map<String, List<PermissionState>> mSharedUserPermissions;
-
-    /**
-     * Create a new instance of this class.
-     *
-     * @param version the version of the runtime permissions
-     * @param fingerprint the fingerprint of the runtime permissions
-     * @param packagePermissions the runtime permissions by packages
-     * @param sharedUserPermissions the runtime permissions by shared users
-     */
-    public RuntimePermissionsState(int version, @Nullable String fingerprint,
-            @NonNull Map<String, List<PermissionState>> packagePermissions,
-            @NonNull Map<String, List<PermissionState>> sharedUserPermissions) {
-        mVersion = version;
-        mFingerprint = fingerprint;
-        mPackagePermissions = packagePermissions;
-        mSharedUserPermissions = sharedUserPermissions;
-    }
-
-    /**
-     * Get the version of the runtime permissions.
-     *
-     * @return the version of the runtime permissions
-     */
-    public int getVersion() {
-        return mVersion;
-    }
-
-    /**
-     * Get the fingerprint of the runtime permissions.
-     *
-     * @return the fingerprint of the runtime permissions
-     */
-    @Nullable
-    public String getFingerprint() {
-        return mFingerprint;
-    }
-
-    /**
-     * Get the runtime permissions by packages.
-     *
-     * @return the runtime permissions by packages
-     */
-    @NonNull
-    public Map<String, List<PermissionState>> getPackagePermissions() {
-        return mPackagePermissions;
-    }
-
-    /**
-     * Get the runtime permissions by shared users.
-     *
-     * @return the runtime permissions by shared users
-     */
-    @NonNull
-    public Map<String, List<PermissionState>> getSharedUserPermissions() {
-        return mSharedUserPermissions;
-    }
-
-    @Override
-    public boolean equals(Object object) {
-        if (this == object) {
-            return true;
-        }
-        if (object == null || getClass() != object.getClass()) {
-            return false;
-        }
-        RuntimePermissionsState that = (RuntimePermissionsState) object;
-        return mVersion == that.mVersion
-                && Objects.equals(mFingerprint, that.mFingerprint)
-                && Objects.equals(mPackagePermissions, that.mPackagePermissions)
-                && Objects.equals(mSharedUserPermissions, that.mSharedUserPermissions);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mVersion, mFingerprint, mPackagePermissions, mSharedUserPermissions);
-    }
-
-    /**
-     * State of a single permission.
-     */
-    public static final class PermissionState {
-
-        /**
-         * The name of the permission.
-         */
-        @NonNull
-        private final String mName;
-
-        /**
-         * Whether the permission is granted.
-         */
-        private final boolean mGranted;
-
-        /**
-         * The flags of the permission.
-         */
-        private final int mFlags;
-
-        /**
-         * Create a new instance of this class.
-         *
-         * @param name the name of the permission
-         * @param granted whether the permission is granted
-         * @param flags the flags of the permission
-         */
-        public PermissionState(@NonNull String name, boolean granted, int flags) {
-            mName = name;
-            mGranted = granted;
-            mFlags = flags;
-        }
-
-        /**
-         * Get the name of the permission.
-         *
-         * @return the name of the permission
-         */
-        @NonNull
-        public String getName() {
-            return mName;
-        }
-
-        /**
-         * Get whether the permission is granted.
-         *
-         * @return whether the permission is granted
-         */
-        public boolean isGranted() {
-            return mGranted;
-        }
-
-        /**
-         * Get the flags of the permission.
-         *
-         * @return the flags of the permission
-         */
-        public int getFlags() {
-            return mFlags;
-        }
-
-        @Override
-        public boolean equals(Object object) {
-            if (this == object) {
-                return true;
-            }
-            if (object == null || getClass() != object.getClass()) {
-                return false;
-            }
-            PermissionState that = (PermissionState) object;
-            return mGranted == that.mGranted
-                    && mFlags == that.mFlags
-                    && Objects.equals(mName, that.mName);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mName, mGranted, mFlags);
-        }
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/util/ArrayUtils.java b/apex/permission/service/java/com/android/permission/util/ArrayUtils.java
deleted file mode 100644
index 5d5cd78..0000000
--- a/apex/permission/service/java/com/android/permission/util/ArrayUtils.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.util;
-
-import android.annotation.Nullable;
-
-import java.util.Objects;
-
-/**
- * Array utilities.
- */
-public final class ArrayUtils {
-    private ArrayUtils() {}
-
-    /**
-     * @see java.util.List#contains(Object)
-     */
-    public static <T> boolean contains(@Nullable T[] array, T value) {
-        return indexOf(array, value) != -1;
-    }
-
-    /**
-     * Get the first element of an array, or {@code null} if none.
-     *
-     * @param array the array
-     * @param <T> the type of the elements of the array
-     * @return first element of an array, or {@code null} if none
-     */
-    public static <T> T firstOrNull(@Nullable T[] array) {
-        return !isEmpty(array) ? array[0] : null;
-    }
-
-    /**
-     * @see java.util.List#indexOf(Object)
-     */
-    public static <T> int indexOf(@Nullable T[] array, T value) {
-        if (array == null) {
-            return -1;
-        }
-        final int length = array.length;
-        for (int i = 0; i < length; i++) {
-            final T element = array[i];
-            if (Objects.equals(element, value)) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * @see java.util.List#isEmpty()
-     */
-    public static <T> boolean isEmpty(@Nullable T[] array) {
-        return array == null || array.length == 0;
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/util/BackgroundThread.java b/apex/permission/service/java/com/android/permission/util/BackgroundThread.java
deleted file mode 100644
index 7308eec..0000000
--- a/apex/permission/service/java/com/android/permission/util/BackgroundThread.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.util;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.HandlerThread;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.Executor;
-
-/**
- * Shared singleton background thread.
- */
-public class BackgroundThread extends HandlerThread {
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    private static BackgroundThread sInstance;
-    @GuardedBy("sLock")
-    private static Handler sHandler;
-    @GuardedBy("sLock")
-    private static Executor sExecutor;
-
-    private BackgroundThread() {
-        super(BackgroundThread.class.getName());
-    }
-
-    @GuardedBy("sLock")
-    private static void ensureInstanceLocked() {
-        if (sInstance == null) {
-            sInstance = new BackgroundThread();
-            sInstance.start();
-            sHandler = new Handler(sInstance.getLooper());
-            sExecutor = new HandlerExecutor(sHandler);
-        }
-    }
-
-    /**
-     * Get the singleton instance of thi class.
-     *
-     * @return the singleton instance of thi class
-     */
-    @NonNull
-    public static BackgroundThread get() {
-        synchronized (sLock) {
-            ensureInstanceLocked();
-            return sInstance;
-        }
-    }
-
-    /**
-     * Get the {@link Handler} for this thread.
-     *
-     * @return the {@link Handler} for this thread.
-     */
-    @NonNull
-    public static Handler getHandler() {
-        synchronized (sLock) {
-            ensureInstanceLocked();
-            return sHandler;
-        }
-    }
-
-    /**
-     * Get the {@link Executor} for this thread.
-     *
-     * @return the {@link Executor} for this thread.
-     */
-    @NonNull
-    public static Executor getExecutor() {
-        synchronized (sLock) {
-            ensureInstanceLocked();
-            return sExecutor;
-        }
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/util/CollectionUtils.java b/apex/permission/service/java/com/android/permission/util/CollectionUtils.java
deleted file mode 100644
index ea49524..0000000
--- a/apex/permission/service/java/com/android/permission/util/CollectionUtils.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.util;
-
-import android.annotation.Nullable;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * {@link Collection} utilities.
- */
-public class CollectionUtils {
-    private CollectionUtils() {}
-
-    /**
-     * Get the first element of a {@link List}, or {@code null} if none.
-     *
-     * @param list the {@link List}, or {@code null}
-     * @param <E> the element type of the {@link List}
-     * @return the first element of the {@link List}, or {@code 0} if none
-     */
-    @Nullable
-    public static <E> E firstOrNull(@Nullable List<E> list) {
-        return !isEmpty(list) ? list.get(0) : null;
-    }
-
-    /**
-     * Check whether a {@link Collection} is empty or {@code null}.
-     *
-     * @param collection the {@link Collection}, or {@code null}
-     * @return whether the {@link Collection} is empty or {@code null}
-     */
-    public static boolean isEmpty(@Nullable Collection<?> collection) {
-        return collection == null || collection.isEmpty();
-    }
-
-    /**
-     * Get the size of a {@link Collection}, or {@code 0} if {@code null}.
-     *
-     * @param collection the {@link Collection}, or {@code null}
-     * @return the size of the {@link Collection}, or {@code 0} if {@code null}
-     */
-    public static int size(@Nullable Collection<?> collection) {
-        return collection != null ? collection.size() : 0;
-    }
-
-    /**
-     * Get the size of a {@link Map}, or {@code 0} if {@code null}.
-     *
-     * @param collection the {@link Map}, or {@code null}
-     * @return the size of the {@link Map}, or {@code 0} if {@code null}
-     */
-    public static int size(@Nullable Map<?, ?> collection) {
-        return collection != null ? collection.size() : 0;
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/util/ForegroundThread.java b/apex/permission/service/java/com/android/permission/util/ForegroundThread.java
deleted file mode 100644
index cd6f605..0000000
--- a/apex/permission/service/java/com/android/permission/util/ForegroundThread.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2021 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.permission.util;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.HandlerThread;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.Executor;
-
-/**
- * Shared singleton foreground thread.
- */
-public class ForegroundThread extends HandlerThread {
-    private static final Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    private static ForegroundThread sInstance;
-    @GuardedBy("sLock")
-    private static Handler sHandler;
-    @GuardedBy("sLock")
-    private static Executor sExecutor;
-
-    private ForegroundThread() {
-        super(ForegroundThread.class.getName());
-    }
-
-    @GuardedBy("sLock")
-    private static void ensureInstanceLocked() {
-        if (sInstance == null) {
-            sInstance = new ForegroundThread();
-            sInstance.start();
-            sHandler = new Handler(sInstance.getLooper());
-            sExecutor = new HandlerExecutor(sHandler);
-        }
-    }
-
-    /**
-     * Get the singleton instance of thi class.
-     *
-     * @return the singleton instance of thi class
-     */
-    @NonNull
-    public static ForegroundThread get() {
-        synchronized (sLock) {
-            ensureInstanceLocked();
-            return sInstance;
-        }
-    }
-
-    /**
-     * Get the {@link Handler} for this thread.
-     *
-     * @return the {@link Handler} for this thread.
-     */
-    @NonNull
-    public static Handler getHandler() {
-        synchronized (sLock) {
-            ensureInstanceLocked();
-            return sHandler;
-        }
-    }
-
-    /**
-     * Get the {@link Executor} for this thread.
-     *
-     * @return the {@link Executor} for this thread.
-     */
-    @NonNull
-    public static Executor getExecutor() {
-        synchronized (sLock) {
-            ensureInstanceLocked();
-            return sExecutor;
-        }
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/util/ThrottledRunnable.java b/apex/permission/service/java/com/android/permission/util/ThrottledRunnable.java
deleted file mode 100644
index ba1c393..0000000
--- a/apex/permission/service/java/com/android/permission/util/ThrottledRunnable.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2019 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.permission.util;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.SystemClock;
-
-import com.android.internal.annotations.GuardedBy;
-
-/**
- * A throttled runnable that can wrap around a runnable and throttle calls to its run().
- *
- * The throttling logic makes sure that the original runnable will be called only after the
- * specified interval passes since the last actual call. The first call in a while (after the
- * specified interval passes since the last actual call) will always result in the original runnable
- * being called immediately, and then subsequent calls will start to be throttled. It is guaranteed
- * that any call to this throttled runnable will always result in the original runnable being called
- * afterwards, within the specified interval.
- */
-public class ThrottledRunnable implements Runnable {
-
-    @NonNull
-    private final Handler mHandler;
-    private final long mIntervalMillis;
-    @NonNull
-    private final Runnable mRunnable;
-
-    @NonNull
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private long mScheduledUptimeMillis;
-
-    public ThrottledRunnable(@NonNull Handler handler, long intervalMillis,
-            @NonNull Runnable runnable) {
-        mHandler = handler;
-        mIntervalMillis = intervalMillis;
-        mRunnable = runnable;
-    }
-
-    @Override
-    public void run() {
-        synchronized (mLock) {
-            if (mHandler.hasCallbacks(mRunnable)) {
-                // We have a scheduled runnable.
-                return;
-            }
-            long currentUptimeMillis = SystemClock.uptimeMillis();
-            if (mScheduledUptimeMillis == 0
-                    || currentUptimeMillis > mScheduledUptimeMillis + mIntervalMillis) {
-                // First time in a while, schedule immediately.
-                mScheduledUptimeMillis = currentUptimeMillis;
-            } else {
-                // We were scheduled not long ago, so schedule with delay for throttling.
-                mScheduledUptimeMillis = mScheduledUptimeMillis + mIntervalMillis;
-            }
-            mHandler.postAtTime(mRunnable, mScheduledUptimeMillis);
-        }
-    }
-}
diff --git a/apex/permission/service/java/com/android/permission/util/package-info.java b/apex/permission/service/java/com/android/permission/util/package-info.java
deleted file mode 100644
index 18fada5..0000000
--- a/apex/permission/service/java/com/android/permission/util/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-/**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
- */
-@android.annotation.Hide
-package com.android.permission.util;
diff --git a/apex/permission/service/java/com/android/role/RoleManagerLocal.java b/apex/permission/service/java/com/android/role/RoleManagerLocal.java
deleted file mode 100644
index e243e2e..0000000
--- a/apex/permission/service/java/com/android/role/RoleManagerLocal.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2019 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.role;
-
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.annotation.UserIdInt;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Internal calls into {@link RoleService}.
- *
- * @hide
- */
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public interface RoleManagerLocal {
-    /**
-     * Get all roles and their holders.
-     *
-     * @param userId The user to query to roles for
-     *
-     * @return The roles and their holders
-     */
-    @NonNull
-    Map<String, Set<String>> getRolesAndHolders(@UserIdInt int userId);
-}
diff --git a/apex/permission/service/java/com/android/role/RoleService.java b/apex/permission/service/java/com/android/role/RoleService.java
deleted file mode 100644
index 5f7eb22..0000000
--- a/apex/permission/service/java/com/android/role/RoleService.java
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- * Copyright (C) 2018 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.role;
-
-import android.Manifest;
-import android.annotation.AnyThread;
-import android.annotation.MainThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.annotation.WorkerThread;
-import android.app.AppOpsManager;
-import android.app.role.IOnRoleHoldersChangedListener;
-import android.app.role.IRoleManager;
-import android.app.role.RoleControllerManager;
-import android.app.role.RoleManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteCallback;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.dump.DualDumpOutputStream;
-import com.android.permission.compat.UserHandleCompat;
-import com.android.permission.util.ArrayUtils;
-import com.android.permission.util.CollectionUtils;
-import com.android.permission.util.ForegroundThread;
-import com.android.permission.util.ThrottledRunnable;
-import com.android.server.LocalManagerRegistry;
-import com.android.server.SystemService;
-import com.android.server.role.RoleServicePlatformHelper;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Service for role management.
- *
- * @see RoleManager
- */
-public class RoleService extends SystemService implements RoleUserState.Callback {
-    private static final String LOG_TAG = RoleService.class.getSimpleName();
-
-    private static final boolean DEBUG = false;
-
-    private static final long GRANT_DEFAULT_ROLES_INTERVAL_MILLIS = 1000;
-
-    @NonNull
-    private final AppOpsManager mAppOpsManager;
-    @NonNull
-    private final UserManager mUserManager;
-
-    @NonNull
-    private final Object mLock = new Object();
-
-    @NonNull
-    private final RoleServicePlatformHelper mPlatformHelper;
-
-    /**
-     * Maps user id to its state.
-     */
-    @GuardedBy("mLock")
-    @NonNull
-    private final SparseArray<RoleUserState> mUserStates = new SparseArray<>();
-
-    /**
-     * Maps user id to its controller.
-     */
-    @GuardedBy("mLock")
-    @NonNull
-    private final SparseArray<RoleControllerManager> mControllers = new SparseArray<>();
-
-    /**
-     * Maps user id to its list of listeners.
-     */
-    @GuardedBy("mLock")
-    @NonNull
-    private final SparseArray<RemoteCallbackList<IOnRoleHoldersChangedListener>> mListeners =
-            new SparseArray<>();
-
-    @NonNull
-    private final Handler mListenerHandler = ForegroundThread.getHandler();
-
-    /**
-     * Maps user id to its throttled runnable for granting default roles.
-     */
-    @GuardedBy("mLock")
-    @NonNull
-    private final SparseArray<ThrottledRunnable> mGrantDefaultRolesThrottledRunnables =
-            new SparseArray<>();
-
-    public RoleService(@NonNull Context context) {
-        super(context);
-
-        mPlatformHelper = LocalManagerRegistry.getManager(RoleServicePlatformHelper.class);
-
-        RoleControllerManager.initializeRemoteServiceComponentName(context);
-
-        mAppOpsManager = context.getSystemService(AppOpsManager.class);
-        mUserManager = context.getSystemService(UserManager.class);
-
-        LocalManagerRegistry.addManager(RoleManagerLocal.class, new Local());
-
-        registerUserRemovedReceiver();
-    }
-
-    private void registerUserRemovedReceiver() {
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
-        getContext().registerReceiverForAllUsers(new BroadcastReceiver() {
-            @Override
-            public void onReceive(@NonNull Context context, @NonNull Intent intent) {
-                if (TextUtils.equals(intent.getAction(), Intent.ACTION_USER_REMOVED)) {
-                    int userId = intent.<UserHandle>getParcelableExtra(Intent.EXTRA_USER)
-                            .getIdentifier();
-                    onRemoveUser(userId);
-                }
-            }
-        }, intentFilter, null, null);
-    }
-
-    @Override
-    public void onStart() {
-        publishBinderService(Context.ROLE_SERVICE, new Stub());
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        intentFilter.addDataScheme("package");
-        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-        getContext().registerReceiverForAllUsers(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                int userId = UserHandleCompat.getUserId(intent.getIntExtra(Intent.EXTRA_UID, -1));
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Packages changed - re-running initial grants for user "
-                            + userId);
-                }
-                if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
-                        && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                    // Package is being upgraded - we're about to get ACTION_PACKAGE_ADDED
-                    return;
-                }
-                maybeGrantDefaultRolesAsync(userId);
-            }
-        }, intentFilter, null, null);
-    }
-
-    @Override
-    public void onUserStarting(@NonNull TargetUser user) {
-        maybeGrantDefaultRolesSync(user.getUserHandle().getIdentifier());
-    }
-
-    @MainThread
-    private void maybeGrantDefaultRolesSync(@UserIdInt int userId) {
-        AndroidFuture<Void> future = maybeGrantDefaultRolesInternal(userId);
-        try {
-            future.get(30, TimeUnit.SECONDS);
-        } catch (InterruptedException | ExecutionException | TimeoutException e) {
-            Log.e(LOG_TAG, "Failed to grant default roles for user " + userId, e);
-        }
-    }
-
-    private void maybeGrantDefaultRolesAsync(@UserIdInt int userId) {
-        ThrottledRunnable runnable;
-        synchronized (mLock) {
-            runnable = mGrantDefaultRolesThrottledRunnables.get(userId);
-            if (runnable == null) {
-                runnable = new ThrottledRunnable(ForegroundThread.getHandler(),
-                        GRANT_DEFAULT_ROLES_INTERVAL_MILLIS,
-                        () -> maybeGrantDefaultRolesInternal(userId));
-                mGrantDefaultRolesThrottledRunnables.put(userId, runnable);
-            }
-        }
-        runnable.run();
-    }
-
-    @AnyThread
-    @NonNull
-    private AndroidFuture<Void> maybeGrantDefaultRolesInternal(@UserIdInt int userId) {
-        RoleUserState userState = getOrCreateUserState(userId);
-        String oldPackagesHash = userState.getPackagesHash();
-        String newPackagesHash = mPlatformHelper.computePackageStateHash(userId);
-        if (Objects.equals(oldPackagesHash, newPackagesHash)) {
-            if (DEBUG) {
-                Log.i(LOG_TAG, "Already granted default roles for packages hash "
-                        + newPackagesHash);
-            }
-            return AndroidFuture.completedFuture(null);
-        }
-
-        // Some package state has changed, so grant default roles again.
-        Log.i(LOG_TAG, "Granting default roles...");
-        AndroidFuture<Void> future = new AndroidFuture<>();
-        getOrCreateController(userId).grantDefaultRoles(ForegroundThread.getExecutor(),
-                successful -> {
-                    if (successful) {
-                        userState.setPackagesHash(newPackagesHash);
-                        future.complete(null);
-                    } else {
-                        future.completeExceptionally(new RuntimeException());
-                    }
-                });
-        return future;
-    }
-
-    @NonNull
-    private RoleUserState getOrCreateUserState(@UserIdInt int userId) {
-        synchronized (mLock) {
-            RoleUserState userState = mUserStates.get(userId);
-            if (userState == null) {
-                userState = new RoleUserState(userId, mPlatformHelper, this);
-                mUserStates.put(userId, userState);
-            }
-            return userState;
-        }
-    }
-
-    @NonNull
-    private RoleControllerManager getOrCreateController(@UserIdInt int userId) {
-        synchronized (mLock) {
-            RoleControllerManager controller = mControllers.get(userId);
-            if (controller == null) {
-                Context systemContext = getContext();
-                Context context;
-                try {
-                    context = systemContext.createPackageContextAsUser(
-                            systemContext.getPackageName(), 0, UserHandle.of(userId));
-                } catch (PackageManager.NameNotFoundException e) {
-                    throw new RuntimeException(e);
-                }
-                controller = RoleControllerManager.createWithInitializedRemoteServiceComponentName(
-                        ForegroundThread.getHandler(), context);
-                mControllers.put(userId, controller);
-            }
-            return controller;
-        }
-    }
-
-    @Nullable
-    private RemoteCallbackList<IOnRoleHoldersChangedListener> getListeners(@UserIdInt int userId) {
-        synchronized (mLock) {
-            return mListeners.get(userId);
-        }
-    }
-
-    @NonNull
-    private RemoteCallbackList<IOnRoleHoldersChangedListener> getOrCreateListeners(
-            @UserIdInt int userId) {
-        synchronized (mLock) {
-            RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = mListeners.get(userId);
-            if (listeners == null) {
-                listeners = new RemoteCallbackList<>();
-                mListeners.put(userId, listeners);
-            }
-            return listeners;
-        }
-    }
-
-    private void onRemoveUser(@UserIdInt int userId) {
-        RemoteCallbackList<IOnRoleHoldersChangedListener> listeners;
-        RoleUserState userState;
-        synchronized (mLock) {
-            mGrantDefaultRolesThrottledRunnables.remove(userId);
-            listeners = mListeners.get(userId);
-            mListeners.remove(userId);
-            mControllers.remove(userId);
-            userState = mUserStates.get(userId);
-            mUserStates.remove(userId);
-        }
-        if (listeners != null) {
-            listeners.kill();
-        }
-        if (userState != null) {
-            userState.destroy();
-        }
-    }
-
-    @Override
-    public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
-        mListenerHandler.post(() -> notifyRoleHoldersChanged(roleName, userId));
-    }
-
-    @WorkerThread
-    private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
-        RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
-        if (listeners != null) {
-            notifyRoleHoldersChangedForListeners(listeners, roleName, userId);
-        }
-
-        RemoteCallbackList<IOnRoleHoldersChangedListener> allUsersListeners = getListeners(
-                UserHandleCompat.USER_ALL);
-        if (allUsersListeners != null) {
-            notifyRoleHoldersChangedForListeners(allUsersListeners, roleName, userId);
-        }
-    }
-
-    @WorkerThread
-    private void notifyRoleHoldersChangedForListeners(
-            @NonNull RemoteCallbackList<IOnRoleHoldersChangedListener> listeners,
-            @NonNull String roleName, @UserIdInt int userId) {
-        int broadcastCount = listeners.beginBroadcast();
-        try {
-            for (int i = 0; i < broadcastCount; i++) {
-                IOnRoleHoldersChangedListener listener = listeners.getBroadcastItem(i);
-                try {
-                    listener.onRoleHoldersChanged(roleName, userId);
-                } catch (RemoteException e) {
-                    Log.e(LOG_TAG, "Error calling OnRoleHoldersChangedListener", e);
-                }
-            }
-        } finally {
-            listeners.finishBroadcast();
-        }
-    }
-
-    private class Stub extends IRoleManager.Stub {
-
-        @Override
-        public boolean isRoleAvailable(@NonNull String roleName) {
-            Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-
-            int userId = UserHandleCompat.getUserId(getCallingUid());
-            return getOrCreateUserState(userId).isRoleAvailable(roleName);
-        }
-
-        @Override
-        public boolean isRoleHeld(@NonNull String roleName, @NonNull String packageName) {
-            int callingUid = getCallingUid();
-            mAppOpsManager.checkPackage(callingUid, packageName);
-
-            Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-            Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-
-            int userId = UserHandleCompat.getUserId(callingUid);
-            ArraySet<String> roleHolders = getOrCreateUserState(userId).getRoleHolders(roleName);
-            if (roleHolders == null) {
-                return false;
-            }
-            return roleHolders.contains(packageName);
-        }
-
-        @NonNull
-        @Override
-        public List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) {
-            if (!isUserExistent(userId)) {
-                Log.e(LOG_TAG, "user " + userId + " does not exist");
-                return Collections.emptyList();
-            }
-            enforceCrossUserPermission(userId, false, "getRoleHoldersAsUser");
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
-                    "getRoleHoldersAsUser");
-
-            Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-
-            ArraySet<String> roleHolders = getOrCreateUserState(userId).getRoleHolders(roleName);
-            if (roleHolders == null) {
-                return Collections.emptyList();
-            }
-            return new ArrayList<>(roleHolders);
-        }
-
-        @Override
-        public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
-                @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
-                @NonNull RemoteCallback callback) {
-            if (!isUserExistent(userId)) {
-                Log.e(LOG_TAG, "user " + userId + " does not exist");
-                return;
-            }
-            enforceCrossUserPermission(userId, false, "addRoleHolderAsUser");
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
-                    "addRoleHolderAsUser");
-
-            Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-            Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-            Objects.requireNonNull(callback, "callback cannot be null");
-
-            getOrCreateController(userId).onAddRoleHolder(roleName, packageName, flags,
-                    callback);
-        }
-
-        @Override
-        public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
-                @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
-                @NonNull RemoteCallback callback) {
-            if (!isUserExistent(userId)) {
-                Log.e(LOG_TAG, "user " + userId + " does not exist");
-                return;
-            }
-            enforceCrossUserPermission(userId, false, "removeRoleHolderAsUser");
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
-                    "removeRoleHolderAsUser");
-
-            Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-            Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-            Objects.requireNonNull(callback, "callback cannot be null");
-
-            getOrCreateController(userId).onRemoveRoleHolder(roleName, packageName, flags,
-                    callback);
-        }
-
-        @Override
-        public void clearRoleHoldersAsUser(@NonNull String roleName,
-                @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
-                @NonNull RemoteCallback callback) {
-            if (!isUserExistent(userId)) {
-                Log.e(LOG_TAG, "user " + userId + " does not exist");
-                return;
-            }
-            enforceCrossUserPermission(userId, false, "clearRoleHoldersAsUser");
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
-                    "clearRoleHoldersAsUser");
-
-            Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-            Objects.requireNonNull(callback, "callback cannot be null");
-
-            getOrCreateController(userId).onClearRoleHolders(roleName, flags, callback);
-        }
-
-        @Override
-        public void addOnRoleHoldersChangedListenerAsUser(
-                @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
-            if (userId != UserHandleCompat.USER_ALL && !isUserExistent(userId)) {
-                Log.e(LOG_TAG, "user " + userId + " does not exist");
-                return;
-            }
-            enforceCrossUserPermission(userId, true, "addOnRoleHoldersChangedListenerAsUser");
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
-                    "addOnRoleHoldersChangedListenerAsUser");
-
-            Objects.requireNonNull(listener, "listener cannot be null");
-
-            RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getOrCreateListeners(
-                    userId);
-            listeners.register(listener);
-        }
-
-        @Override
-        public void removeOnRoleHoldersChangedListenerAsUser(
-                @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
-            if (userId != UserHandleCompat.USER_ALL && !isUserExistent(userId)) {
-                Log.e(LOG_TAG, "user " + userId + " does not exist");
-                return;
-            }
-            enforceCrossUserPermission(userId, true, "removeOnRoleHoldersChangedListenerAsUser");
-            getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
-                    "removeOnRoleHoldersChangedListenerAsUser");
-
-            Objects.requireNonNull(listener, "listener cannot be null");
-
-            RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
-            if (listener == null) {
-                return;
-            }
-            listeners.unregister(listener);
-        }
-
-        @Override
-        public void setRoleNamesFromController(@NonNull List<String> roleNames) {
-            getContext().enforceCallingOrSelfPermission(
-                    RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
-                    "setRoleNamesFromController");
-
-            Objects.requireNonNull(roleNames, "roleNames cannot be null");
-
-            int userId = UserHandleCompat.getUserId(Binder.getCallingUid());
-            getOrCreateUserState(userId).setRoleNames(roleNames);
-        }
-
-        @Override
-        public boolean addRoleHolderFromController(@NonNull String roleName,
-                @NonNull String packageName) {
-            getContext().enforceCallingOrSelfPermission(
-                    RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
-                    "addRoleHolderFromController");
-
-            Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-            Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-
-            int userId = UserHandleCompat.getUserId(Binder.getCallingUid());
-            return getOrCreateUserState(userId).addRoleHolder(roleName, packageName);
-        }
-
-        @Override
-        public boolean removeRoleHolderFromController(@NonNull String roleName,
-                @NonNull String packageName) {
-            getContext().enforceCallingOrSelfPermission(
-                    RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
-                    "removeRoleHolderFromController");
-
-            Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
-            Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-
-            int userId = UserHandleCompat.getUserId(Binder.getCallingUid());
-            return getOrCreateUserState(userId).removeRoleHolder(roleName, packageName);
-        }
-
-        @Override
-        public List<String> getHeldRolesFromController(@NonNull String packageName) {
-            getContext().enforceCallingOrSelfPermission(
-                    RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
-                    "getRolesHeldFromController");
-
-            Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
-
-            int userId = UserHandleCompat.getUserId(Binder.getCallingUid());
-            return getOrCreateUserState(userId).getHeldRoles(packageName);
-        }
-
-        private boolean isUserExistent(@UserIdInt int userId) {
-            // FIXME: This checks whether the user is alive, but we should check for whether the
-            //  user is existent.
-            return mUserManager.getUserHandles(true).contains(UserHandle.of(userId));
-        }
-
-        private void enforceCrossUserPermission(@UserIdInt int userId, boolean allowAll,
-                @NonNull String message) {
-            final int callingUid = Binder.getCallingUid();
-            final int callingUserId = UserHandleCompat.getUserId(callingUid);
-            if (userId == callingUserId) {
-                return;
-            }
-            Preconditions.checkArgument(userId >= UserHandleCompat.USER_SYSTEM
-                    || (allowAll && userId == UserHandleCompat.USER_ALL), "Invalid user " + userId);
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-            if (callingUid == Process.SHELL_UID && userId >= UserHandleCompat.USER_SYSTEM) {
-                if (mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_DEBUGGING_FEATURES,
-                        UserHandle.of(userId))) {
-                    throw new SecurityException("Shell does not have permission to access user "
-                            + userId);
-                }
-            }
-        }
-
-        @Override
-        public int handleShellCommand(@NonNull ParcelFileDescriptor in,
-                @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
-                @NonNull String[] args) {
-            return new RoleShellCommand(this).exec(this, in.getFileDescriptor(),
-                    out.getFileDescriptor(), err.getFileDescriptor(), args);
-        }
-
-        @Nullable
-        @Override
-        public String getBrowserRoleHolder(@UserIdInt int userId) {
-            final int callingUid = Binder.getCallingUid();
-            if (UserHandleCompat.getUserId(callingUid) != userId) {
-                getContext().enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
-            }
-            if (isInstantApp(callingUid)) {
-                return null;
-            }
-
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                return CollectionUtils.firstOrNull(getRoleHoldersAsUser(RoleManager.ROLE_BROWSER,
-                        userId));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        private boolean isInstantApp(int uid) {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                final UserHandle user = UserHandle.getUserHandleForUid(uid);
-                final Context userContext = getContext().createContextAsUser(user, 0);
-                final PackageManager userPackageManager = userContext.getPackageManager();
-                // Instant apps can not have shared UID, so it's safe to check only the first
-                // package name here.
-                final String packageName = ArrayUtils.firstOrNull(
-                        userPackageManager.getPackagesForUid(uid));
-                if (packageName == null) {
-                    return false;
-                }
-                return userPackageManager.isInstantApp(packageName);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
-        public boolean setBrowserRoleHolder(@Nullable String packageName, @UserIdInt int userId) {
-            final Context context = getContext();
-            context.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
-            if (UserHandleCompat.getUserId(Binder.getCallingUid()) != userId) {
-                context.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
-            }
-
-            if (!isUserExistent(userId)) {
-                return false;
-            }
-
-            final AndroidFuture<Void> future = new AndroidFuture<>();
-            final RemoteCallback callback = new RemoteCallback(result -> {
-                boolean successful = result != null;
-                if (successful) {
-                    future.complete(null);
-                } else {
-                    future.completeExceptionally(new RuntimeException());
-                }
-            });
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                if (packageName != null) {
-                    addRoleHolderAsUser(RoleManager.ROLE_BROWSER, packageName, 0, userId, callback);
-                } else {
-                    clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, userId, callback);
-                }
-                try {
-                    future.get(5, TimeUnit.SECONDS);
-                } catch (InterruptedException | ExecutionException | TimeoutException e) {
-                    Log.e(LOG_TAG, "Exception while setting default browser: " + packageName, e);
-                    return false;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-
-            return true;
-        }
-
-        @Override
-        public String getSmsRoleHolder(int userId) {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                return CollectionUtils.firstOrNull(getRoleHoldersAsUser(RoleManager.ROLE_SMS,
-                        userId));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
-        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
-                @Nullable String[] args) {
-            if (!checkDumpPermission("role", fout)) {
-                return;
-            }
-
-            boolean dumpAsProto = args != null && ArrayUtils.contains(args, "--proto");
-            DualDumpOutputStream dumpOutputStream;
-            if (dumpAsProto) {
-                dumpOutputStream = new DualDumpOutputStream(new ProtoOutputStream(
-                        new FileOutputStream(fd)));
-            } else {
-                fout.println("ROLE STATE (dumpsys role):");
-                dumpOutputStream = new DualDumpOutputStream(new IndentingPrintWriter(fout, "  "));
-            }
-
-            synchronized (mLock) {
-                final int userStatesSize = mUserStates.size();
-                for (int i = 0; i < userStatesSize; i++) {
-                    final RoleUserState userState = mUserStates.valueAt(i);
-
-                    userState.dump(dumpOutputStream, "user_states",
-                            RoleServiceDumpProto.USER_STATES);
-                }
-            }
-
-            dumpOutputStream.flush();
-        }
-
-        private boolean checkDumpPermission(@NonNull String serviceName,
-                @NonNull PrintWriter writer) {
-            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                    != PackageManager.PERMISSION_GRANTED) {
-                writer.println("Permission Denial: can't dump " + serviceName + " from from pid="
-                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                        + " due to missing " + android.Manifest.permission.DUMP + " permission");
-                return false;
-            } else {
-                return true;
-            }
-        }
-    }
-
-    private class Local implements RoleManagerLocal {
-        @NonNull
-        @Override
-        public Map<String, Set<String>> getRolesAndHolders(@UserIdInt int userId) {
-            // Convert ArrayMap<String, ArraySet<String>> to Map<String, Set<String>> for the API.
-            //noinspection unchecked
-            return (Map<String, Set<String>>) (Map<String, ?>)
-                    getOrCreateUserState(userId).getRolesAndHolders();
-        }
-    }
-}
diff --git a/apex/permission/service/java/com/android/role/RoleShellCommand.java b/apex/permission/service/java/com/android/role/RoleShellCommand.java
deleted file mode 100644
index 03b7c76..0000000
--- a/apex/permission/service/java/com/android/role/RoleShellCommand.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2018 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.role;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.role.IRoleManager;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-
-import com.android.modules.utils.BasicShellCommandHandler;
-import com.android.permission.compat.UserHandleCompat;
-
-import java.io.PrintWriter;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-
-class RoleShellCommand extends BasicShellCommandHandler {
-    @NonNull
-    private final IRoleManager mRoleManager;
-
-    RoleShellCommand(@NonNull IRoleManager roleManager) {
-        mRoleManager = roleManager;
-    }
-
-    private class CallbackFuture extends CompletableFuture<Void> {
-        @NonNull
-        public RemoteCallback createCallback() {
-            return new RemoteCallback(result -> {
-                boolean successful = result != null;
-                if (successful) {
-                    complete(null);
-                } else {
-                    completeExceptionally(new RuntimeException("Failed"));
-                }
-            });
-        }
-
-        public int waitForResult() {
-            try {
-                get(5, TimeUnit.SECONDS);
-                return 0;
-            } catch (Exception e) {
-                getErrPrintWriter().println("Error: see logcat for details.\n" + e);
-                return -1;
-            }
-        }
-    }
-
-    @Override
-    public int onCommand(@Nullable String cmd) {
-        if (cmd == null) {
-            return handleDefaultCommands(cmd);
-        }
-
-        PrintWriter pw = getOutPrintWriter();
-        try {
-            switch (cmd) {
-                case "add-role-holder":
-                    return runAddRoleHolder();
-                case "remove-role-holder":
-                    return runRemoveRoleHolder();
-                case "clear-role-holders":
-                    return runClearRoleHolders();
-                default:
-                    return handleDefaultCommands(cmd);
-            }
-        } catch (RemoteException e) {
-            pw.println("Remote exception: " + e);
-        }
-        return -1;
-    }
-
-    private int getUserIdMaybe() {
-        int userId = UserHandleCompat.USER_SYSTEM;
-        String option = getNextOption();
-        if (option != null && option.equals("--user")) {
-            userId = Integer.parseInt(getNextArgRequired());
-        }
-        return userId;
-    }
-
-    private int getFlagsMaybe() {
-        String flags = getNextArg();
-        if (flags == null) {
-            return 0;
-        }
-        return Integer.parseInt(flags);
-    }
-
-    private int runAddRoleHolder() throws RemoteException {
-        int userId = getUserIdMaybe();
-        String roleName = getNextArgRequired();
-        String packageName = getNextArgRequired();
-        int flags = getFlagsMaybe();
-
-        CallbackFuture future = new CallbackFuture();
-        mRoleManager.addRoleHolderAsUser(roleName, packageName, flags, userId,
-                future.createCallback());
-        return future.waitForResult();
-    }
-
-    private int runRemoveRoleHolder() throws RemoteException {
-        int userId = getUserIdMaybe();
-        String roleName = getNextArgRequired();
-        String packageName = getNextArgRequired();
-        int flags = getFlagsMaybe();
-
-        CallbackFuture future = new CallbackFuture();
-        mRoleManager.removeRoleHolderAsUser(roleName, packageName, flags, userId,
-                future.createCallback());
-        return future.waitForResult();
-    }
-
-    private int runClearRoleHolders() throws RemoteException {
-        int userId = getUserIdMaybe();
-        String roleName = getNextArgRequired();
-        int flags = getFlagsMaybe();
-
-        CallbackFuture future = new CallbackFuture();
-        mRoleManager.clearRoleHoldersAsUser(roleName, flags, userId, future.createCallback());
-        return future.waitForResult();
-    }
-
-    @Override
-    public void onHelp() {
-        PrintWriter pw = getOutPrintWriter();
-        pw.println("Role (role) commands:");
-        pw.println("  help or -h");
-        pw.println("    Print this help text.");
-        pw.println();
-        pw.println("  add-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]");
-        pw.println("  remove-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]");
-        pw.println("  clear-role-holders [--user USER_ID] ROLE [FLAGS]");
-        pw.println();
-    }
-}
diff --git a/apex/permission/service/java/com/android/role/RoleUserState.java b/apex/permission/service/java/com/android/role/RoleUserState.java
deleted file mode 100644
index 78d8d15..0000000
--- a/apex/permission/service/java/com/android/role/RoleUserState.java
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 2018 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.role;
-
-import android.annotation.CheckResult;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.annotation.WorkerThread;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.dump.DualDumpOutputStream;
-import com.android.permission.util.BackgroundThread;
-import com.android.permission.util.CollectionUtils;
-import com.android.role.persistence.RolesPersistence;
-import com.android.role.persistence.RolesState;
-import com.android.server.role.RoleServicePlatformHelper;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * Stores the state of roles for a user.
- */
-class RoleUserState {
-    private static final String LOG_TAG = RoleUserState.class.getSimpleName();
-
-    public static final int VERSION_UNDEFINED = -1;
-
-    private static final long WRITE_DELAY_MILLIS = 200;
-
-    private final RolesPersistence mPersistence = RolesPersistence.createInstance();
-
-    @UserIdInt
-    private final int mUserId;
-
-    @NonNull
-    private final RoleServicePlatformHelper mPlatformHelper;
-
-    @NonNull
-    private final Callback mCallback;
-
-    @NonNull
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private int mVersion = VERSION_UNDEFINED;
-
-    @GuardedBy("mLock")
-    @Nullable
-    private String mPackagesHash;
-
-    /**
-     * Maps role names to its holders' package names. The values should never be null.
-     */
-    @GuardedBy("mLock")
-    @NonNull
-    private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>();
-
-    @GuardedBy("mLock")
-    private boolean mWriteScheduled;
-
-    @GuardedBy("mLock")
-    private boolean mDestroyed;
-
-    @NonNull
-    private final Handler mWriteHandler = new Handler(BackgroundThread.get().getLooper());
-
-    /**
-     * Create a new user state, and read its state from disk if previously persisted.
-     *
-     * @param userId the user id for this user state
-     * @param platformHelper the platform helper
-     * @param callback the callback for this user state
-     */
-    public RoleUserState(@UserIdInt int userId, @NonNull RoleServicePlatformHelper platformHelper,
-            @NonNull Callback callback) {
-        mUserId = userId;
-        mPlatformHelper = platformHelper;
-        mCallback = callback;
-
-        readFile();
-    }
-
-    /**
-     * Get the version of this user state.
-     */
-    public int getVersion() {
-        synchronized (mLock) {
-            return mVersion;
-        }
-    }
-
-    /**
-     * Set the version of this user state.
-     *
-     * @param version the version to set
-     */
-    public void setVersion(int version) {
-        synchronized (mLock) {
-            if (mVersion == version) {
-                return;
-            }
-            mVersion = version;
-            scheduleWriteFileLocked();
-        }
-    }
-
-    /**
-     * Get the hash representing the state of packages during the last time initial grants was run.
-     *
-     * @return the hash representing the state of packages
-     */
-    @Nullable
-    public String getPackagesHash() {
-        synchronized (mLock) {
-            return mPackagesHash;
-        }
-    }
-
-    /**
-     * Set the hash representing the state of packages during the last time initial grants was run.
-     *
-     * @param packagesHash the hash representing the state of packages
-     */
-    public void setPackagesHash(@Nullable String packagesHash) {
-        synchronized (mLock) {
-            if (Objects.equals(mPackagesHash, packagesHash)) {
-                return;
-            }
-            mPackagesHash = packagesHash;
-            scheduleWriteFileLocked();
-        }
-    }
-
-    /**
-     * Get whether the role is available.
-     *
-     * @param roleName the name of the role to get the holders for
-     *
-     * @return whether the role is available
-     */
-    public boolean isRoleAvailable(@NonNull String roleName) {
-        synchronized (mLock) {
-            return mRoles.containsKey(roleName);
-        }
-    }
-
-    /**
-     * Get the holders of a role.
-     *
-     * @param roleName the name of the role to query for
-     *
-     * @return the set of role holders, or {@code null} if and only if the role is not found
-     */
-    @Nullable
-    public ArraySet<String> getRoleHolders(@NonNull String roleName) {
-        synchronized (mLock) {
-            ArraySet<String> packageNames = mRoles.get(roleName);
-            if (packageNames == null) {
-                return null;
-            }
-            return new ArraySet<>(packageNames);
-        }
-    }
-
-    /**
-     * Adds the given role, effectively marking it as {@link #isRoleAvailable available}
-     *
-     * @param roleName the name of the role
-     *
-     * @return whether any changes were made
-     */
-    public boolean addRoleName(@NonNull String roleName) {
-        synchronized (mLock) {
-            if (!mRoles.containsKey(roleName)) {
-                mRoles.put(roleName, new ArraySet<>());
-                Log.i(LOG_TAG, "Added new role: " + roleName);
-                scheduleWriteFileLocked();
-                return true;
-            } else {
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Set the names of all available roles.
-     *
-     * @param roleNames the names of all the available roles
-     */
-    public void setRoleNames(@NonNull List<String> roleNames) {
-        synchronized (mLock) {
-            boolean changed = false;
-
-            for (int i = mRoles.size() - 1; i >= 0; i--) {
-                String roleName = mRoles.keyAt(i);
-
-                if (!roleNames.contains(roleName)) {
-                    ArraySet<String> packageNames = mRoles.valueAt(i);
-                    if (!packageNames.isEmpty()) {
-                        Log.e(LOG_TAG, "Holders of a removed role should have been cleaned up,"
-                                + " role: " + roleName + ", holders: " + packageNames);
-                    }
-                    mRoles.removeAt(i);
-                    changed = true;
-                }
-            }
-
-            int roleNamesSize = roleNames.size();
-            for (int i = 0; i < roleNamesSize; i++) {
-                changed |= addRoleName(roleNames.get(i));
-            }
-
-            if (changed) {
-                scheduleWriteFileLocked();
-            }
-        }
-    }
-
-    /**
-     * Add a holder to a role.
-     *
-     * @param roleName the name of the role to add the holder to
-     * @param packageName the package name of the new holder
-     *
-     * @return {@code false} if and only if the role is not found
-     */
-    @CheckResult
-    public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
-        boolean changed;
-
-        synchronized (mLock) {
-            ArraySet<String> roleHolders = mRoles.get(roleName);
-            if (roleHolders == null) {
-                Log.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
-                        + ", package: " + packageName);
-                return false;
-            }
-            changed = roleHolders.add(packageName);
-            if (changed) {
-                scheduleWriteFileLocked();
-            }
-        }
-
-        if (changed) {
-            mCallback.onRoleHoldersChanged(roleName, mUserId);
-        }
-        return true;
-    }
-
-    /**
-     * Remove a holder from a role.
-     *
-     * @param roleName the name of the role to remove the holder from
-     * @param packageName the package name of the holder to remove
-     *
-     * @return {@code false} if and only if the role is not found
-     */
-    @CheckResult
-    public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) {
-        boolean changed;
-
-        synchronized (mLock) {
-            ArraySet<String> roleHolders = mRoles.get(roleName);
-            if (roleHolders == null) {
-                Log.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
-                        + ", package: " + packageName);
-                return false;
-            }
-
-            changed = roleHolders.remove(packageName);
-            if (changed) {
-                scheduleWriteFileLocked();
-            }
-        }
-
-        if (changed) {
-            mCallback.onRoleHoldersChanged(roleName, mUserId);
-        }
-        return true;
-    }
-
-    /**
-     * @see android.app.role.RoleManager#getHeldRolesFromController
-     */
-    @NonNull
-    public List<String> getHeldRoles(@NonNull String packageName) {
-        synchronized (mLock) {
-            List<String> roleNames = new ArrayList<>();
-            int size = mRoles.size();
-            for (int i = 0; i < size; i++) {
-                if (mRoles.valueAt(i).contains(packageName)) {
-                    roleNames.add(mRoles.keyAt(i));
-                }
-            }
-            return roleNames;
-        }
-    }
-
-    /**
-     * Schedule writing the state to file.
-     */
-    @GuardedBy("mLock")
-    private void scheduleWriteFileLocked() {
-        if (mDestroyed) {
-            return;
-        }
-
-        if (!mWriteScheduled) {
-            mWriteHandler.postDelayed(this::writeFile, WRITE_DELAY_MILLIS);
-            mWriteScheduled = true;
-        }
-    }
-
-    @WorkerThread
-    private void writeFile() {
-        RolesState roles;
-        synchronized (mLock) {
-            if (mDestroyed) {
-                return;
-            }
-
-            mWriteScheduled = false;
-
-            roles = new RolesState(mVersion, mPackagesHash,
-                    (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked());
-        }
-
-        mPersistence.writeForUser(roles, UserHandle.of(mUserId));
-    }
-
-    private void readFile() {
-        synchronized (mLock) {
-            RolesState roleState = mPersistence.readForUser(UserHandle.of(mUserId));
-
-            Map<String, Set<String>> roles;
-            if (roleState != null) {
-                mVersion = roleState.getVersion();
-                mPackagesHash = roleState.getPackagesHash();
-                roles = roleState.getRoles();
-            } else {
-                roles = mPlatformHelper.getLegacyRoleState(mUserId);
-            }
-            mRoles.clear();
-            for (Map.Entry<String, Set<String>> entry : roles.entrySet()) {
-                String roleName = entry.getKey();
-                ArraySet<String> roleHolders = new ArraySet<>(entry.getValue());
-                mRoles.put(roleName, roleHolders);
-            }
-
-            if (roleState == null) {
-                scheduleWriteFileLocked();
-            }
-        }
-    }
-
-    /**
-     * Dump this user state.
-     *
-     * @param dumpOutputStream the output stream to dump to
-     */
-    public void dump(@NonNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName,
-            long fieldId) {
-        int version;
-        String packagesHash;
-        ArrayMap<String, ArraySet<String>> roles;
-        synchronized (mLock) {
-            version = mVersion;
-            packagesHash = mPackagesHash;
-            roles = snapshotRolesLocked();
-        }
-
-        long fieldToken = dumpOutputStream.start(fieldName, fieldId);
-        dumpOutputStream.write("user_id", RoleUserStateProto.USER_ID, mUserId);
-        dumpOutputStream.write("version", RoleUserStateProto.VERSION, version);
-        dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, packagesHash);
-
-        int rolesSize = roles.size();
-        for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
-            String roleName = roles.keyAt(rolesIndex);
-            ArraySet<String> roleHolders = roles.valueAt(rolesIndex);
-
-            long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES);
-            dumpOutputStream.write("name", RoleProto.NAME, roleName);
-
-            int roleHoldersSize = roleHolders.size();
-            for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) {
-                String roleHolder = roleHolders.valueAt(roleHoldersIndex);
-
-                dumpOutputStream.write("holders", RoleProto.HOLDERS, roleHolder);
-            }
-
-            dumpOutputStream.end(rolesToken);
-        }
-
-        dumpOutputStream.end(fieldToken);
-    }
-
-    /**
-     * Get the roles and their holders.
-     *
-     * @return A copy of the roles and their holders
-     */
-    @NonNull
-    public ArrayMap<String, ArraySet<String>> getRolesAndHolders() {
-        synchronized (mLock) {
-            return snapshotRolesLocked();
-        }
-    }
-
-    @GuardedBy("mLock")
-    @NonNull
-    private ArrayMap<String, ArraySet<String>> snapshotRolesLocked() {
-        ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
-        for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) {
-            String roleName = mRoles.keyAt(i);
-            ArraySet<String> roleHolders = mRoles.valueAt(i);
-
-            roleHolders = new ArraySet<>(roleHolders);
-            roles.put(roleName, roleHolders);
-        }
-        return roles;
-    }
-
-    /**
-     * Destroy this user state and delete the corresponding file. Any pending writes to the file
-     * will be cancelled, and any future interaction with this state will throw an exception.
-     */
-    public void destroy() {
-        synchronized (mLock) {
-            if (mDestroyed) {
-                throw new IllegalStateException("This RoleUserState has already been destroyed");
-            }
-            mWriteHandler.removeCallbacksAndMessages(null);
-            mPersistence.deleteForUser(UserHandle.of(mUserId));
-            mDestroyed = true;
-        }
-    }
-
-    /**
-     * Callback for a user state.
-     */
-    public interface Callback {
-
-        /**
-         * Called when the holders of roles are changed.
-         *
-         * @param roleName the name of the role whose holders are changed
-         * @param userId the user id for this role holder change
-         */
-        void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId);
-    }
-}
diff --git a/apex/permission/service/java/com/android/role/TEST_MAPPING b/apex/permission/service/java/com/android/role/TEST_MAPPING
deleted file mode 100644
index 0d7bc14..0000000
--- a/apex/permission/service/java/com/android/role/TEST_MAPPING
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-    "presubmit": [
-        {
-            "name": "CtsStatsdHostTestCases",
-            "options": [
-                {
-                    "include-filter": "android.cts.statsd.atom.UidAtomTests#testRoleHolder"
-                }
-            ]
-        },
-        {
-            "name": "CtsRoleTestCases",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ]
-        }
-    ]
-}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java
deleted file mode 100644
index 2e5a28a..0000000
--- a/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 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.role.persistence;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.SystemApi.Client;
-import android.os.UserHandle;
-
-/**
- * Persistence for roles.
- *
- * TODO(b/147914847): Remove @hide when it becomes the default.
- * @hide
- */
-@SystemApi(client = Client.SYSTEM_SERVER)
-public interface RolesPersistence {
-
-    /**
-     * Read the roles from persistence.
-     *
-     * This will perform I/O operations synchronously.
-     *
-     * @param user the user to read for
-     * @return the roles read
-     */
-    @Nullable
-    RolesState readForUser(@NonNull UserHandle user);
-
-    /**
-     * Write the roles to persistence.
-     *
-     * This will perform I/O operations synchronously.
-     *
-     * @param roles the roles to write
-     * @param user the user to write for
-     */
-    void writeForUser(@NonNull RolesState roles, @NonNull UserHandle user);
-
-    /**
-     * Delete the roles from persistence.
-     *
-     * This will perform I/O operations synchronously.
-     *
-     * @param user the user to delete for
-     */
-    void deleteForUser(@NonNull UserHandle user);
-
-    /**
-     * Create a new instance of {@link RolesPersistence} implementation.
-     *
-     * @return the new instance.
-     */
-    @NonNull
-    static RolesPersistence createInstance() {
-        return new RolesPersistenceImpl();
-    }
-}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java
deleted file mode 100644
index f66257f..0000000
--- a/apex/permission/service/java/com/android/role/persistence/RolesPersistenceImpl.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2020 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.role.persistence;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ApexEnvironment;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.Xml;
-
-import com.android.permission.persistence.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Persistence implementation for roles.
- *
- * TODO(b/147914847): Remove @hide when it becomes the default.
- * @hide
- */
-public class RolesPersistenceImpl implements RolesPersistence {
-
-    private static final String LOG_TAG = RolesPersistenceImpl.class.getSimpleName();
-
-    private static final String APEX_MODULE_NAME = "com.android.permission";
-
-    private static final String ROLES_FILE_NAME = "roles.xml";
-
-    private static final String TAG_ROLES = "roles";
-    private static final String TAG_ROLE = "role";
-    private static final String TAG_HOLDER = "holder";
-
-    private static final String ATTRIBUTE_VERSION = "version";
-    private static final String ATTRIBUTE_NAME = "name";
-    private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
-
-    @Nullable
-    @Override
-    public RolesState readForUser(@NonNull UserHandle user) {
-        File file = getFile(user);
-        try (FileInputStream inputStream = new AtomicFile(file).openRead()) {
-            XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(inputStream, null);
-            return parseXml(parser);
-        } catch (FileNotFoundException e) {
-            Log.i(LOG_TAG, "roles.xml not found");
-            return null;
-        } catch (XmlPullParserException | IOException e) {
-            throw new IllegalStateException("Failed to read roles.xml: " + file , e);
-        }
-    }
-
-    @NonNull
-    private static RolesState parseXml(@NonNull XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        int type;
-        int depth;
-        int innerDepth = parser.getDepth() + 1;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
-            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            if (parser.getName().equals(TAG_ROLES)) {
-                return parseRoles(parser);
-            }
-        }
-        throw new IllegalStateException("Missing <" + TAG_ROLES + "> in roles.xml");
-    }
-
-    @NonNull
-    private static RolesState parseRoles(@NonNull XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        int version = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION));
-        String packagesHash = parser.getAttributeValue(null, ATTRIBUTE_PACKAGES_HASH);
-
-        Map<String, Set<String>> roles = new ArrayMap<>();
-        int type;
-        int depth;
-        int innerDepth = parser.getDepth() + 1;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
-            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            if (parser.getName().equals(TAG_ROLE)) {
-                String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
-                Set<String> roleHolders = parseRoleHolders(parser);
-                roles.put(roleName, roleHolders);
-            }
-        }
-
-        return new RolesState(version, packagesHash, roles);
-    }
-
-    @NonNull
-    private static Set<String> parseRoleHolders(@NonNull XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        Set<String> roleHolders = new ArraySet<>();
-        int type;
-        int depth;
-        int innerDepth = parser.getDepth() + 1;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
-            if (depth > innerDepth || type != XmlPullParser.START_TAG) {
-                continue;
-            }
-
-            if (parser.getName().equals(TAG_HOLDER)) {
-                String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME);
-                roleHolders.add(roleHolder);
-            }
-        }
-        return roleHolders;
-    }
-
-    @Override
-    public void writeForUser(@NonNull RolesState roles, @NonNull UserHandle user) {
-        File file = getFile(user);
-        AtomicFile atomicFile = new AtomicFile(file);
-        FileOutputStream outputStream = null;
-        try {
-            outputStream = atomicFile.startWrite();
-
-            XmlSerializer serializer = Xml.newSerializer();
-            serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
-            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-            serializer.startDocument(null, true);
-
-            serializeRoles(serializer, roles);
-
-            serializer.endDocument();
-            atomicFile.finishWrite(outputStream);
-        } catch (Exception e) {
-            Log.wtf(LOG_TAG, "Failed to write roles.xml, restoring backup: " + file,
-                    e);
-            atomicFile.failWrite(outputStream);
-        } finally {
-            IoUtils.closeQuietly(outputStream);
-        }
-    }
-
-    private static void serializeRoles(@NonNull XmlSerializer serializer,
-            @NonNull RolesState roles) throws IOException {
-        serializer.startTag(null, TAG_ROLES);
-
-        int version = roles.getVersion();
-        serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version));
-        String packagesHash = roles.getPackagesHash();
-        if (packagesHash != null) {
-            serializer.attribute(null, ATTRIBUTE_PACKAGES_HASH, packagesHash);
-        }
-
-        for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) {
-            String roleName = entry.getKey();
-            Set<String> roleHolders = entry.getValue();
-
-            serializer.startTag(null, TAG_ROLE);
-            serializer.attribute(null, ATTRIBUTE_NAME, roleName);
-            serializeRoleHolders(serializer, roleHolders);
-            serializer.endTag(null, TAG_ROLE);
-        }
-
-        serializer.endTag(null, TAG_ROLES);
-    }
-
-    private static void serializeRoleHolders(@NonNull XmlSerializer serializer,
-            @NonNull Set<String> roleHolders) throws IOException {
-        for (String roleHolder : roleHolders) {
-            serializer.startTag(null, TAG_HOLDER);
-            serializer.attribute(null, ATTRIBUTE_NAME, roleHolder);
-            serializer.endTag(null, TAG_HOLDER);
-        }
-    }
-
-    @Override
-    public void deleteForUser(@NonNull UserHandle user) {
-        getFile(user).delete();
-    }
-
-    @NonNull
-    private static File getFile(@NonNull UserHandle user) {
-        ApexEnvironment apexEnvironment = ApexEnvironment.getApexEnvironment(APEX_MODULE_NAME);
-        File dataDirectory = apexEnvironment.getDeviceProtectedDataDirForUser(user);
-        return new File(dataDirectory, ROLES_FILE_NAME);
-    }
-}
diff --git a/apex/permission/service/java/com/android/role/persistence/RolesState.java b/apex/permission/service/java/com/android/role/persistence/RolesState.java
deleted file mode 100644
index f61efa0..0000000
--- a/apex/permission/service/java/com/android/role/persistence/RolesState.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2020 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.role.persistence;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.SystemApi.Client;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * State of all roles.
- *
- * TODO(b/147914847): Remove @hide when it becomes the default.
- * @hide
- */
-@SystemApi(client = Client.SYSTEM_SERVER)
-public final class RolesState {
-
-    /**
-     * The version of the roles.
-     */
-    private final int mVersion;
-
-    /**
-     * The hash of all packages in the system.
-     */
-    @Nullable
-    private final String mPackagesHash;
-
-    /**
-     * The roles.
-     */
-    @NonNull
-    private final Map<String, Set<String>> mRoles;
-
-    /**
-     * Create a new instance of this class.
-     *
-     * @param version the version of the roles
-     * @param packagesHash the hash of all packages in the system
-     * @param roles the roles
-     */
-    public RolesState(int version, @Nullable String packagesHash,
-            @NonNull Map<String, Set<String>> roles) {
-        mVersion = version;
-        mPackagesHash = packagesHash;
-        mRoles = roles;
-    }
-
-    /**
-     * Get the version of the roles.
-     *
-     * @return the version of the roles
-     */
-    public int getVersion() {
-        return mVersion;
-    }
-
-    /**
-     * Get the hash of all packages in the system.
-     *
-     * @return the hash of all packages in the system
-     */
-    @Nullable
-    public String getPackagesHash() {
-        return mPackagesHash;
-    }
-
-    /**
-     * Get the roles.
-     *
-     * @return the roles
-     */
-    @NonNull
-    public Map<String, Set<String>> getRoles() {
-        return mRoles;
-    }
-
-    @Override
-    public boolean equals(Object object) {
-        if (this == object) {
-            return true;
-        }
-        if (object == null || getClass() != object.getClass()) {
-            return false;
-        }
-        RolesState that = (RolesState) object;
-        return mVersion == that.mVersion
-                && Objects.equals(mPackagesHash, that.mPackagesHash)
-                && Objects.equals(mRoles, that.mRoles);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mVersion, mPackagesHash, mRoles);
-    }
-}
diff --git a/apex/permission/service/proto/com/android/role/roleservice.proto b/apex/permission/service/proto/com/android/role/roleservice.proto
deleted file mode 100644
index 79c4229..0000000
--- a/apex/permission/service/proto/com/android/role/roleservice.proto
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-syntax = "proto2";
-
-package com.android.role;
-
-option java_multiple_files = true;
-
-import "frameworks/base/core/proto/android/privacy.proto";
-
-message RoleServiceDumpProto {
-  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
-  // List of per-user states for all users.
-  repeated RoleUserStateProto user_states = 1;
-}
-
-message RoleUserStateProto {
-  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
-  // The user id of this state.
-  optional int32 user_id = 1;
-
-  // The version of this state.
-  optional int32 version = 2;
-
-  // The hash of packages for this state.
-  optional string packages_hash = 3;
-
-  // The set of roles in this state.
-  repeated RoleProto roles = 4;
-}
-
-message RoleProto {
-  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
-  // The name of this role, e.g. "android.app.role.DIALER".
-  optional string name = 1;
-
-  // The package names of the holders of this role.
-  repeated string holders = 2;
-}
diff --git a/apex/permission/testing/Android.bp b/apex/permission/testing/Android.bp
deleted file mode 100644
index 63bf0a0..0000000
--- a/apex/permission/testing/Android.bp
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2019 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.
-
-apex_test {
-    name: "test_com.android.permission",
-    visibility: [
-        "//system/apex/tests",
-    ],
-    defaults: ["com.android.permission-defaults"],
-    manifest: "test_manifest.json",
-    file_contexts: ":com.android.permission-file_contexts",
-    // Test APEX, should never be installed
-    installable: false,
-}
diff --git a/apex/permission/testing/test_manifest.json b/apex/permission/testing/test_manifest.json
deleted file mode 100644
index bc19a9e..0000000
--- a/apex/permission/testing/test_manifest.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "name": "com.android.permission",
-  "version": 2147483647
-}
diff --git a/apex/permission/tests/java/com/android/permission/persistence/RuntimePermissionsPersistenceTest.kt b/apex/permission/tests/java/com/android/permission/persistence/RuntimePermissionsPersistenceTest.kt
deleted file mode 100644
index 2987da0..0000000
--- a/apex/permission/tests/java/com/android/permission/persistence/RuntimePermissionsPersistenceTest.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2020 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.permission.persistence
-
-import android.content.ApexEnvironment
-import android.content.Context
-import android.os.Process
-import android.os.UserHandle
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations.initMocks
-import org.mockito.MockitoSession
-import org.mockito.quality.Strictness
-import java.io.File
-
-@RunWith(AndroidJUnit4::class)
-class RuntimePermissionsPersistenceTest {
-    private val context = InstrumentationRegistry.getInstrumentation().context
-
-    private lateinit var mockDataDirectory: File
-
-    private lateinit var mockitoSession: MockitoSession
-    @Mock
-    lateinit var apexEnvironment: ApexEnvironment
-
-    private val persistence = RuntimePermissionsPersistence.createInstance()
-    private val permissionState = RuntimePermissionsState.PermissionState("permission", true, 3)
-    private val state = RuntimePermissionsState(
-        1, "fingerprint", mapOf("package" to listOf(permissionState)),
-        mapOf("sharedUser" to listOf(permissionState))
-    )
-    private val user = Process.myUserHandle()
-
-    @Before
-    fun createMockDataDirectory() {
-        mockDataDirectory = context.getDir("mock_data", Context.MODE_PRIVATE)
-        mockDataDirectory.listFiles()!!.forEach { assertThat(it.deleteRecursively()).isTrue() }
-    }
-
-    @Before
-    fun mockApexEnvironment() {
-        initMocks(this)
-        mockitoSession = mockitoSession()
-            .mockStatic(ApexEnvironment::class.java)
-            .strictness(Strictness.LENIENT)
-            .startMocking()
-        `when`(ApexEnvironment.getApexEnvironment(eq(APEX_MODULE_NAME))).thenReturn(apexEnvironment)
-        `when`(apexEnvironment.getDeviceProtectedDataDirForUser(any(UserHandle::class.java))).then {
-            File(mockDataDirectory, it.arguments[0].toString()).also { it.mkdirs() }
-        }
-    }
-
-    @After
-    fun finishMockingApexEnvironment() {
-        mockitoSession.finishMocking()
-    }
-
-    @Test
-    fun testReadWrite() {
-        persistence.writeForUser(state, user)
-        val persistedState = persistence.readForUser(user)
-
-        assertThat(persistedState).isEqualTo(state)
-        assertThat(persistedState!!.version).isEqualTo(state.version)
-        assertThat(persistedState.fingerprint).isEqualTo(state.fingerprint)
-        assertThat(persistedState.packagePermissions).isEqualTo(state.packagePermissions)
-        val persistedPermissionState = persistedState.packagePermissions.values.first().first()
-        assertThat(persistedPermissionState.name).isEqualTo(permissionState.name)
-        assertThat(persistedPermissionState.isGranted).isEqualTo(permissionState.isGranted)
-        assertThat(persistedPermissionState.flags).isEqualTo(permissionState.flags)
-        assertThat(persistedState.sharedUserPermissions).isEqualTo(state.sharedUserPermissions)
-    }
-
-    @Test
-    fun testDelete() {
-        persistence.writeForUser(state, user)
-        persistence.deleteForUser(user)
-        val persistedState = persistence.readForUser(user)
-
-        assertThat(persistedState).isNull()
-    }
-
-    companion object {
-        private const val APEX_MODULE_NAME = "com.android.permission"
-    }
-}
diff --git a/apex/permission/tests/java/com/android/role/persistence/RolesPersistenceTest.kt b/apex/permission/tests/java/com/android/role/persistence/RolesPersistenceTest.kt
deleted file mode 100644
index f9d9d5a..0000000
--- a/apex/permission/tests/java/com/android/role/persistence/RolesPersistenceTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2020 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.role.persistence
-
-import android.content.ApexEnvironment
-import android.content.Context
-import android.os.Process
-import android.os.UserHandle
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations.initMocks
-import org.mockito.MockitoSession
-import org.mockito.quality.Strictness
-import java.io.File
-
-@RunWith(AndroidJUnit4::class)
-class RolesPersistenceTest {
-    private val context = InstrumentationRegistry.getInstrumentation().context
-
-    private lateinit var mockDataDirectory: File
-
-    private lateinit var mockitoSession: MockitoSession
-    @Mock
-    lateinit var apexEnvironment: ApexEnvironment
-
-    private val persistence = RolesPersistence.createInstance()
-    private val state = RolesState(1, "packagesHash", mapOf("role" to setOf("holder1", "holder2")))
-    private val user = Process.myUserHandle()
-
-    @Before
-    fun createMockDataDirectory() {
-        mockDataDirectory = context.getDir("mock_data", Context.MODE_PRIVATE)
-        mockDataDirectory.listFiles()!!.forEach { assertThat(it.deleteRecursively()).isTrue() }
-    }
-
-    @Before
-    fun mockApexEnvironment() {
-        initMocks(this)
-        mockitoSession = mockitoSession()
-            .mockStatic(ApexEnvironment::class.java)
-            .strictness(Strictness.LENIENT)
-            .startMocking()
-        `when`(ApexEnvironment.getApexEnvironment(eq(APEX_MODULE_NAME))).thenReturn(apexEnvironment)
-        `when`(apexEnvironment.getDeviceProtectedDataDirForUser(any(UserHandle::class.java))).then {
-            File(mockDataDirectory, it.arguments[0].toString()).also { it.mkdirs() }
-        }
-    }
-
-    @After
-    fun finishMockingApexEnvironment() {
-        mockitoSession.finishMocking()
-    }
-
-    @Test
-    fun testReadWrite() {
-        persistence.writeForUser(state, user)
-        val persistedState = persistence.readForUser(user)
-
-        assertThat(persistedState).isEqualTo(state)
-        assertThat(persistedState!!.version).isEqualTo(state.version)
-        assertThat(persistedState.packagesHash).isEqualTo(state.packagesHash)
-        assertThat(persistedState.roles).isEqualTo(state.roles)
-    }
-
-    @Test
-    fun testDelete() {
-        persistence.writeForUser(state, user)
-        persistence.deleteForUser(user)
-        val persistedState = persistence.readForUser(user)
-
-        assertThat(persistedState).isNull()
-    }
-
-    companion object {
-        private const val APEX_MODULE_NAME = "com.android.permission"
-    }
-}
diff --git a/boot/Android.bp b/boot/Android.bp
new file mode 100644
index 0000000..dd4066a
--- /dev/null
+++ b/boot/Android.bp
@@ -0,0 +1,18 @@
+// Copyright (C) 2021 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.
+
+boot_image {
+    name: "framework-boot-image",
+    image_name: "boot",
+}
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 5f13a5c..f85e30d 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -70,6 +70,7 @@
     private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
     private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
     private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls";
+    private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
 
     /**
      * Change the system dialer package name if a package name was specified,
@@ -220,6 +221,9 @@
             case COMMAND_CLEANUP_STUCK_CALLS:
                 runCleanupStuckCalls();
                 break;
+            case COMMAND_RESET_CAR_MODE:
+                runResetCarMode();
+                break;
             case COMMAND_SET_DEFAULT_DIALER:
                 runSetDefaultDialer();
                 break;
@@ -345,6 +349,10 @@
         mTelecomService.cleanupStuckCalls();
     }
 
+    private void runResetCarMode() throws RemoteException {
+        mTelecomService.resetCarMode();
+    }
+
     private void runSetDefaultDialer() throws RemoteException {
         String packageName = nextArg();
         if ("default".equals(packageName)) packageName = null;
diff --git a/core/api/current.txt b/core/api/current.txt
index e8174c0..f5c67c1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -157,7 +157,7 @@
     field public static final String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
     field public static final String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
     field public static final String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
-    field public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
+    field @Deprecated public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
     field public static final String START_VIEW_PERMISSION_USAGE = "android.permission.START_VIEW_PERMISSION_USAGE";
     field public static final String STATUS_BAR = "android.permission.STATUS_BAR";
     field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
@@ -657,6 +657,7 @@
     field @Deprecated public static final int fontProviderCerts = 16844125; // 0x101055d
     field @Deprecated public static final int fontProviderPackage = 16844119; // 0x1010557
     field @Deprecated public static final int fontProviderQuery = 16844113; // 0x1010551
+    field public static final int fontProviderSystemFontFamily = 16844322; // 0x1010622
     field public static final int fontStyle = 16844095; // 0x101053f
     field public static final int fontVariationSettings = 16844144; // 0x1010570
     field public static final int fontWeight = 16844083; // 0x1010533
@@ -721,6 +722,7 @@
     field public static final int gwpAsanMode = 16844310; // 0x1010616
     field public static final int hand_hour = 16843011; // 0x1010103
     field public static final int hand_minute = 16843012; // 0x1010104
+    field public static final int hand_second = 16844323; // 0x1010623
     field public static final int handle = 16843354; // 0x101025a
     field public static final int handleProfiling = 16842786; // 0x1010022
     field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
@@ -962,6 +964,7 @@
     field public static final int measureWithLargestChild = 16843476; // 0x10102d4
     field public static final int mediaRouteButtonStyle = 16843693; // 0x10103ad
     field public static final int mediaRouteTypes = 16843694; // 0x10103ae
+    field public static final int memtagMode = 16844324; // 0x1010624
     field public static final int menuCategory = 16843230; // 0x10101de
     field public static final int mimeGroup = 16844309; // 0x1010615
     field public static final int mimeType = 16842790; // 0x1010026
@@ -985,6 +988,7 @@
     field public static final int multiArch = 16843918; // 0x101048e
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
+    field public static final int nativeHeapZeroInit = 16844325; // 0x1010625
     field public static final int navigationBarColor = 16843858; // 0x1010452
     field public static final int navigationBarDividerColor = 16844141; // 0x101056d
     field public static final int navigationContentDescription = 16843969; // 0x10104c1
@@ -1607,6 +1611,8 @@
     field public static final int windowAnimationStyle = 16842926; // 0x10100ae
     field public static final int windowBackground = 16842836; // 0x1010054
     field public static final int windowBackgroundFallback = 16844035; // 0x1010503
+    field public static final int windowBlurBehindEnabled = 16844316; // 0x101061c
+    field public static final int windowBlurBehindRadius = 16844315; // 0x101061b
     field public static final int windowClipToOutline = 16843947; // 0x10104ab
     field public static final int windowCloseOnTouchOutside = 16843611; // 0x101035b
     field public static final int windowContentOverlay = 16842841; // 0x1010059
@@ -6924,6 +6930,7 @@
     method public void addPersistentPreferredActivity(@NonNull android.content.ComponentName, android.content.IntentFilter, @NonNull android.content.ComponentName);
     method public void addUserRestriction(@NonNull android.content.ComponentName, String);
     method public boolean bindDeviceAdminServiceAsUser(@NonNull android.content.ComponentName, android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
+    method public boolean canAdminGrantSensorsPermissions();
     method public void clearApplicationUserData(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener);
     method public void clearCrossProfileIntentFilters(@NonNull android.content.ComponentName);
     method @Deprecated public void clearDeviceOwnerApp(String);
@@ -7221,6 +7228,7 @@
     field public static final String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
     field public static final String EXTRA_PROVISIONING_MAIN_COLOR = "android.app.extra.PROVISIONING_MAIN_COLOR";
     field public static final String EXTRA_PROVISIONING_MODE = "android.app.extra.PROVISIONING_MODE";
+    field public static final String EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT = "android.app.extra.PROVISIONING_PERMISSION_GRANT_OPT_OUT";
     field public static final String EXTRA_PROVISIONING_SERIAL_NUMBER = "android.app.extra.PROVISIONING_SERIAL_NUMBER";
     field public static final String EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS = "android.app.extra.PROVISIONING_SKIP_EDUCATION_SCREENS";
     field public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
@@ -7310,6 +7318,7 @@
     field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2
     field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1
     field public static final int SKIP_SETUP_WIZARD = 1; // 0x1
+    field public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1; // 0x1
     field public static final int WIPE_EUICC = 4; // 0x4
     field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
     field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
@@ -7463,6 +7472,7 @@
 
   public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
     method public int describeContents();
+    method public int getReason();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR;
   }
@@ -7741,7 +7751,6 @@
     method public long getTriggerContentUpdateDelay();
     method @Nullable public android.app.job.JobInfo.TriggerContentUri[] getTriggerContentUris();
     method public boolean isExpedited();
-    method @Deprecated public boolean isForegroundJob();
     method public boolean isImportantWhileForeground();
     method public boolean isPeriodic();
     method public boolean isPersisted();
@@ -7774,7 +7783,6 @@
     method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long, long);
     method @NonNull public android.app.job.JobInfo.Builder setExpedited(boolean);
     method public android.app.job.JobInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
-    method @Deprecated @NonNull public android.app.job.JobInfo.Builder setForeground(boolean);
     method @Deprecated public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -8349,6 +8357,7 @@
     method public android.appwidget.AppWidgetProviderInfo clone();
     method public int describeContents();
     method public final android.os.UserHandle getProfile();
+    method @NonNull public android.content.pm.ActivityInfo getProviderInfo();
     method @Nullable public final String loadDescription(@NonNull android.content.Context);
     method public final android.graphics.drawable.Drawable loadIcon(@NonNull android.content.Context, int);
     method public final String loadLabel(android.content.pm.PackageManager);
@@ -9277,7 +9286,7 @@
     method public boolean getIncludeTxPowerLevel();
     method public android.util.SparseArray<byte[]> getManufacturerSpecificData();
     method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
-    method @Nullable public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
+    method @NonNull public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
     method public java.util.List<android.os.ParcelUuid> getServiceUuids();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR;
@@ -10373,6 +10382,7 @@
     field public static final String LAUNCHER_APPS_SERVICE = "launcherapps";
     field public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
     field public static final String LOCATION_SERVICE = "location";
+    field public static final String MEDIA_COMMUNICATION_SERVICE = "media_communication";
     field public static final String MEDIA_METRICS_SERVICE = "media_metrics";
     field public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
     field public static final String MEDIA_ROUTER_SERVICE = "media_router";
@@ -11635,6 +11645,8 @@
     method public void dump(android.util.Printer, String);
     method public static CharSequence getCategoryTitle(android.content.Context, int);
     method public int getGwpAsanMode();
+    method public int getMemtagMode();
+    method @Nullable public Boolean isNativeHeapZeroInit();
     method public boolean isProfileableByShell();
     method public boolean isResourceOverlay();
     method public boolean isVirtualPreload();
@@ -11684,6 +11696,10 @@
     field public static final int GWP_ASAN_ALWAYS = 1; // 0x1
     field public static final int GWP_ASAN_DEFAULT = -1; // 0xffffffff
     field public static final int GWP_ASAN_NEVER = 0; // 0x0
+    field public static final int MEMTAG_ASYNC = 1; // 0x1
+    field public static final int MEMTAG_DEFAULT = -1; // 0xffffffff
+    field public static final int MEMTAG_OFF = 0; // 0x0
+    field public static final int MEMTAG_SYNC = 2; // 0x2
     field public String appComponentFactory;
     field public String backupAgentName;
     field public int category;
@@ -11866,6 +11882,7 @@
   }
 
   public class LauncherActivityInfo {
+    method @NonNull public android.content.pm.ActivityInfo getActivityInfo();
     method public android.content.pm.ApplicationInfo getApplicationInfo();
     method public android.graphics.drawable.Drawable getBadgedIcon(int);
     method public android.content.ComponentName getComponentName();
@@ -12238,6 +12255,7 @@
     method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo, @Nullable android.content.res.Configuration) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
@@ -12282,7 +12300,7 @@
     method @Deprecated public abstract void removePackageFromPreferred(@NonNull String);
     method public abstract void removePermission(@NonNull String);
     method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
-    method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.IntentSender) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
+    method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
     method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
     method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
@@ -12302,7 +12320,6 @@
     field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
     field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
     field public static final int DONT_KILL_APP = 1; // 0x1
-    field public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
     field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
     field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
     field public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS = "android.software.activities_on_secondary_displays";
@@ -12344,9 +12361,13 @@
     field public static final String FEATURE_GAMEPAD = "android.hardware.gamepad";
     field public static final String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
     field public static final String FEATURE_HOME_SCREEN = "android.software.home_screen";
+    field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE = "android.hardware.identity_credential";
+    field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS = "android.hardware.identity_credential_direct_access";
     field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
     field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
     field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+    field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
+    field public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = "android.hardware.keystore.single_use_key";
     field public static final String FEATURE_LEANBACK = "android.software.leanback";
     field public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
     field public static final String FEATURE_LIVE_TV = "android.software.live_tv";
@@ -12464,6 +12485,7 @@
     field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
     field public static final int PERMISSION_DENIED = -1; // 0xffffffff
     field public static final int PERMISSION_GRANTED = 0; // 0x0
+    field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES";
     field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff
     field public static final int SIGNATURE_MATCH = 0; // 0x0
     field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1
@@ -12483,6 +12505,10 @@
     ctor public PackageManager.NameNotFoundException(String);
   }
 
+  @java.lang.FunctionalInterface public static interface PackageManager.OnChecksumsReadyListener {
+    method public void onChecksumsReady(@NonNull java.util.List<android.content.pm.ApkChecksum>);
+  }
+
   public static final class PackageManager.Property implements android.os.Parcelable {
     method public int describeContents();
     method public boolean getBoolean();
@@ -15004,6 +15030,7 @@
     field public static final int RGB_565 = 4; // 0x4
     field public static final int UNKNOWN = 0; // 0x0
     field public static final int Y8 = 538982489; // 0x20203859
+    field public static final int YCBCR_P010 = 54; // 0x36
     field public static final int YUV_420_888 = 35; // 0x23
     field public static final int YUV_422_888 = 39; // 0x27
     field public static final int YUV_444_888 = 40; // 0x28
@@ -16796,6 +16823,18 @@
 
 package android.hardware {
 
+  public abstract class Battery {
+    ctor public Battery();
+    method @FloatRange(from=-1.0F, to=1.0f) public abstract float getCapacity();
+    method public abstract int getStatus();
+    method public abstract boolean hasBattery();
+    field public static final int STATUS_CHARGING = 2; // 0x2
+    field public static final int STATUS_DISCHARGING = 3; // 0x3
+    field public static final int STATUS_FULL = 5; // 0x5
+    field public static final int STATUS_NOT_CHARGING = 4; // 0x4
+    field public static final int STATUS_UNKNOWN = 1; // 0x1
+  }
+
   @Deprecated public class Camera {
     method @Deprecated public final void addCallbackBuffer(byte[]);
     method @Deprecated public final void autoFocus(android.hardware.Camera.AutoFocusCallback);
@@ -18496,6 +18535,7 @@
   public final class InputManager {
     method public android.view.InputDevice getInputDevice(int);
     method public int[] getInputDeviceIds();
+    method public float getMaximumObscuringOpacityForTouch();
     method public void registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler);
     method public void unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener);
     method @Nullable public android.view.VerifiedInputEvent verifyInputEvent(@NonNull android.view.InputEvent);
@@ -19429,7 +19469,7 @@
   public interface LocationListener {
     method public default void onFlushComplete(int);
     method public void onLocationChanged(@NonNull android.location.Location);
-    method public default void onLocationChanged(@NonNull android.location.LocationResult);
+    method public default void onLocationChanged(@NonNull java.util.List<android.location.Location>);
     method public default void onProviderDisabled(@NonNull String);
     method public default void onProviderEnabled(@NonNull String);
     method @Deprecated public default void onStatusChanged(String, int, android.os.Bundle);
@@ -19515,8 +19555,8 @@
     field public static final String FUSED_PROVIDER = "fused";
     field public static final String GPS_PROVIDER = "gps";
     field public static final String KEY_FLUSH_COMPLETE = "flushComplete";
+    field public static final String KEY_LOCATIONS = "locations";
     field public static final String KEY_LOCATION_CHANGED = "location";
-    field public static final String KEY_LOCATION_RESULT = "locationResult";
     field public static final String KEY_PROVIDER_ENABLED = "providerEnabled";
     field public static final String KEY_PROXIMITY_ENTERING = "entering";
     field @Deprecated public static final String KEY_STATUS_CHANGED = "status";
@@ -19574,18 +19614,6 @@
     method @NonNull public android.location.LocationRequest.Builder setQuality(int);
   }
 
-  public final class LocationResult implements android.os.Parcelable {
-    method @NonNull public java.util.List<android.location.Location> asList();
-    method @NonNull public static android.location.LocationResult create(@NonNull android.location.Location);
-    method @NonNull public static android.location.LocationResult create(@NonNull java.util.List<android.location.Location>);
-    method public int describeContents();
-    method @NonNull public android.location.Location get(@IntRange(from=0) int);
-    method @NonNull public android.location.Location getLastLocation();
-    method @IntRange(from=1) public int size();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.location.LocationResult> CREATOR;
-  }
-
   public interface OnNmeaMessageListener {
     method public void onNmeaMessage(String, long);
   }
@@ -19845,6 +19873,10 @@
     field public static final int ENCODING_IEC61937 = 13; // 0xd
     field public static final int ENCODING_INVALID = 0; // 0x0
     field public static final int ENCODING_MP3 = 9; // 0x9
+    field public static final int ENCODING_MPEGH_BL_L3 = 23; // 0x17
+    field public static final int ENCODING_MPEGH_BL_L4 = 24; // 0x18
+    field public static final int ENCODING_MPEGH_LC_L3 = 25; // 0x19
+    field public static final int ENCODING_MPEGH_LC_L4 = 26; // 0x1a
     field public static final int ENCODING_OPUS = 20; // 0x14
     field public static final int ENCODING_PCM_16BIT = 2; // 0x2
     field public static final int ENCODING_PCM_24BIT_PACKED = 21; // 0x15
@@ -20155,7 +20187,7 @@
   }
 
   public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
-    ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
+    ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method @Deprecated public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
     method protected void finalize();
@@ -20216,7 +20248,7 @@
 
   public static class AudioRecord.Builder {
     ctor public AudioRecord.Builder();
-    method public android.media.AudioRecord build() throws java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public android.media.AudioRecord build() throws java.lang.UnsupportedOperationException;
     method public android.media.AudioRecord.Builder setAudioFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
     method @NonNull public android.media.AudioRecord.Builder setAudioPlaybackCaptureConfig(@NonNull android.media.AudioPlaybackCaptureConfiguration);
     method public android.media.AudioRecord.Builder setAudioSource(int) throws java.lang.IllegalArgumentException;
@@ -21086,7 +21118,9 @@
 
   public static final class MediaCodecInfo.AudioCapabilities {
     method public android.util.Range<java.lang.Integer> getBitrateRange();
+    method @NonNull public android.util.Range<java.lang.Integer>[] getInputChannelCountRanges();
     method public int getMaxInputChannelCount();
+    method public int getMinInputChannelCount();
     method public android.util.Range<java.lang.Integer>[] getSupportedSampleRateRanges();
     method public int[] getSupportedSampleRates();
     method public boolean isSampleRateSupported(int);
@@ -24223,7 +24257,8 @@
     method public void setCallback(@Nullable android.media.session.MediaSession.Callback, @Nullable android.os.Handler);
     method public void setExtras(@Nullable android.os.Bundle);
     method public void setFlags(int);
-    method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent);
+    method public void setMediaButtonBroadcastReceiver(@Nullable android.content.ComponentName);
+    method @Deprecated public void setMediaButtonReceiver(@Nullable android.app.PendingIntent);
     method public void setMetadata(@Nullable android.media.MediaMetadata);
     method public void setPlaybackState(@Nullable android.media.session.PlaybackState);
     method public void setPlaybackToLocal(android.media.AudioAttributes);
@@ -30181,6 +30216,8 @@
     field @Deprecated public static final String RADIO;
     field @Deprecated public static final String SERIAL;
     field @NonNull public static final String SKU;
+    field @NonNull public static final String SOC_MANUFACTURER;
+    field @NonNull public static final String SOC_MODEL;
     field public static final String[] SUPPORTED_32_BIT_ABIS;
     field public static final String[] SUPPORTED_64_BIT_ABIS;
     field public static final String[] SUPPORTED_ABIS;
@@ -31413,6 +31450,7 @@
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(String);
     method public boolean isDemoUser();
+    method public static boolean isHeadlessSystemUserMode();
     method public boolean isManagedProfile();
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
@@ -31839,6 +31877,10 @@
   public final class ImplicitDirectBootViolation extends android.os.strictmode.Violation {
   }
 
+  public final class IncorrectContextUseViolation extends android.os.strictmode.Violation {
+    ctor public IncorrectContextUseViolation(@NonNull String, @NonNull Throwable);
+  }
+
   public class InstanceCountViolation extends android.os.strictmode.Violation {
     method public long getNumberOfInstances();
   }
@@ -31868,6 +31910,8 @@
   }
 
   public final class UnsafeIntentLaunchViolation extends android.os.strictmode.Violation {
+    ctor public UnsafeIntentLaunchViolation(@NonNull android.content.Intent);
+    method @Nullable public android.content.Intent getIntent();
   }
 
   public final class UntaggedSocketViolation extends android.os.strictmode.Violation {
@@ -34938,6 +34982,55 @@
     field public static final String PATH_SETTING_INTENT = "intent";
   }
 
+  public final class SimPhonebookContract {
+    field public static final String AUTHORITY = "com.android.simphonebook";
+    field @NonNull public static final android.net.Uri AUTHORITY_URI;
+  }
+
+  public static final class SimPhonebookContract.ElementaryFiles {
+    method @NonNull public static android.net.Uri getItemUri(int, int);
+    field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-elementary-file";
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file";
+    field @NonNull public static final android.net.Uri CONTENT_URI;
+    field public static final int EF_ADN = 1; // 0x1
+    field public static final int EF_FDN = 2; // 0x2
+    field public static final int EF_SDN = 3; // 0x3
+    field public static final String EF_TYPE = "ef_type";
+    field public static final int EF_UNKNOWN = 0; // 0x0
+    field public static final String MAX_RECORDS = "max_records";
+    field public static final String NAME_MAX_LENGTH = "name_max_length";
+    field public static final String PHONE_NUMBER_MAX_LENGTH = "phone_number_max_length";
+    field public static final String RECORD_COUNT = "record_count";
+    field public static final String SLOT_INDEX = "slot_index";
+    field public static final String SUBSCRIPTION_ID = "subscription_id";
+  }
+
+  public static final class SimPhonebookContract.SimRecords {
+    method @NonNull public static android.net.Uri getContentUri(int, int);
+    method @NonNull public static android.net.Uri getItemUri(int, int, int);
+    method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String);
+    field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2";
+    field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
+    field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type";
+    field public static final String NAME = "name";
+    field public static final String PHONE_NUMBER = "phone_number";
+    field public static final String RECORD_NUMBER = "record_number";
+    field public static final String SUBSCRIPTION_ID = "subscription_id";
+  }
+
+  public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable {
+    ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int);
+    method public int describeContents();
+    method public int getEncodedLength();
+    method public int getMaxEncodedLength();
+    method @NonNull public String getName();
+    method @NonNull public String getSanitizedName();
+    method public boolean isSupportedCharacter(int);
+    method public boolean isValid();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR;
+  }
+
   public class SyncStateContract {
     ctor public SyncStateContract();
   }
@@ -36708,15 +36801,20 @@
   public abstract class IdentityCredential {
     method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair();
     method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException;
+    method @NonNull public byte[] delete(@NonNull byte[]);
     method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]);
     method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification();
     method @NonNull public abstract int[] getAuthenticationDataUsageCount();
     method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain();
     method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException;
+    method @NonNull public byte[] proveOwnership(@NonNull byte[]);
     method public abstract void setAllowUsingExhaustedKeys(boolean);
+    method public void setAllowUsingExpiredKeys(boolean);
     method public abstract void setAvailableAuthenticationKeys(int, int);
     method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException;
-    method public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
+    method @Deprecated public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
+    method public void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull java.time.Instant, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException;
+    method @NonNull public byte[] update(@NonNull android.security.identity.PersonalizationData);
   }
 
   public class IdentityCredentialException extends java.lang.Exception {
@@ -36726,7 +36824,7 @@
 
   public abstract class IdentityCredentialStore {
     method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException;
-    method @Nullable public abstract byte[] deleteCredentialByName(@NonNull String);
+    method @Deprecated @Nullable public abstract byte[] deleteCredentialByName(@NonNull String);
     method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException;
     method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context);
     method @Nullable public static android.security.identity.IdentityCredentialStore getInstance(@NonNull android.content.Context);
@@ -36802,9 +36900,10 @@
 package android.security.keystore {
 
   public class BackendBusyException extends java.security.ProviderException {
-    ctor public BackendBusyException();
-    ctor public BackendBusyException(@NonNull String);
-    ctor public BackendBusyException(@NonNull String, @NonNull Throwable);
+    ctor public BackendBusyException(long);
+    ctor public BackendBusyException(long, @NonNull String);
+    ctor public BackendBusyException(long, @NonNull String, @NonNull Throwable);
+    method public long getBackOffHintMillis();
   }
 
   public class KeyExpiredException extends java.security.InvalidKeyException {
@@ -36828,6 +36927,7 @@
     method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
     method @Nullable public java.util.Date getKeyValidityStart();
     method @NonNull public String getKeystoreAlias();
+    method public int getMaxUsageCount();
     method public int getPurposes();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
@@ -36864,6 +36964,7 @@
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
+    method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean);
@@ -36886,6 +36987,7 @@
     method public String getKeystoreAlias();
     method public int getOrigin();
     method public int getPurposes();
+    method public int getRemainingUsageCount();
     method public int getSecurityLevel();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
@@ -36942,6 +37044,7 @@
     field public static final int ORIGIN_IMPORTED = 2; // 0x2
     field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8
     field public static final int ORIGIN_UNKNOWN = 4; // 0x4
+    field public static final int PURPOSE_AGREE_KEY = 64; // 0x40
     field public static final int PURPOSE_DECRYPT = 2; // 0x2
     field public static final int PURPOSE_ENCRYPT = 1; // 0x1
     field public static final int PURPOSE_SIGN = 4; // 0x4
@@ -36954,6 +37057,7 @@
     field public static final int SECURITY_LEVEL_UNKNOWN_SECURE = -1; // 0xffffffff
     field public static final String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1";
     field public static final String SIGNATURE_PADDING_RSA_PSS = "PSS";
+    field public static final int UNRESTRICTED_USAGE_COUNT = -1; // 0xffffffff
   }
 
   public final class KeyProtection implements java.security.KeyStore.ProtectionParameter {
@@ -36963,6 +37067,7 @@
     method @Nullable public java.util.Date getKeyValidityForConsumptionEnd();
     method @Nullable public java.util.Date getKeyValidityForOriginationEnd();
     method @Nullable public java.util.Date getKeyValidityStart();
+    method public int getMaxUsageCount();
     method public int getPurposes();
     method @NonNull public String[] getSignaturePaddings();
     method public int getUserAuthenticationType();
@@ -36989,6 +37094,7 @@
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForConsumptionEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
     method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date);
+    method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int);
     method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean);
     method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...);
     method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean);
@@ -38555,6 +38661,7 @@
 
   public class SpeechRecognizer {
     method public void cancel();
+    method @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context);
     method public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context);
     method public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName);
     method public void destroy();
@@ -39692,7 +39799,6 @@
     field public static final String EXTRA_HAS_PICTURE = "android.telecom.extra.HAS_PICTURE";
     field public static final String EXTRA_INCOMING_CALL_ADDRESS = "android.telecom.extra.INCOMING_CALL_ADDRESS";
     field public static final String EXTRA_INCOMING_CALL_EXTRAS = "android.telecom.extra.INCOMING_CALL_EXTRAS";
-    field public static final String EXTRA_INCOMING_PICTURE = "android.telecom.extra.INCOMING_PICTURE";
     field public static final String EXTRA_INCOMING_VIDEO_STATE = "android.telecom.extra.INCOMING_VIDEO_STATE";
     field public static final String EXTRA_IS_DEFAULT_CALL_SCREENING_APP = "android.telecom.extra.IS_DEFAULT_CALL_SCREENING_APP";
     field public static final String EXTRA_LOCATION = "android.telecom.extra.LOCATION";
@@ -39701,6 +39807,7 @@
     field public static final String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
     field public static final String EXTRA_OUTGOING_PICTURE = "android.telecom.extra.OUTGOING_PICTURE";
     field public static final String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
+    field public static final String EXTRA_PICTURE_URI = "android.telecom.extra.PICTURE_URI";
     field public static final String EXTRA_PRIORITY = "android.telecom.extra.PRIORITY";
     field public static final String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT";
     field public static final String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
@@ -40208,6 +40315,7 @@
     field public static final String KEY_RTT_DOWNGRADE_SUPPORTED_BOOL = "rtt_downgrade_supported_bool";
     field public static final String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool";
     field public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool";
+    field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool";
     field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool";
     field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
     field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
@@ -40289,6 +40397,7 @@
     field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool";
     field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool";
     field public static final String KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL = "ims.ims_single_registration_required_bool";
+    field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int";
     field public static final String KEY_PREFIX = "ims.";
     field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool";
     field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int";
@@ -45688,6 +45797,8 @@
     method public void clear();
     method public android.util.SparseArray<E> clone();
     method public boolean contains(int);
+    method public boolean contentEquals(@Nullable android.util.SparseArray<E>);
+    method public int contentHashCode();
     method public void delete(int);
     method public E get(int);
     method public E get(int, E);
@@ -46098,6 +46209,7 @@
     method @Deprecated public void getRectSize(android.graphics.Rect);
     method public float getRefreshRate();
     method public int getRotation();
+    method @Nullable public android.view.RoundedCorner getRoundedCorner(int);
     method @Deprecated public void getSize(android.graphics.Point);
     method public int getState();
     method public android.view.Display.Mode[] getSupportedModes();
@@ -46158,6 +46270,7 @@
     method @NonNull public android.graphics.Rect getBoundingRectRight();
     method @NonNull public android.graphics.Rect getBoundingRectTop();
     method @NonNull public java.util.List<android.graphics.Rect> getBoundingRects();
+    method @Nullable public android.graphics.Path getCutoutPath();
     method public int getSafeInsetBottom();
     method public int getSafeInsetLeft();
     method public int getSafeInsetRight();
@@ -46340,6 +46453,7 @@
 
   public final class InputDevice implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public android.hardware.Battery getBattery();
     method public int getControllerNumber();
     method public String getDescriptor();
     method public static android.view.InputDevice getDevice(int);
@@ -46394,6 +46508,7 @@
     field public static final int SOURCE_MOUSE = 8194; // 0x2002
     field public static final int SOURCE_MOUSE_RELATIVE = 131076; // 0x20004
     field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
+    field public static final int SOURCE_SENSOR = 67108864; // 0x4000000
     field public static final int SOURCE_STYLUS = 16386; // 0x4002
     field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
     field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
@@ -47349,6 +47464,20 @@
     field public static final int TYPE_ZOOM_OUT = 1019; // 0x3fb
   }
 
+  public final class RoundedCorner implements android.os.Parcelable {
+    ctor public RoundedCorner(int, int, int, int);
+    method public int describeContents();
+    method @NonNull public android.graphics.Point getCenter();
+    method public int getPosition();
+    method public int getRadius();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.RoundedCorner> CREATOR;
+    field public static final int POSITION_BOTTOM_LEFT = 3; // 0x3
+    field public static final int POSITION_BOTTOM_RIGHT = 2; // 0x2
+    field public static final int POSITION_TOP_LEFT = 0; // 0x0
+    field public static final int POSITION_TOP_RIGHT = 1; // 0x1
+  }
+
   public class ScaleGestureDetector {
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener);
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener, android.os.Handler);
@@ -49358,6 +49487,7 @@
     method @NonNull public android.graphics.Insets getInsets(int);
     method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
     method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
+    method @Nullable public android.view.RoundedCorner getRoundedCorner(int);
     method @Deprecated public int getStableInsetBottom();
     method @Deprecated public int getStableInsetLeft();
     method @Deprecated public int getStableInsetRight();
@@ -49391,6 +49521,7 @@
     method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
     method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
+    method @NonNull public android.view.WindowInsets.Builder setRoundedCorner(int, @Nullable android.view.RoundedCorner);
     method @Deprecated @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets);
     method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets);
     method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemWindowInsets(@NonNull android.graphics.Insets);
@@ -49543,6 +49674,7 @@
     field public static final int FLAGS_CHANGED = 4; // 0x4
     field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
     field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
+    field public static final int FLAG_BLUR_BEHIND = 4; // 0x4
     field public static final int FLAG_DIM_BEHIND = 2; // 0x2
     field @Deprecated public static final int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
     field @Deprecated public static final int FLAG_DITHER = 4096; // 0x1000
@@ -49633,6 +49765,7 @@
     field @Deprecated public static final int TYPE_TOAST = 2005; // 0x7d5
     field public static final int TYPE_WALLPAPER = 2013; // 0x7dd
     field public float alpha;
+    field public int blurBehindRadius;
     field public float buttonBrightness;
     field public float dimAmount;
     field public int flags;
@@ -50942,7 +51075,7 @@
     method public boolean sendKeyEvent(android.view.KeyEvent);
     method public boolean setComposingRegion(int, int);
     method public boolean setComposingText(CharSequence, int);
-    method public default boolean setImeTemporarilyConsumesInput(boolean);
+    method public default boolean setImeConsumesInput(boolean);
     method public boolean setSelection(int, int);
     field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1
     field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
@@ -53035,6 +53168,12 @@
     ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet);
     ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int);
     ctor @Deprecated public AnalogClock(android.content.Context, android.util.AttributeSet, int, int);
+    method @Deprecated @Nullable public String getTimeZone();
+    method @Deprecated public void setDial(@NonNull android.graphics.drawable.Icon);
+    method @Deprecated public void setHourHand(@NonNull android.graphics.drawable.Icon);
+    method @Deprecated public void setMinuteHand(@NonNull android.graphics.drawable.Icon);
+    method @Deprecated public void setSecondHand(@Nullable android.graphics.drawable.Icon);
+    method @Deprecated public void setTimeZone(@Nullable String);
   }
 
   public class ArrayAdapter<T> extends android.widget.BaseAdapter implements android.widget.Filterable android.widget.ThemedSpinnerAdapter {
@@ -53212,7 +53351,7 @@
     method public void onSelectedDayChange(@NonNull android.widget.CalendarView, int, int, int);
   }
 
-  public class CheckBox extends android.widget.CompoundButton {
+  @android.widget.RemoteViews.RemoteView public class CheckBox extends android.widget.CompoundButton {
     ctor public CheckBox(android.content.Context);
     ctor public CheckBox(android.content.Context, android.util.AttributeSet);
     ctor public CheckBox(android.content.Context, android.util.AttributeSet, int);
@@ -53278,6 +53417,7 @@
     method public boolean isChecked();
     method public void setButtonDrawable(@DrawableRes int);
     method public void setButtonDrawable(@Nullable android.graphics.drawable.Drawable);
+    method public void setButtonIcon(@Nullable android.graphics.drawable.Icon);
     method public void setButtonTintBlendMode(@Nullable android.graphics.BlendMode);
     method public void setButtonTintList(@Nullable android.content.res.ColorStateList);
     method public void setButtonTintMode(@Nullable android.graphics.PorterDuff.Mode);
@@ -54318,14 +54458,14 @@
     field protected String[] mExcludeMimes;
   }
 
-  public class RadioButton extends android.widget.CompoundButton {
+  @android.widget.RemoteViews.RemoteView public class RadioButton extends android.widget.CompoundButton {
     ctor public RadioButton(android.content.Context);
     ctor public RadioButton(android.content.Context, android.util.AttributeSet);
     ctor public RadioButton(android.content.Context, android.util.AttributeSet, int);
     ctor public RadioButton(android.content.Context, android.util.AttributeSet, int, int);
   }
 
-  public class RadioGroup extends android.widget.LinearLayout {
+  @android.widget.RemoteViews.RemoteView public class RadioGroup extends android.widget.LinearLayout {
     ctor public RadioGroup(android.content.Context);
     ctor public RadioGroup(android.content.Context, android.util.AttributeSet);
     method public void check(@IdRes int);
@@ -54442,19 +54582,27 @@
     method public void setByte(@IdRes int, String, byte);
     method public void setChar(@IdRes int, String, char);
     method public void setCharSequence(@IdRes int, String, CharSequence);
+    method public void setCharSequence(@IdRes int, @NonNull String, @StringRes int);
     method public void setChronometer(@IdRes int, long, String, boolean);
     method public void setChronometerCountDown(@IdRes int, boolean);
+    method public void setColor(@IdRes int, @NonNull String, @ColorRes int);
+    method public void setColorStateList(@IdRes int, @NonNull String, @ColorRes int);
+    method public void setCompoundButtonChecked(@IdRes int, boolean);
     method public void setContentDescription(@IdRes int, CharSequence);
     method public void setDisplayedChild(@IdRes int, int);
     method public void setDouble(@IdRes int, String, double);
     method public void setEmptyView(@IdRes int, @IdRes int);
     method public void setFloat(@IdRes int, String, float);
+    method public void setFloatDimen(@IdRes int, @NonNull String, @DimenRes int);
+    method public void setFloatDimen(@IdRes int, @NonNull String, float, int);
     method public void setIcon(@IdRes int, String, android.graphics.drawable.Icon);
     method public void setImageViewBitmap(@IdRes int, android.graphics.Bitmap);
     method public void setImageViewIcon(@IdRes int, android.graphics.drawable.Icon);
     method public void setImageViewResource(@IdRes int, @DrawableRes int);
     method public void setImageViewUri(@IdRes int, android.net.Uri);
     method public void setInt(@IdRes int, String, int);
+    method public void setIntDimen(@IdRes int, @NonNull String, @DimenRes int);
+    method public void setIntDimen(@IdRes int, @NonNull String, float, int);
     method public void setIntent(@IdRes int, String, android.content.Intent);
     method public void setLabelFor(@IdRes int, @IdRes int);
     method public void setLightBackgroundLayoutId(@LayoutRes int);
@@ -54464,6 +54612,7 @@
     method public void setOnClickResponse(@IdRes int, @NonNull android.widget.RemoteViews.RemoteResponse);
     method public void setPendingIntentTemplate(@IdRes int, android.app.PendingIntent);
     method public void setProgressBar(@IdRes int, int, int, boolean);
+    method public void setRadioGroupChecked(@IdRes int, @IdRes int);
     method public void setRelativeScrollPosition(@IdRes int, int);
     method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
     method public void setRemoteAdapter(@IdRes int, android.content.Intent);
@@ -54830,7 +54979,7 @@
     ctor public StackView(android.content.Context, android.util.AttributeSet, int, int);
   }
 
-  public class Switch extends android.widget.CompoundButton {
+  @android.widget.RemoteViews.RemoteView public class Switch extends android.widget.CompoundButton {
     ctor public Switch(android.content.Context);
     ctor public Switch(android.content.Context, android.util.AttributeSet);
     ctor public Switch(android.content.Context, android.util.AttributeSet, int);
@@ -54861,12 +55010,14 @@
     method public void setTextOff(CharSequence);
     method public void setTextOn(CharSequence);
     method public void setThumbDrawable(android.graphics.drawable.Drawable);
+    method public void setThumbIcon(@Nullable android.graphics.drawable.Icon);
     method public void setThumbResource(@DrawableRes int);
     method public void setThumbTextPadding(int);
     method public void setThumbTintBlendMode(@Nullable android.graphics.BlendMode);
     method public void setThumbTintList(@Nullable android.content.res.ColorStateList);
     method public void setThumbTintMode(@Nullable android.graphics.PorterDuff.Mode);
     method public void setTrackDrawable(android.graphics.drawable.Drawable);
+    method public void setTrackIcon(@Nullable android.graphics.drawable.Icon);
     method public void setTrackResource(@DrawableRes int);
     method public void setTrackTintBlendMode(@Nullable android.graphics.BlendMode);
     method public void setTrackTintList(@Nullable android.content.res.ColorStateList);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index cb2810c..bf70803 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -103,6 +103,7 @@
   }
 
   public class MediaServiceManager {
+    method @NonNull public android.media.MediaServiceManager.ServiceRegisterer getMediaCommunicationServiceRegisterer();
     method @NonNull public android.media.MediaServiceManager.ServiceRegisterer getMediaSessionServiceRegisterer();
     method @NonNull public android.media.MediaServiceManager.ServiceRegisterer getMediaTranscodingServiceRegisterer();
   }
@@ -156,14 +157,35 @@
 
 package android.net {
 
+  public final class ConnectivityFrameworkInitializer {
+    method public static void registerServiceWrappers();
+  }
+
   public class ConnectivityManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
   }
 
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public int getResourceId();
+  }
+
+  public final class NetworkAgentConfig implements android.os.Parcelable {
+    method @Nullable public String getSubscriberId();
+  }
+
+  public static final class NetworkAgentConfig.Builder {
+    method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
+  }
+
   public final class NetworkCapabilities implements android.os.Parcelable {
     field public static final int TRANSPORT_TEST = 7; // 0x7
   }
 
+  public final class Proxy {
+    method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo);
+  }
+
   public final class TcpRepairWindow {
     ctor public TcpRepairWindow(int, int, int, int, int, int);
     field public final int maxWindow;
@@ -190,6 +212,16 @@
     method public void teardownTestNetwork(@NonNull android.net.Network);
   }
 
+  public final class UnderlyingNetworkInfo implements android.os.Parcelable {
+    ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>);
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.UnderlyingNetworkInfo> CREATOR;
+    field @NonNull public final String iface;
+    field public final int ownerUid;
+    field @NonNull public final java.util.List<java.lang.String> underlyingIfaces;
+  }
+
 }
 
 package android.os {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d34f956..cf4be05 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -34,6 +34,7 @@
     field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE";
     field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE";
     field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
+    field public static final String BIND_DOMAIN_VERIFICATION_AGENT = "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
     field public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
     field public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission.BIND_EXTERNAL_STORAGE_SERVICE";
     field public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE";
@@ -86,6 +87,7 @@
     field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
     field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
     field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
+    field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
     field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
     field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
     field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
@@ -138,6 +140,7 @@
     field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER";
     field public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
     field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
+    field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
     field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
     field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS";
     field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS";
@@ -217,6 +220,7 @@
     field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+    field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM";
     field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
     field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
     field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
@@ -256,12 +260,12 @@
     field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
     field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
     field public static final String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
+    field public static final String UPDATE_DOMAIN_VERIFICATION_USER_SELECTION = "android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION";
     field public static final String UPDATE_FONTS = "android.permission.UPDATE_FONTS";
     field public static final String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
     field public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
-    field public static final String USE_BACKGROUND_BLUR = "android.permission.USE_BACKGROUND_BLUR";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
@@ -286,6 +290,7 @@
 
   public static final class R.attr {
     field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
+    field public static final int hotwordDetectionService = 16844326; // 0x1010626
     field public static final int isVrOnly = 16844152; // 0x1010578
     field public static final int minExtensionVersion = 16844305; // 0x1010611
     field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
@@ -293,8 +298,6 @@
     field public static final int sdkVersion = 16844304; // 0x1010610
     field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
-    field public static final int windowBackgroundBlurEnabled = 16844316; // 0x101061c
-    field public static final int windowBackgroundBlurRadius = 16844315; // 0x101061b
   }
 
   public static final class R.bool {
@@ -630,8 +633,6 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
     method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(int, long);
     method public android.os.Bundle toBundle();
-    field public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0
-    field public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1
   }
 
   public class DownloadManager {
@@ -869,6 +870,7 @@
   }
 
   public class DevicePolicyManager {
+    method public boolean canAdminGrantSensorsPermissionsForUser(int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -1492,6 +1494,169 @@
 
 }
 
+package android.app.smartspace {
+
+  public final class SmartspaceAction implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public CharSequence getContentDescription();
+    method @Nullable public android.os.Bundle getExtras();
+    method @Nullable public android.graphics.drawable.Icon getIcon();
+    method @NonNull public String getId();
+    method @Nullable public android.content.Intent getIntent();
+    method @Nullable public android.app.PendingIntent getPendingIntent();
+    method @Nullable public CharSequence getSubtitle();
+    method @NonNull public CharSequence getTitle();
+    method @Nullable public android.os.UserHandle getUserHandle();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceAction> CREATOR;
+  }
+
+  public static final class SmartspaceAction.Builder {
+    ctor public SmartspaceAction.Builder(@NonNull String, @NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceAction build();
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setContentDescription(@Nullable CharSequence);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setExtras(@Nullable android.os.Bundle);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIcon(@Nullable android.graphics.drawable.Icon);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setIntent(@Nullable android.content.Intent);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setSubtitle(@Nullable CharSequence);
+    method @NonNull public android.app.smartspace.SmartspaceAction.Builder setUserHandle(@Nullable android.os.UserHandle);
+  }
+
+  public final class SmartspaceConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.os.Bundle getExtras();
+    method @NonNull public String getPackageName();
+    method @NonNull public int getSmartspaceTargetCount();
+    method @NonNull public String getUiSurface();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceConfig> CREATOR;
+  }
+
+  public static final class SmartspaceConfig.Builder {
+    ctor public SmartspaceConfig.Builder(@NonNull android.content.Context, @NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceConfig build();
+    method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setExtras(@NonNull android.os.Bundle);
+    method @NonNull public android.app.smartspace.SmartspaceConfig.Builder setSmartspaceTargetCount(int);
+  }
+
+  public final class SmartspaceManager {
+    method @NonNull public android.app.smartspace.SmartspaceSession createSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig);
+  }
+
+  public final class SmartspaceSession implements java.lang.AutoCloseable {
+    method public void close();
+    method public void destroy();
+    method protected void finalize();
+    method public void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceTargetEvent);
+    method public void registerSmartspaceUpdates(@NonNull java.util.concurrent.Executor, @NonNull android.app.smartspace.SmartspaceSession.Callback);
+    method public void requestSmartspaceUpdate();
+    method public void unregisterSmartspaceUpdates(@NonNull android.app.smartspace.SmartspaceSession.Callback);
+  }
+
+  public static interface SmartspaceSession.Callback {
+    method public void onTargetsAvailable(@NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+  }
+
+  public final class SmartspaceSessionId implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getId();
+    method @NonNull public int getUserId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceSessionId> CREATOR;
+  }
+
+  public final class SmartspaceTarget implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getActionChips();
+    method @Nullable public String getAssociatedSmartspaceTargetId();
+    method @Nullable public android.app.smartspace.SmartspaceAction getBaseAction();
+    method @NonNull public android.content.ComponentName getComponentName();
+    method @NonNull public long getCreationTimeMillis();
+    method @NonNull public long getExpiryTimeMillis();
+    method @NonNull public int getFeatureType();
+    method @Nullable public android.app.smartspace.SmartspaceAction getHeaderAction();
+    method @NonNull public java.util.List<android.app.smartspace.SmartspaceAction> getIconGrid();
+    method @NonNull public float getScore();
+    method @Nullable public android.net.Uri getSliceUri();
+    method @NonNull public String getSmartspaceTargetId();
+    method @Nullable public String getSourceNotificationKey();
+    method @NonNull public android.os.UserHandle getUserHandle();
+    method @Nullable public android.appwidget.AppWidgetProviderInfo getWidgetId();
+    method @NonNull public boolean isSensitive();
+    method @NonNull public boolean shouldShowExpanded();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTarget> CREATOR;
+    field public static final int FEATURE_ALARM = 7; // 0x7
+    field public static final int FEATURE_BEDTIME_ROUTINE = 16; // 0x10
+    field public static final int FEATURE_CALENDAR = 2; // 0x2
+    field public static final int FEATURE_COMMUTE_TIME = 3; // 0x3
+    field public static final int FEATURE_CONSENT = 11; // 0xb
+    field public static final int FEATURE_ETA_MONITORING = 18; // 0x12
+    field public static final int FEATURE_FITNESS_TRACKING = 17; // 0x11
+    field public static final int FEATURE_FLIGHT = 4; // 0x4
+    field public static final int FEATURE_LOYALTY_CARD = 14; // 0xe
+    field public static final int FEATURE_MEDIA = 15; // 0xf
+    field public static final int FEATURE_MISSED_CALL = 19; // 0x13
+    field public static final int FEATURE_ONBOARDING = 8; // 0x8
+    field public static final int FEATURE_PACKAGE_TRACKING = 20; // 0x14
+    field public static final int FEATURE_REMINDER = 6; // 0x6
+    field public static final int FEATURE_SHOPPING_LIST = 13; // 0xd
+    field public static final int FEATURE_SPORTS = 9; // 0x9
+    field public static final int FEATURE_STOCK_PRICE_CHANGE = 12; // 0xc
+    field public static final int FEATURE_STOPWATCH = 22; // 0x16
+    field public static final int FEATURE_TIMER = 21; // 0x15
+    field public static final int FEATURE_TIPS = 5; // 0x5
+    field public static final int FEATURE_UNDEFINED = 0; // 0x0
+    field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17
+    field public static final int FEATURE_WEATHER = 1; // 0x1
+    field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa
+  }
+
+  public static final class SmartspaceTarget.Builder {
+    ctor public SmartspaceTarget.Builder(@NonNull String, @NonNull android.content.ComponentName, @NonNull android.os.UserHandle);
+    method @NonNull public android.app.smartspace.SmartspaceTarget build();
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setActionChips(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setAssociatedSmartspaceTargetId(@NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setBaseAction(@NonNull android.app.smartspace.SmartspaceAction);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setCreationTimeMillis(@NonNull long);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setExpiryTimeMillis(@NonNull long);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setFeatureType(@NonNull int);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setHeaderAction(@NonNull android.app.smartspace.SmartspaceAction);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setIconGrid(@NonNull java.util.List<android.app.smartspace.SmartspaceAction>);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setScore(@NonNull float);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSensitive(@NonNull boolean);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(@NonNull boolean);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidgetId(@NonNull android.appwidget.AppWidgetProviderInfo);
+  }
+
+  public final class SmartspaceTargetEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public int getEventType();
+    method @Nullable public String getSmartspaceActionId();
+    method @Nullable public android.app.smartspace.SmartspaceTarget getSmartspaceTarget();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.SmartspaceTargetEvent> CREATOR;
+    field public static final int EVENT_TARGET_BLOCK = 5; // 0x5
+    field public static final int EVENT_TARGET_DISMISS = 4; // 0x4
+    field public static final int EVENT_TARGET_INTERACTION = 1; // 0x1
+    field public static final int EVENT_TARGET_IN_VIEW = 2; // 0x2
+    field public static final int EVENT_TARGET_OUT_OF_VIEW = 3; // 0x3
+    field public static final int EVENT_UI_SURFACE_IN_VIEW = 6; // 0x6
+    field public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7; // 0x7
+  }
+
+  public static final class SmartspaceTargetEvent.Builder {
+    ctor public SmartspaceTargetEvent.Builder(int);
+    method @NonNull public android.app.smartspace.SmartspaceTargetEvent build();
+    method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceActionId(@NonNull String);
+    method @NonNull public android.app.smartspace.SmartspaceTargetEvent.Builder setSmartspaceTarget(@NonNull android.app.smartspace.SmartspaceTarget);
+  }
+
+}
+
 package android.app.time {
 
   public final class TimeManager {
@@ -1624,6 +1789,17 @@
 
 }
 
+package android.apphibernation {
+
+  public final class AppHibernationManager {
+    method public boolean isHibernatingForUser(@NonNull String);
+    method public boolean isHibernatingGlobally(@NonNull String);
+    method public void setHibernatingForUser(@NonNull String, boolean);
+    method public void setHibernatingGlobally(@NonNull String, boolean);
+  }
+
+}
+
 package android.bluetooth {
 
   public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile {
@@ -1920,6 +2096,7 @@
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
     method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+    field public static final String APP_HIBERNATION_SERVICE = "app_hibernation";
     field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
     field public static final String APP_PREDICTION_SERVICE = "app_prediction";
     field public static final String BACKUP_SERVICE = "backup";
@@ -1928,6 +2105,7 @@
     field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
     field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
+    field public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
     field public static final String ETHERNET_SERVICE = "ethernet";
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
     field public static final String FONT_SERVICE = "font";
@@ -1942,6 +2120,7 @@
     field public static final String ROLLBACK_SERVICE = "rollback";
     field public static final String SEARCH_UI_SERVICE = "search_ui";
     field public static final String SECURE_ELEMENT_SERVICE = "secure_element";
+    field public static final String SMARTSPACE_SERVICE = "smartspace";
     field public static final String STATS_MANAGER = "stats";
     field public static final String STATUS_BAR_SERVICE = "statusbar";
     field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
@@ -1970,12 +2149,13 @@
     field public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
     field public static final String ACTION_DEVICE_CUSTOMIZATION_READY = "android.intent.action.DEVICE_CUSTOMIZATION_READY";
     field public static final String ACTION_DIAL_EMERGENCY = "android.intent.action.DIAL_EMERGENCY";
+    field public static final String ACTION_DOMAINS_NEED_VERIFICATION = "android.intent.action.DOMAINS_NEED_VERIFICATION";
     field public static final String ACTION_FACTORY_RESET = "android.intent.action.FACTORY_RESET";
     field public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
     field public static final String ACTION_INCIDENT_REPORT_READY = "android.intent.action.INCIDENT_REPORT_READY";
     field public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE";
     field public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
-    field public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+    field @Deprecated public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
     field public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA";
     field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_MANAGE_APP_PERMISSION = "android.intent.action.MANAGE_APP_PERMISSION";
     field public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
@@ -2308,8 +2488,8 @@
     method @Nullable public abstract android.content.ComponentName getInstantAppInstallerComponent();
     method @Nullable public abstract android.content.ComponentName getInstantAppResolverSettingsComponent();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
-    method @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
     method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
@@ -2333,9 +2513,9 @@
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
     method public void setSystemAppState(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
-    method @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
-    method @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
     field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -2345,6 +2525,7 @@
     field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
     field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
+    field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
     field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
     field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000
@@ -2359,6 +2540,7 @@
     field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40
     field public static final int FLAG_PERMISSION_REVOKED_COMPAT = 8; // 0x8
     field @Deprecated public static final int FLAG_PERMISSION_REVOKE_ON_UPGRADE = 8; // 0x8
+    field public static final int FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY = 524288; // 0x80000
     field public static final int FLAG_PERMISSION_SYSTEM_FIXED = 16; // 0x10
     field public static final int FLAG_PERMISSION_USER_FIXED = 2; // 0x2
     field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 512; // 0x200
@@ -2401,13 +2583,13 @@
     field public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; // 0xffffff99
     field public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; // 0xffffff9a
     field public static final int INSTALL_SUCCEEDED = 1; // 0x1
-    field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
-    field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
-    field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; // 0x1
-    field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; // 0x3
-    field public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; // 0x0
-    field public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff
-    field public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
+    field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2; // 0x2
+    field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4; // 0x4
+    field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1; // 0x1
+    field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3; // 0x3
+    field @Deprecated public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0; // 0x0
+    field @Deprecated public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1; // 0xffffffff
+    field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
     field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
@@ -2535,6 +2717,52 @@
 
 }
 
+package android.content.pm.verify.domain {
+
+  public final class DomainVerificationInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
+    method @NonNull public java.util.UUID getIdentifier();
+    method @NonNull public String getPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR;
+  }
+
+  public interface DomainVerificationManager {
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames();
+    method public static boolean isStateModifiable(int);
+    method public static boolean isStateVerified(int);
+    method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
+    field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
+    field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400
+    field public static final int STATE_NO_RESPONSE = 0; // 0x0
+    field public static final int STATE_SUCCESS = 1; // 0x1
+  }
+
+  public final class DomainVerificationRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Set<java.lang.String> getPackageNames();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationRequest> CREATOR;
+  }
+
+  public final class DomainVerificationUserSelection implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.Map<java.lang.String,java.lang.Boolean> getHostToUserSelectionMap();
+    method @NonNull public java.util.UUID getIdentifier();
+    method @NonNull public String getPackageName();
+    method @NonNull public android.os.UserHandle getUser();
+    method @NonNull public boolean isLinkHandlingAllowed();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR;
+  }
+
+}
+
 package android.content.rollback {
 
   public final class PackageRollbackInfo implements android.os.Parcelable {
@@ -2580,22 +2808,21 @@
 
 }
 
-package android.graphics.drawable {
-
-  public final class BackgroundBlurDrawable extends android.graphics.drawable.Drawable {
-    ctor @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public BackgroundBlurDrawable();
-    method public void setBlurRadius(int);
-    method public void setColor(@ColorInt int);
-    method public void setCornerRadius(float);
-    method public void setCornerRadius(float, float, float, float);
-  }
-
-}
-
 package android.graphics.fonts {
 
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
+    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
+    field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
+    field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
+    field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
+    field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
+    field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
 }
@@ -2685,6 +2912,9 @@
     field public final boolean nightMode;
     field public final String packageName;
     field public final float powerBrightnessFactor;
+    field public final boolean reduceBrightColors;
+    field public final float reduceBrightColorsOffset;
+    field public final int reduceBrightColorsStrength;
     field public final long timeStamp;
   }
 
@@ -3158,6 +3388,7 @@
 
   public class ContextHubClientCallback {
     ctor public ContextHubClientCallback();
+    method public void onClientAuthorizationChanged(@NonNull android.hardware.location.ContextHubClient, long, int);
     method public void onHubReset(android.hardware.location.ContextHubClient);
     method public void onMessageFromNanoApp(android.hardware.location.ContextHubClient, android.hardware.location.NanoAppMessage);
     method public void onNanoAppAborted(android.hardware.location.ContextHubClient, long, int);
@@ -3194,6 +3425,7 @@
 
   public class ContextHubIntentEvent {
     method @NonNull public static android.hardware.location.ContextHubIntentEvent fromIntent(@NonNull android.content.Intent);
+    method public int getClientAuthorizationState();
     method @NonNull public android.hardware.location.ContextHubInfo getContextHubInfo();
     method public int getEventType();
     method public int getNanoAppAbortCode();
@@ -3202,8 +3434,10 @@
   }
 
   public final class ContextHubManager {
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@Nullable android.content.Context, @NonNull android.hardware.location.ContextHubInfo, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.location.ContextHubClientCallback);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.ContextHubClientCallback, @NonNull java.util.concurrent.Executor);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.ContextHubClientCallback);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@Nullable android.content.Context, @NonNull android.hardware.location.ContextHubInfo, @NonNull android.app.PendingIntent, long);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubClient createClient(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.app.PendingIntent, long);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> disableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> enableNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
@@ -3221,6 +3455,10 @@
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public int unloadNanoApp(int);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_CONTEXT_HUB}) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
     method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
+    field public static final int AUTHORIZATION_DENIED = 0; // 0x0
+    field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
+    field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
+    field public static final int EVENT_CLIENT_AUTHORIZATION = 7; // 0x7
     field public static final int EVENT_HUB_RESET = 6; // 0x6
     field public static final int EVENT_NANOAPP_ABORTED = 4; // 0x4
     field public static final int EVENT_NANOAPP_DISABLED = 3; // 0x3
@@ -3228,6 +3466,7 @@
     field public static final int EVENT_NANOAPP_LOADED = 0; // 0x0
     field public static final int EVENT_NANOAPP_MESSAGE = 5; // 0x5
     field public static final int EVENT_NANOAPP_UNLOADED = 1; // 0x1
+    field public static final String EXTRA_CLIENT_AUTHORIZATION_STATE = "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE";
     field public static final String EXTRA_CONTEXT_HUB_INFO = "android.hardware.location.extra.CONTEXT_HUB_INFO";
     field public static final String EXTRA_EVENT_TYPE = "android.hardware.location.extra.EVENT_TYPE";
     field public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE";
@@ -3466,8 +3705,10 @@
 
   public final class NanoAppState implements android.os.Parcelable {
     ctor public NanoAppState(long, int, boolean);
+    ctor public NanoAppState(long, int, boolean, @NonNull java.util.List<java.lang.String>);
     method public int describeContents();
     method public long getNanoAppId();
+    method @NonNull public java.util.List<java.lang.String> getNanoAppPermissions();
     method public long getNanoAppVersion();
     method public boolean isEnabled();
     method public void writeToParcel(android.os.Parcel, int);
@@ -4479,10 +4720,6 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
   }
 
-  public final class LocationResult implements android.os.Parcelable {
-    method @NonNull public static android.location.LocationResult wrap(@NonNull android.location.Location);
-  }
-
   public final class SatellitePvt implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.location.SatellitePvt.ClockInfo getClockInfo();
@@ -4549,7 +4786,7 @@
     method public abstract void onSendExtraCommand(@NonNull String, @Nullable android.os.Bundle);
     method public abstract void onSetRequest(@NonNull android.location.provider.ProviderRequest);
     method public void reportLocation(@NonNull android.location.Location);
-    method public void reportLocation(@NonNull android.location.LocationResult);
+    method public void reportLocations(@NonNull java.util.List<android.location.Location>);
     method public void setAllowed(boolean);
     method public void setProperties(@NonNull android.location.provider.ProviderProperties);
     field public static final String ACTION_FUSED_PROVIDER = "com.android.location.service.FusedLocationProvider";
@@ -4610,6 +4847,7 @@
   public static class AudioAttributes.Builder {
     method public android.media.AudioAttributes.Builder addBundle(@NonNull android.os.Bundle);
     method public android.media.AudioAttributes.Builder setCapturePreset(int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public android.media.AudioAttributes.Builder setHotwordMode();
     method public android.media.AudioAttributes.Builder setInternalCapturePreset(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioAttributes.Builder setSystemUsage(int);
   }
@@ -4751,7 +4989,7 @@
   }
 
   public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection {
-    ctor public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
+    ctor @RequiresPermission(android.Manifest.permission.RECORD_AUDIO) public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
   }
 
   public static class AudioRecord.Builder {
@@ -5595,12 +5833,12 @@
   public class Filter implements java.lang.AutoCloseable {
     method public void close();
     method public int configure(@NonNull android.media.tv.tuner.filter.FilterConfiguration);
-    method public int configureMonitorEvent(int);
     method public int flush();
     method public int getId();
     method public long getId64Bit();
     method public int read(@NonNull byte[], long, long);
     method public int setDataSource(@Nullable android.media.tv.tuner.filter.Filter);
+    method public int setMonitorEventMask(int);
     method public int start();
     method public int stop();
     field public static final int MONITOR_EVENT_IP_CID_CHANGE = 2; // 0x2
@@ -5792,6 +6030,7 @@
 
   public final class RestartEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public int getStartId();
+    field public static final int NEW_FILTER_FIRST_START_ID = 0; // 0x0
   }
 
   public final class ScramblingStatusEvent extends android.media.tv.tuner.filter.FilterEvent {
@@ -7079,6 +7318,7 @@
     method @Nullable public String getSsid();
     method @NonNull public int[] getTransportTypes();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
+    field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c
     field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
     field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a
     field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18
@@ -7750,11 +7990,13 @@
     method public boolean setupInterfaceForSoftApMode(@NonNull String);
     method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.SignalPollResult signalPoll(@NonNull String);
     method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.nl80211.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.PnoScanRequestCallback);
-    method public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>);
+    method @Deprecated public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>);
+    method public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>, @Nullable android.os.Bundle);
     method public boolean stopPnoScan(@NonNull String);
     method public boolean tearDownClientInterface(@NonNull String);
     method public boolean tearDownInterfaces();
     method public boolean tearDownSoftApInterface(@NonNull String);
+    field public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR";
     field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1
     field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0
     field public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5; // 0x5
@@ -8271,6 +8513,8 @@
     field public static final int EVENT_MMS = 2; // 0x2
     field public static final int EVENT_SMS = 1; // 0x1
     field public static final int EVENT_UNSPECIFIED = 0; // 0x0
+    field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0
+    field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1
   }
 
   public class RecoverySystem {
@@ -8400,6 +8644,7 @@
     method @NonNull public static String formatUid(int);
     method public static int getAppId(int);
     method public int getIdentifier();
+    method public static int getUid(@NonNull android.os.UserHandle, int);
     method @Deprecated public boolean isOwner();
     method public boolean isSystem();
     method public static int myUserId();
@@ -8873,6 +9118,7 @@
     field @Deprecated public static final String NAMESPACE_STORAGE = "storage";
     field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
     field public static final String NAMESPACE_SYSTEMUI = "systemui";
+    field public static final String NAMESPACE_SYSTEM_TIME = "system_time";
     field public static final String NAMESPACE_TELEPHONY = "telephony";
     field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
     field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
@@ -9120,6 +9366,24 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
   }
 
+  public final class SimPhonebookContract {
+    method @NonNull public static String getEfUriPath(int);
+    field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
+  }
+
+  public static final class SimPhonebookContract.ElementaryFiles {
+    field public static final String EF_ADN_PATH_SEGMENT = "adn";
+    field public static final String EF_FDN_PATH_SEGMENT = "fdn";
+    field public static final String EF_SDN_PATH_SEGMENT = "sdn";
+    field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
+  }
+
+  public static final class SimPhonebookContract.SimRecords {
+    field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT";
+    field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2";
+    field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
+  }
+
   public static final class Telephony.Carriers implements android.provider.BaseColumns {
     field public static final String APN_SET_ID = "apn_set_id";
     field public static final int CARRIER_EDITED = 4; // 0x4
@@ -9248,6 +9512,11 @@
     method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int);
   }
 
+  public abstract class KeyProperties {
+    field public static final int NAMESPACE_APPLICATION = -1; // 0xffffffff
+    field public static final int NAMESPACE_WIFI = 102; // 0x66
+  }
+
 }
 
 package android.security.keystore.recovery {
@@ -9412,29 +9681,6 @@
 
 }
 
-package android.service.attestation {
-
-  public abstract class ImpressionAttestationService extends android.app.Service {
-    ctor public ImpressionAttestationService();
-    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @Nullable public abstract android.service.attestation.ImpressionToken onGenerateImpressionToken(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String);
-    method public abstract boolean onVerifyImpressionToken(@NonNull byte[], @NonNull android.service.attestation.ImpressionToken);
-  }
-
-  public final class ImpressionToken implements android.os.Parcelable {
-    ctor public ImpressionToken(long, @NonNull android.graphics.Rect, @NonNull String, @NonNull byte[], @NonNull byte[]);
-    method public int describeContents();
-    method @NonNull public android.graphics.Rect getBoundsInWindow();
-    method @NonNull public String getHashingAlgorithm();
-    method @NonNull public byte[] getHmac();
-    method @NonNull public byte[] getImageHash();
-    method public long getScreenshotTimeMillis();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.service.attestation.ImpressionToken> CREATOR;
-  }
-
-}
-
 package android.service.autofill {
 
   public abstract class AutofillFieldClassificationService extends android.app.Service {
@@ -9822,6 +10068,7 @@
     method public void onNotificationDirectReplied(@NonNull String);
     method @Nullable public abstract android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification);
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
+    method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel, @NonNull android.service.notification.NotificationListenerService.RankingMap);
     method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
     method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
     method public void onNotificationVisibilityChanged(@NonNull String, boolean);
@@ -9999,6 +10246,30 @@
 
 }
 
+package android.service.screenshot {
+
+  public final class ScreenshotHash implements android.os.Parcelable {
+    ctor public ScreenshotHash(long, @NonNull android.graphics.Rect, @NonNull String, @NonNull byte[], @NonNull byte[]);
+    method public int describeContents();
+    method @NonNull public android.graphics.Rect getBoundsInWindow();
+    method @NonNull public String getHashingAlgorithm();
+    method @NonNull public byte[] getHmac();
+    method @NonNull public byte[] getImageHash();
+    method public long getScreenshotTimeMillis();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.screenshot.ScreenshotHash> CREATOR;
+  }
+
+  public abstract class ScreenshotHasherService extends android.app.Service {
+    ctor public ScreenshotHasherService();
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method @Nullable public abstract android.service.screenshot.ScreenshotHash onGenerateScreenshotHash(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String);
+    method public abstract boolean onVerifyScreenshotHash(@NonNull byte[], @NonNull android.service.screenshot.ScreenshotHash);
+    field public static final String SERVICE_INTERFACE = "android.service.screenshot.ScreenshotHasherService";
+  }
+
+}
+
 package android.service.search {
 
   public abstract class SearchUiService extends android.app.Service {
@@ -10047,13 +10318,28 @@
 
 }
 
+package android.service.smartspace {
+
+  public abstract class SmartspaceService extends android.app.Service {
+    ctor public SmartspaceService();
+    method @MainThread public abstract void notifySmartspaceEvent(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull android.app.smartspace.SmartspaceTargetEvent);
+    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onCreateSmartspaceSession(@NonNull android.app.smartspace.SmartspaceConfig, @NonNull android.app.smartspace.SmartspaceSessionId);
+    method @MainThread public abstract void onDestroy(@NonNull android.app.smartspace.SmartspaceSessionId);
+    method public abstract void onDestroySmartspaceSession(@NonNull android.app.smartspace.SmartspaceSessionId);
+    method @MainThread public abstract void onRequestSmartspaceUpdate(@NonNull android.app.smartspace.SmartspaceSessionId);
+    method public final void updateSmartspaceTargets(@NonNull android.app.smartspace.SmartspaceSessionId, @NonNull java.util.List<android.app.smartspace.SmartspaceTarget>);
+  }
+
+}
+
 package android.service.storage {
 
   public abstract class ExternalStorageService extends android.app.Service {
     ctor public ExternalStorageService();
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onEndSession(@NonNull String) throws java.io.IOException;
-    method public void onFreeCacheRequested(@NonNull java.util.UUID, long);
+    method public void onFreeCache(@NonNull java.util.UUID, long) throws java.io.IOException;
     method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException;
     method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException;
     field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2
@@ -11426,6 +11712,7 @@
     method public boolean canManageSubscription(@NonNull android.telephony.SubscriptionInfo, @NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getActiveSubscriptionIdList();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public byte[] getAllSimSpecificSettingsForBackup();
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getCompleteActiveSubscriptionIdList();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
@@ -11433,6 +11720,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
     method public void requestEmbeddedSubscriptionInfoListRefresh();
     method public void requestEmbeddedSubscriptionInfoListRefresh(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void restoreAllSimSpecificSettingsFromBackup(@NonNull byte[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultDataSubId(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultSmsSubId(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
@@ -11598,7 +11886,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult setIccLockEnabled(boolean, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabledStatus(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMobileDataPolicyEnabled(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
     method public int setNrDualConnectivityState(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
@@ -12956,11 +13244,100 @@
     field public static final String RCS_PROFILE_2_3 = "UP_2.3";
   }
 
+  public final class RcsContactPresenceTuple implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.Uri getContactUri();
+    method @Nullable public android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities getServiceCapabilities();
+    method @Nullable public String getServiceDescription();
+    method @NonNull public String getServiceId();
+    method @NonNull public String getServiceVersion();
+    method @NonNull public String getStatus();
+    method @Nullable public String getTimestamp();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactPresenceTuple> CREATOR;
+    field public static final String SERVICE_ID_CALL_COMPOSER = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callcomposer";
+    field public static final String SERVICE_ID_CHATBOT = "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.chatbot";
+    field public static final String SERVICE_ID_CHATBOT_ROLE = "org.gsma.rcs.isbot";
+    field public static final String SERVICE_ID_CHATBOT_STANDALONE = " org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.chatbot.sa";
+    field public static final String SERVICE_ID_CHAT_V1 = "org.openmobilealliance:IM-session";
+    field public static final String SERVICE_ID_CHAT_V2 = "org.openmobilealliance:ChatSession";
+    field public static final String SERVICE_ID_FT = "org.openmobilealliance:File-Transfer-HTTP";
+    field public static final String SERVICE_ID_FT_OVER_SMS = "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.ftsms";
+    field public static final String SERVICE_ID_GEO_PUSH = "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.geopush";
+    field public static final String SERVICE_ID_GEO_PUSH_VIA_SMS = "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.geosms";
+    field public static final String SERVICE_ID_MMTEL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.mmtel";
+    field public static final String SERVICE_ID_POST_CALL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callunanswered";
+    field public static final String SERVICE_ID_SHARED_MAP = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.sharedmap";
+    field public static final String SERVICE_ID_SHARED_SKETCH = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.sharedsketch";
+    field public static final String TUPLE_BASIC_STATUS_CLOSED = "closed";
+    field public static final String TUPLE_BASIC_STATUS_OPEN = "open";
+  }
+
+  public static final class RcsContactPresenceTuple.Builder {
+    ctor public RcsContactPresenceTuple.Builder(@NonNull String, @NonNull String, @NonNull String);
+    method @NonNull public android.telephony.ims.RcsContactPresenceTuple build();
+    method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setContactUri(@NonNull android.net.Uri);
+    method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setServiceCapabilities(@NonNull android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities);
+    method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setServiceDescription(@NonNull String);
+    method @NonNull public android.telephony.ims.RcsContactPresenceTuple.Builder setTimestamp(@NonNull String);
+  }
+
+  public static final class RcsContactPresenceTuple.ServiceCapabilities implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getSupportedDuplexModes();
+    method @NonNull public java.util.List<java.lang.String> getUnsupportedDuplexModes();
+    method public boolean isAudioCapable();
+    method public boolean isVideoCapable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities> CREATOR;
+    field public static final String DUPLEX_MODE_FULL = "full";
+    field public static final String DUPLEX_MODE_HALF = "half";
+    field public static final String DUPLEX_MODE_RECEIVE_ONLY = "receive-only";
+    field public static final String DUPLEX_MODE_SEND_ONLY = "send-only";
+  }
+
+  public static final class RcsContactPresenceTuple.ServiceCapabilities.Builder {
+    ctor public RcsContactPresenceTuple.ServiceCapabilities.Builder(boolean, boolean);
+    method @NonNull public android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities.Builder addSupportedDuplexMode(@NonNull String);
+    method @NonNull public android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities.Builder addUnsupportedDuplexMode(@NonNull String);
+    method @NonNull public android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities build();
+  }
+
+  public final class RcsContactUceCapability implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getCapabilityMechanism();
+    method @Nullable public android.telephony.ims.RcsContactPresenceTuple getCapabilityTuple(@NonNull String);
+    method @NonNull public java.util.List<android.telephony.ims.RcsContactPresenceTuple> getCapabilityTuples();
+    method @NonNull public android.net.Uri getContactUri();
+    method public int getRequestResult();
+    method public int getSourceType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CAPABILITY_MECHANISM_OPTIONS = 2; // 0x2
+    field public static final int CAPABILITY_MECHANISM_PRESENCE = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
+    field public static final int REQUEST_RESULT_FOUND = 3; // 0x3
+    field public static final int REQUEST_RESULT_NOT_FOUND = 2; // 0x2
+    field public static final int REQUEST_RESULT_NOT_ONLINE = 1; // 0x1
+    field public static final int REQUEST_RESULT_UNKNOWN = 0; // 0x0
+    field public static final int SOURCE_TYPE_CACHED = 1; // 0x1
+    field public static final int SOURCE_TYPE_NETWORK = 0; // 0x0
+  }
+
+  public static final class RcsContactUceCapability.PresenceBuilder {
+    ctor public RcsContactUceCapability.PresenceBuilder(@NonNull android.net.Uri, int, int);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder addCapabilityTuple(@NonNull android.telephony.ims.RcsContactPresenceTuple);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.PresenceBuilder addCapabilityTuples(@NonNull java.util.List<android.telephony.ims.RcsContactPresenceTuple>);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+  }
+
   public class RcsUceAdapter {
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addOnPublishStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getUcePublishState() throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnPublishStateChangedListener(@NonNull android.telephony.ims.RcsUceAdapter.OnPublishStateChangedListener) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestAvailability(@NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void requestCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RcsUceAdapter.CapabilitiesCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
+    field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2
     field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 7; // 0x7
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 6; // 0x6
@@ -12973,6 +13350,18 @@
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11; // 0xb
     field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN = 8; // 0x8
     field public static final int CAPABILITY_UPDATE_TRIGGER_UNKNOWN = 0; // 0x0
+    field public static final int ERROR_FORBIDDEN = 6; // 0x6
+    field public static final int ERROR_GENERIC_FAILURE = 1; // 0x1
+    field public static final int ERROR_INSUFFICIENT_MEMORY = 10; // 0xa
+    field public static final int ERROR_LOST_NETWORK = 11; // 0xb
+    field public static final int ERROR_NOT_AUTHORIZED = 5; // 0x5
+    field public static final int ERROR_NOT_AVAILABLE = 3; // 0x3
+    field public static final int ERROR_NOT_ENABLED = 2; // 0x2
+    field public static final int ERROR_NOT_FOUND = 7; // 0x7
+    field public static final int ERROR_NOT_REGISTERED = 4; // 0x4
+    field public static final int ERROR_REQUEST_TIMEOUT = 9; // 0x9
+    field public static final int ERROR_REQUEST_TOO_LARGE = 8; // 0x8
+    field public static final int ERROR_SERVER_UNAVAILABLE = 12; // 0xc
     field public static final int PUBLISH_STATE_NOT_PUBLISHED = 2; // 0x2
     field public static final int PUBLISH_STATE_OK = 1; // 0x1
     field public static final int PUBLISH_STATE_OTHER_ERROR = 6; // 0x6
@@ -12981,6 +13370,12 @@
     field public static final int PUBLISH_STATE_VOICE_PROVISION_ERROR = 3; // 0x3
   }
 
+  public static interface RcsUceAdapter.CapabilitiesCallback {
+    method public void onCapabilitiesReceived(@NonNull java.util.List<android.telephony.ims.RcsContactUceCapability>);
+    method public void onComplete();
+    method public void onError(int, long);
+  }
+
   public static interface RcsUceAdapter.OnPublishStateChangedListener {
     method public void onPublishStateChange(int);
   }
@@ -13024,6 +13419,7 @@
     field public static final String IPTYPE_IPV6 = "IPV6";
     field public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = "sip_config_auhentication_header_string";
     field public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = "sip_config_authentication_nonce_string";
+    field public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING = "sip_config_cellular_network_info_header_string";
     field public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string";
     field public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string";
     field public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string";
@@ -13056,6 +13452,7 @@
     field public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = "sip_config_ue_public_port_with_nat_int";
     field public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = "sip_config_ue_public_user_id_string";
     field public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = "sip_config_uri_user_part_string";
+    field public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING = "sip_config_sip_user_agent_header_string";
     field public static final String SIP_TRANSPORT_TCP = "TCP";
     field public static final String SIP_TRANSPORT_UDP = "UDP";
   }
@@ -13392,6 +13789,7 @@
   public class RcsCapabilityExchangeImplBase {
     ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor);
     method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback);
+    method public void subscribeForCapabilities(@NonNull java.util.List<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback);
     field public static final int COMMAND_CODE_FETCH_ERROR = 3; // 0x3
     field public static final int COMMAND_CODE_GENERIC_FAILURE = 1; // 0x1
     field public static final int COMMAND_CODE_INSUFFICIENT_MEMORY = 5; // 0x5
@@ -13408,6 +13806,16 @@
   public static interface RcsCapabilityExchangeImplBase.PublishResponseCallback {
     method public void onCommandError(int) throws android.telephony.ims.ImsException;
     method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException;
+    method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String, @IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException;
+  }
+
+  public static interface RcsCapabilityExchangeImplBase.SubscribeResponseCallback {
+    method public void onCommandError(int) throws android.telephony.ims.ImsException;
+    method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException;
+    method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String, @IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException;
+    method public void onNotifyCapabilitiesUpdate(@NonNull java.util.List<java.lang.String>) throws android.telephony.ims.ImsException;
+    method public void onResourceTerminated(@NonNull java.util.List<android.util.Pair<android.net.Uri,java.lang.String>>) throws android.telephony.ims.ImsException;
+    method public void onTerminated(@NonNull String, long) throws android.telephony.ims.ImsException;
   }
 
   public interface SipDelegate {
@@ -13534,7 +13942,9 @@
   public final class FontConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<android.text.FontConfig.Alias> getAliases();
+    method @IntRange(from=0) public int getConfigVersion();
     method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFontFamilies();
+    method public long getLastModifiedTimeMillis();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
   }
@@ -13759,10 +14169,8 @@
     method public boolean isSystemApplicationOverlay();
     method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean);
     method public final void setUserActivityTimeout(long);
-    field @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public static final int FLAG_BLUR_BEHIND = 4; // 0x4
     field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
     field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10
-    field @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR) public int backgroundBlurRadius;
   }
 
   @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
@@ -14032,6 +14440,7 @@
     method public String getDataDirectorySuffix();
     method public String getErrorString(android.content.Context, int);
     method public int getPackageId(android.content.res.Resources, String);
+    method @NonNull public long[] getTimestamps();
     method @Deprecated public void invokeDrawGlFunctor(android.view.View, long, boolean);
     method public boolean isMultiProcessEnabled();
     method public boolean isTraceTagEnabled();
@@ -14047,6 +14456,12 @@
     method public static android.content.pm.PackageInfo getLoadedPackageInfo();
     method public static int loadWebViewNativeLibraryFromPackage(String, ClassLoader);
     method public static void prepareWebViewInZygote();
+    field public static final int ADD_ASSETS_END = 4; // 0x4
+    field public static final int ADD_ASSETS_START = 3; // 0x3
+    field public static final int CREATE_CONTEXT_END = 2; // 0x2
+    field public static final int CREATE_CONTEXT_START = 1; // 0x1
+    field public static final int GET_CLASS_LOADER_END = 6; // 0x6
+    field public static final int GET_CLASS_LOADER_START = 5; // 0x5
     field public static final int LIBLOAD_ADDRESS_SPACE_NOT_RESERVED = 2; // 0x2
     field public static final int LIBLOAD_FAILED_JNI_CALL = 7; // 0x7
     field public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4
@@ -14057,6 +14472,11 @@
     field public static final int LIBLOAD_FAILED_WAITING_FOR_WEBVIEW_REASON_UNKNOWN = 8; // 0x8
     field public static final int LIBLOAD_SUCCESS = 0; // 0x0
     field public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1; // 0x1
+    field public static final int NATIVE_LOAD_END = 8; // 0x8
+    field public static final int NATIVE_LOAD_START = 7; // 0x7
+    field public static final int PROVIDER_CLASS_FOR_NAME_END = 10; // 0xa
+    field public static final int PROVIDER_CLASS_FOR_NAME_START = 9; // 0x9
+    field public static final int WEBVIEW_LOAD_START = 0; // 0x0
   }
 
   public interface WebViewFactoryProvider {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 8b3cee4..58f6c52 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -6,9 +6,11 @@
 BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex):
     Builder methods names should use setFoo() / addFoo() / clearFoo() style: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex)
 
+
 ExecutorRegistration: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
     Registration methods should have overload that accepts delivery Executor: `setOnRtpRxNoticeListener`
-    
+
+
 GenericException: android.app.prediction.AppPredictor#finalize():
     
 GenericException: android.hardware.location.ContextHubClient#finalize():
@@ -21,6 +23,8 @@
 
 IntentBuilderName: android.app.search.SearchAction#getIntent():
     
+IntentBuilderName: android.app.smartspace.SmartspaceAction#getIntent():
+    Methods creating an Intent should be named `create<Foo>Intent()`, was `getIntent`
 
 
 KotlinKeyword: android.app.Notification#when:
@@ -83,6 +87,10 @@
     
 
 
+OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent):
+    Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent`
+
+
 ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
     
 ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
@@ -187,11 +195,10 @@
     
 SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     
-SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
-    SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-    
 SamShouldBeLast: android.media.AudioTrack#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     
+SamShouldBeLast: android.media.MediaPlayer#setOnRtpRxNoticeListener(android.content.Context, android.media.MediaPlayer.OnRtpRxNoticeListener, android.os.Handler):
+    SAM-compatible parameters (such as parameter 2, "listener", in android.media.MediaPlayer.setOnRtpRxNoticeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
     
 SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
@@ -258,3 +265,5 @@
     Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
 UserHandleName: android.app.search.SearchTarget.Builder#setUserHandle(android.os.UserHandle):
     
+UserHandleName: android.app.smartspace.SmartspaceAction.Builder#setUserHandle(android.os.UserHandle):
+    Method taking UserHandle should be named `doFooAsUser` or `queryFooForUser`, was `setUserHandle`
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c237d7d..e39b2b8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -12,6 +12,7 @@
     field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
     field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
     field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
+    field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE";
     field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
     field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
     field public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
@@ -23,10 +24,12 @@
     field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
     field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
     field public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS";
+    field public static final String QUERY_USERS = "android.permission.QUERY_USERS";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
+    field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
@@ -118,6 +121,7 @@
 
   public class ActivityOptions {
     method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
+    method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
     method public static void setExitTransitionTimeout(long);
     method public void setLaunchActivityType(int);
     method public void setLaunchTaskId(int);
@@ -281,6 +285,7 @@
   }
 
   public final class PendingIntent implements android.os.Parcelable {
+    method @Nullable @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
     field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000
   }
 
@@ -388,10 +393,10 @@
     method public boolean isFactoryResetProtectionPolicySupported();
     method @NonNull public static String operationToString(int);
     method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
-    method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, boolean);
+    method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int);
+    method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
+    method @NonNull public static String unsafeOperationReasonToString(int);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
-    field public static final String ACTION_MANAGED_PROFILE_CREATED = "android.app.action.MANAGED_PROFILE_CREATED";
-    field public static final String ACTION_PROVISIONED_MANAGED_DEVICE = "android.app.action.PROVISIONED_MANAGED_DEVICE";
     field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
     field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
     field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
@@ -400,10 +405,10 @@
     field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
     field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
     field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
-    field public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
+    field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
     field public static final int CODE_OK = 0; // 0x0
     field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
-    field public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
+    field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
     field public static final int CODE_SYSTEM_USER = 10; // 0xa
     field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
     field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
@@ -455,9 +460,11 @@
     field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
     field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
     field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
+    field public static final int UNSAFE_OPERATION_REASON_NONE = -1; // 0xffffffff
   }
 
   public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
+    method public boolean canDeviceOwnerGrantSensorsPermissions();
     method public int describeContents();
     method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
     method public long getLocalTime();
@@ -472,6 +479,7 @@
   public static final class FullyManagedDeviceProvisioningParams.Builder {
     ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setDeviceOwnerCanGrantSensorsPermissions(boolean);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
     method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocale(@Nullable java.util.Locale);
@@ -511,6 +519,7 @@
   }
 
   public final class UnsafeStateException extends java.lang.IllegalStateException implements android.os.Parcelable {
+    ctor public UnsafeStateException(int, int);
     method public int getOperation();
   }
 
@@ -684,6 +693,7 @@
     method public void holdLock(android.os.IBinder, int);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
+    field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
     field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
     field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000
     field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services";
@@ -824,6 +834,17 @@
 
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
+    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
+    field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
+    field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
+    field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
+    field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
+    field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
+    field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
+    field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
+    field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
+    field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
 }
@@ -882,6 +903,40 @@
 
 }
 
+package android.hardware.devicestate {
+
+  public final class DeviceStateManager {
+    method public void addDeviceStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
+    method @NonNull public int[] getSupportedStates();
+    method public void removeDeviceStateListener(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateListener);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+  }
+
+  public static interface DeviceStateManager.DeviceStateListener {
+    method public void onDeviceStateChanged(int);
+  }
+
+  public final class DeviceStateRequest {
+    method public int getFlags();
+    method public int getState();
+    method @NonNull public static android.hardware.devicestate.DeviceStateRequest.Builder newBuilder(int);
+    field public static final int FLAG_CANCEL_WHEN_BASE_CHANGES = 1; // 0x1
+  }
+
+  public static final class DeviceStateRequest.Builder {
+    method @NonNull public android.hardware.devicestate.DeviceStateRequest build();
+    method @NonNull public android.hardware.devicestate.DeviceStateRequest.Builder setFlags(int);
+  }
+
+  public static interface DeviceStateRequest.Callback {
+    method public default void onRequestActivated(@NonNull android.hardware.devicestate.DeviceStateRequest);
+    method public default void onRequestCanceled(@NonNull android.hardware.devicestate.DeviceStateRequest);
+    method public default void onRequestSuspended(@NonNull android.hardware.devicestate.DeviceStateRequest);
+  }
+
+}
+
 package android.hardware.display {
 
   public class AmbientDisplayConfiguration {
@@ -931,9 +986,8 @@
 
   public final class InputManager {
     method public int getBlockUntrustedTouchesMode(@NonNull android.content.Context);
-    method public float getMaximumObscuringOpacityForTouch(@NonNull android.content.Context);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setBlockUntrustedTouchesMode(@NonNull android.content.Context, int);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(@NonNull android.content.Context, float);
+    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(float);
     field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
   }
 
@@ -2031,7 +2085,9 @@
   public final class FontConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public java.util.List<android.text.FontConfig.Alias> getAliases();
+    method @IntRange(from=0) public int getConfigVersion();
     method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFontFamilies();
+    method public long getLastModifiedTimeMillis();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
   }
@@ -2310,6 +2366,8 @@
   }
 
   public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
+    method @Nullable public final android.os.IBinder getWindowContextToken();
+    method public final void setWindowContextToken(@NonNull android.os.IBinder);
     field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
     field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40
     field public CharSequence accessibilityTitle;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 415105f..4728f11 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2669,6 +2669,10 @@
         dispatchActivityDestroyed();
 
         notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
+
+        if (mUiTranslationController != null) {
+            mUiTranslationController.onActivityDestroyed();
+        }
     }
 
     /**
@@ -5086,6 +5090,13 @@
             mTaskDescription.setBackgroundColor(colorBackground);
         }
 
+        int colorBackgroundFloating = a.getColor(
+                com.android.internal.R.styleable.ActivityTaskDescription_colorBackgroundFloating,
+                0);
+        if (colorBackgroundFloating != 0 && Color.alpha(colorBackgroundFloating) == 0xFF) {
+            mTaskDescription.setBackgroundColorFloating(colorBackgroundFloating);
+        }
+
         final int statusBarColor = a.getColor(
                 com.android.internal.R.styleable.ActivityTaskDescription_statusBarColor, 0);
         if (statusBarColor != 0) {
@@ -7877,6 +7888,10 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     final void performCreate(Bundle icicle, PersistableBundle persistentState) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performCreate:"
+                    + mComponent.getClassName());
+        }
         dispatchActivityPreCreated(icicle);
         mCanEnterPictureInPicture = true;
         // initialize mIsInMultiWindowMode and mIsInPictureInPictureMode before onCreate
@@ -7899,6 +7914,7 @@
         mFragments.dispatchActivityCreated();
         mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
         dispatchActivityPostCreated(icicle);
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void performNewIntent(@NonNull Intent intent) {
@@ -8004,6 +8020,10 @@
     }
 
     final void performResume(boolean followedByPause, String reason) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performResume:"
+                    + mComponent.getClassName());
+        }
         dispatchActivityPreResumed();
         performRestart(true /* start */, reason);
 
@@ -8055,9 +8075,14 @@
                 " did not call through to super.onPostResume()");
         }
         dispatchActivityPostResumed();
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void performPause() {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performPause:"
+                    + mComponent.getClassName());
+        }
         dispatchActivityPrePaused();
         mDoReportFullyDrawn = false;
         mFragments.dispatchPause();
@@ -8073,6 +8098,7 @@
                     " did not call through to super.onPause()");
         }
         dispatchActivityPostPaused();
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void performUserLeaving() {
@@ -8081,6 +8107,10 @@
     }
 
     final void performStop(boolean preserveWindow, String reason) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStop:"
+                    + mComponent.getClassName());
+        }
         mDoReportFullyDrawn = false;
         mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
 
@@ -8126,9 +8156,14 @@
             dispatchActivityPostStopped();
         }
         mResumed = false;
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void performDestroy() {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performDestroy:"
+                    + mComponent.getClassName());
+        }
         dispatchActivityPreDestroyed();
         mDestroyed = true;
         mWindow.destroy();
@@ -8141,6 +8176,7 @@
             mVoiceInteractor.detachActivity();
         }
         dispatchActivityPostDestroyed();
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode,
@@ -8450,9 +8486,8 @@
     }
 
     /** @hide */
-    @Override
     @Nullable
-    public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
+    public View findViewByAutofillIdTraversal(@NonNull AutofillId autofillId) {
         final ArrayList<ViewRootImpl> roots =
                 WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
         for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
@@ -8465,12 +8500,18 @@
                 }
             }
         }
-
         return null;
     }
 
     /** @hide */
     @Override
+    @Nullable
+    public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
+        return findViewByAutofillIdTraversal(autofillId);
+    }
+
+    /** @hide */
+    @Override
     public final @NonNull boolean[] autofillClientGetViewVisibility(
             @NonNull AutofillId[] autofillIds) {
         final int autofillIdCount = autofillIds.length;
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index d465b22..401f8cc 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -26,6 +26,8 @@
 import android.util.Singleton;
 import android.view.RemoteAnimationDefinition;
 
+import com.android.internal.policy.IKeyguardDismissCallback;
+
 /**
  * Provides the activity associated operations that communicate with system.
  *
@@ -431,6 +433,37 @@
         }
     }
 
+    /**
+     * Restart the process and activity to adopt the latest configuration for size compat mode.
+     * This only takes effect for visible activity because invisible background activity can be
+     * restarted naturally when it becomes visible.
+     */
+    public void restartActivityProcessIfVisible(IBinder token) {
+        try {
+            getActivityClientController().restartActivityProcessIfVisible(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Removes the snapshot of home task. */
+    public void invalidateHomeTaskSnapshot(IBinder homeToken) {
+        try {
+            getActivityClientController().invalidateHomeTaskSnapshot(homeToken);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback,
+            CharSequence message) {
+        try {
+            getActivityClientController().dismissKeyguard(token, callback, message);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     void registerRemoteAnimations(IBinder token, RemoteAnimationDefinition definition) {
         try {
             getActivityClientController().registerRemoteAnimations(token, definition);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 520959c..43d0269 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1072,13 +1072,15 @@
         private static final String ATTR_TASKDESCRIPTIONCOLOR_PRIMARY =
                 ATTR_TASKDESCRIPTION_PREFIX + "color";
         private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
-                ATTR_TASKDESCRIPTION_PREFIX + "colorBackground";
+                ATTR_TASKDESCRIPTION_PREFIX + "color_background";
         private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
                 ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
         private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE =
                 ATTR_TASKDESCRIPTION_PREFIX + "icon_resource";
         private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE_PACKAGE =
                 ATTR_TASKDESCRIPTION_PREFIX + "icon_package";
+        private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING =
+                ATTR_TASKDESCRIPTION_PREFIX + "color_background_floating";
 
         private String mLabel;
         @Nullable
@@ -1086,6 +1088,7 @@
         private String mIconFilename;
         private int mColorPrimary;
         private int mColorBackground;
+        private int mColorBackgroundFloating;
         private int mStatusBarColor;
         private int mNavigationBarColor;
         private boolean mEnsureStatusBarContrastWhenTransparent;
@@ -1106,7 +1109,7 @@
          */
         public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+                    colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1121,7 +1124,7 @@
          */
         public TaskDescription(String label, @DrawableRes int iconRes) {
             this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
-                    0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+                    0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1130,14 +1133,14 @@
          * @param label A label and description of the current state of this activity.
          */
         public TaskDescription(String label) {
-            this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+            this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
          * Creates an empty TaskDescription.
          */
         public TaskDescription() {
-            this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+            this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /**
@@ -1152,7 +1155,7 @@
         @Deprecated
         public TaskDescription(String label, Bitmap icon, int colorPrimary) {
             this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
-                    false, false, RESIZE_MODE_RESIZEABLE, -1, -1);
+                    false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1168,7 +1171,7 @@
         @Deprecated
         public TaskDescription(String label, Bitmap icon) {
             this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
-                    RESIZE_MODE_RESIZEABLE, -1, -1);
+                    RESIZE_MODE_RESIZEABLE, -1, -1, 0);
         }
 
         /** @hide */
@@ -1177,7 +1180,7 @@
                 int statusBarColor, int navigationBarColor,
                 boolean ensureStatusBarContrastWhenTransparent,
                 boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
-                int minHeight) {
+                int minHeight, int colorBackgroundFloating) {
             mLabel = label;
             mIcon = icon;
             mColorPrimary = colorPrimary;
@@ -1190,6 +1193,7 @@
             mResizeMode = resizeMode;
             mMinWidth = minWidth;
             mMinHeight = minHeight;
+            mColorBackgroundFloating = colorBackgroundFloating;
         }
 
         /**
@@ -1217,6 +1221,7 @@
             mResizeMode = other.mResizeMode;
             mMinWidth = other.mMinWidth;
             mMinHeight = other.mMinHeight;
+            mColorBackgroundFloating = other.mColorBackgroundFloating;
         }
 
         /**
@@ -1253,6 +1258,9 @@
             if (other.mMinHeight != -1) {
                 mMinHeight = other.mMinHeight;
             }
+            if (other.mColorBackgroundFloating != 0) {
+                mColorBackgroundFloating = other.mColorBackgroundFloating;
+            }
         }
 
         private TaskDescription(Parcel source) {
@@ -1292,6 +1300,19 @@
         }
 
         /**
+         * Sets the background color floating for this task description.
+         * @hide
+         */
+        public void setBackgroundColorFloating(int backgroundColor) {
+            // Ensure that the given color is valid
+            if ((backgroundColor != 0) && (Color.alpha(backgroundColor) != 255)) {
+                throw new RuntimeException(
+                        "A TaskDescription's background color floating should be opaque");
+            }
+            mColorBackgroundFloating = backgroundColor;
+        }
+
+        /**
          * @hide
          */
         public void setStatusBarColor(int statusBarColor) {
@@ -1461,6 +1482,14 @@
         }
 
         /**
+         * @return The background color floating.
+         * @hide
+         */
+        public int getBackgroundColorFloating() {
+            return mColorBackgroundFloating;
+        }
+
+        /**
          * @hide
          */
         public int getStatusBarColor() {
@@ -1537,6 +1566,10 @@
             if (mColorBackground != 0) {
                 out.attributeIntHex(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND, mColorBackground);
             }
+            if (mColorBackgroundFloating != 0) {
+                out.attributeIntHex(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING,
+                        mColorBackgroundFloating);
+            }
             if (mIconFilename != null) {
                 out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
             }
@@ -1563,6 +1596,11 @@
             if (colorBackground != 0) {
                 setBackgroundColor(colorBackground);
             }
+            final int colorBackgroundFloating = in.getAttributeIntHex(null,
+                    ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND_FLOATING, 0);
+            if (colorBackgroundFloating != 0) {
+                setBackgroundColorFloating(colorBackgroundFloating);
+            }
             final String iconFilename = in.getAttributeValue(null,
                     ATTR_TASKDESCRIPTIONICON_FILENAME);
             if (iconFilename != null) {
@@ -1615,6 +1653,7 @@
                 dest.writeInt(1);
                 dest.writeString(mIconFilename);
             }
+            dest.writeInt(mColorBackgroundFloating);
         }
 
         public void readFromParcel(Parcel source) {
@@ -1632,6 +1671,7 @@
             mMinWidth = source.readInt();
             mMinHeight = source.readInt();
             mIconFilename = source.readInt() > 0 ? source.readString() : null;
+            mColorBackgroundFloating = source.readInt();
         }
 
         public static final @android.annotation.NonNull Creator<TaskDescription> CREATOR
@@ -1655,7 +1695,8 @@
                     + (mEnsureNavigationBarContrastWhenTransparent
                             ? " (contrast when transparent)" : "")
                     + " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode)
-                    + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight;
+                    + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight
+                    + " colorBackgrounFloating: " + mColorBackgroundFloating;
         }
 
         @Override
@@ -1678,7 +1719,8 @@
                             == other.mEnsureNavigationBarContrastWhenTransparent
                     && mResizeMode == other.mResizeMode
                     && mMinWidth == other.mMinWidth
-                    && mMinHeight == other.mMinHeight;
+                    && mMinHeight == other.mMinHeight
+                    && mColorBackgroundFloating == other.mColorBackgroundFloating;
         }
 
         /** @hide */
@@ -1826,6 +1868,8 @@
                 pw.print(ActivityInfo.resizeModeToString(td.getResizeMode()));
                 pw.print(" minWidth="); pw.print(td.getMinWidth());
                 pw.print(" minHeight="); pw.print(td.getMinHeight());
+                pw.print(" colorBackgroundFloating=#");
+                pw.print(Integer.toHexString(td.getBackgroundColorFloating()));
                 pw.println(" }");
             }
         }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 7402690..c31c22c 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -30,6 +30,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.PowerWhitelistManager.TempAllowListType;
 import android.os.TransactionTooLargeException;
 import android.os.WorkSource;
 import android.util.ArraySet;
@@ -103,7 +104,7 @@
      * @param target
      * @param whitelistToken
      * @param duration temp allowlist duration in milliseconds.
-     * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+     * @param type temp allowlist type defined at {@link TempAllowListType}
      */
     public abstract void setPendingIntentWhitelistDuration(IIntentSender target,
             IBinder whitelistToken, long duration, int type);
@@ -136,10 +137,10 @@
      * @param changingUid uid to add or remove to temp allowlist.
      * @param adding true to add to temp allowlist, false to remove from temp allowlist.
      * @param durationMs when adding is true, the duration to be in temp allowlist.
-     * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}.
+     * @param type temp allowlist type defined at {@link TempAllowListType}.
      */
     public abstract void updateDeviceIdleTempWhitelist(int[] appids, int changingUid,
-            boolean adding, long durationMs, @BroadcastOptions.TempAllowListType int type);
+            boolean adding, long durationMs, @TempAllowListType int type);
 
     /**
      * Get the procstate for the UID.  The return value will be between
@@ -333,7 +334,7 @@
      * @param callerUid the UID that sent the PendingIntent.
      * @param targetUid the UID that is been temp allowlisted.
      * @param duration temp allowlist duration in milliseconds.
-     * @param type temp allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+     * @param type temp allowlist type defined at {@link TempAllowListType}
      * @param tag
      */
     public abstract void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid,
@@ -537,4 +538,10 @@
      * @return mBootTimeTempAllowlistDuration of ActivityManagerConstants.
      */
     public abstract long getBootTimeTempAllowListDuration();
+
+    /** Register an {@link AnrController} to control the ANR dialog behavior */
+    public abstract void registerAnrController(AnrController controller);
+
+    /** Unregister an {@link AnrController} */
+    public abstract void unregisterAnrController(AnrController controller);
 }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 2b5e18d..28da1c3 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
@@ -310,6 +311,9 @@
     private static final String KEY_REMOTE_TRANSITION =
             "android:activity.remoteTransition";
 
+    private static final String KEY_OVERRIDE_TASK_TRANSITION =
+            "android:activity.overrideTaskTransition";
+
     /**
      * @see #setLaunchCookie
      * @hide
@@ -393,6 +397,7 @@
     private RemoteAnimationAdapter mRemoteAnimationAdapter;
     private IBinder mLaunchCookie;
     private IRemoteTransition mRemoteTransition;
+    private boolean mOverrideTaskTransition;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -476,6 +481,40 @@
     }
 
     /**
+     * Create an ActivityOptions specifying a custom animation to run when the activity in the
+     * different task is displayed.
+     *
+     * @param context Who is defining this.  This is the application that the
+     * animation resources will be loaded from.
+     * @param enterResId A resource ID of the animation resource to use for
+     * the incoming activity.  Use 0 for no animation.
+     * @param exitResId A resource ID of the animation resource to use for
+     * the outgoing activity.  Use 0 for no animation.
+     * @param handler If <var>listener</var> is non-null this must be a valid
+     * Handler on which to dispatch the callback; otherwise it should be null.
+     * @param startedListener Optional OnAnimationStartedListener to find out when the
+     * requested animation has started running.  If for some reason the animation
+     * is not executed, the callback will happen immediately.
+     * @param finishedListener Optional OnAnimationFinishedListener when the animation
+     * has finished running.
+     *
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     * @hide
+     */
+    @RequiresPermission(START_TASKS_FROM_RECENTS)
+    @TestApi
+    public static @NonNull ActivityOptions makeCustomTaskAnimation(@NonNull Context context,
+            int enterResId, int exitResId, @Nullable Handler handler,
+            @Nullable OnAnimationStartedListener startedListener,
+            @Nullable OnAnimationFinishedListener finishedListener) {
+        ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler,
+                startedListener, finishedListener);
+        opts.mOverrideTaskTransition = true;
+        return opts;
+    }
+
+    /**
      * Creates an ActivityOptions specifying a custom animation to run in place on an existing
      * activity.
      *
@@ -1107,6 +1146,7 @@
         mLaunchCookie = opts.getBinder(KEY_LAUNCH_COOKIE);
         mRemoteTransition = IRemoteTransition.Stub.asInterface(opts.getBinder(
                 KEY_REMOTE_TRANSITION));
+        mOverrideTaskTransition = opts.getBoolean(KEY_OVERRIDE_TASK_TRANSITION);
     }
 
     /**
@@ -1561,6 +1601,12 @@
         return mLaunchCookie;
     }
 
+
+    /** @hide */
+    public boolean getOverrideTaskTransition() {
+        return mOverrideTaskTransition;
+    }
+
     /**
      * Update the current values in this ActivityOptions from those supplied
      * in <var>otherOptions</var>.  Any values
@@ -1789,6 +1835,9 @@
         if (mRemoteTransition != null) {
             b.putBinder(KEY_REMOTE_TRANSITION, mRemoteTransition.asBinder());
         }
+        if (mOverrideTaskTransition) {
+            b.putBoolean(KEY_OVERRIDE_TASK_TRANSITION, mOverrideTaskTransition);
+        }
         return b;
     }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 9b141b7..e5a04c9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -95,7 +95,6 @@
 import android.media.MediaFrameworkPlatformInitializer;
 import android.media.MediaServiceManager;
 import android.net.ConnectivityManager;
-import android.net.IConnectivityManager;
 import android.net.Proxy;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -2291,9 +2290,10 @@
      * Resources if one has already been created.
      */
     Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
-            String[] libDirs, LoadedApk pkgInfo) {
+            String[] libDirs, LoadedApk pkgInfo, Configuration overrideConfig) {
         return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
-                null, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null);
+                null, overrideConfig, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(),
+                null);
     }
 
     @UnsupportedAppUsage
@@ -6575,25 +6575,6 @@
         // Pass the current context to HardwareRenderer
         HardwareRenderer.setContextForInit(getSystemContext());
 
-        /**
-         * Initialize the default http proxy in this process for the reasons we set the time zone.
-         */
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies");
-        final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
-        if (b != null) {
-            // In pre-boot mode (doing initial launch to collect password), not
-            // all system is up.  This includes the connectivity service, so don't
-            // crash if we can't get it.
-            final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
-            try {
-                Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null));
-            } catch (RemoteException e) {
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
         // Instrumentation info affects the class loader, so load it before
         // setting up the app context.
         final InstrumentationInfo ii;
@@ -6607,6 +6588,23 @@
         updateLocaleListFromAppContext(appContext,
                 mResourcesManager.getConfiguration().getLocales());
 
+        // Initialize the default http proxy in this process.
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies");
+        try {
+            // In pre-boot mode (doing initial launch to collect password), not all system is up.
+            // This includes the connectivity service, so trying to obtain ConnectivityManager at
+            // that point would return null. Check whether the ConnectivityService is available, and
+            // avoid crashing with a NullPointerException if it is not.
+            final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+            if (b != null) {
+                final ConnectivityManager cm =
+                        appContext.getSystemService(ConnectivityManager.class);
+                Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+
         if (!Process.isIsolated()) {
             final int oldMask = StrictMode.allowThreadDiskWritesMask();
             try {
@@ -7521,8 +7519,8 @@
     }
 
     public static void updateHttpProxy(@NonNull Context context) {
-        final ConnectivityManager cm = ConnectivityManager.from(context);
-        Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
+        final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+        Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
     }
 
     @UnsupportedAppUsage
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/core/java/android/app/AnrController.java
similarity index 65%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to core/java/android/app/AnrController.java
index 8b5b251..cfc9d27 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/core/java/android/app/AnrController.java
@@ -14,9 +14,16 @@
  * limitations under the License.
  */
 
+package android.app;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Interface to control the ANR dialog within the activity manager
+ * {@hide}
  */
-@android.annotation.Hide
-package com.android.role;
+public interface AnrController {
+    /**
+     * Returns the delay in milliseconds for an ANR dialog that is about to be shown for
+     * {@code packageName}.
+     */
+    long getAnrDelayMillis(String packageName, int uid);
+}
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index d716a3c..7410a1c 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -50,7 +50,7 @@
  *
  * <p>
  * Application process could die for many reasons, for example {@link #REASON_LOW_MEMORY}
- * when it was killed by the ystem because it was running low on memory. Reason
+ * when it was killed by the system because it was running low on memory. Reason
  * of the death can be retrieved via {@link #getReason}. Besides the reason, there are a few other
  * auxiliary APIs like {@link #getStatus} and {@link #getImportance} to help the caller with
  * additional diagnostic information.
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 77542bd..8ac9139 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -39,11 +39,13 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApkChecksum;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ChangedPackages;
 import android.content.pm.Checksum;
 import android.content.pm.ComponentInfo;
 import android.content.pm.FeatureInfo;
+import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageManager;
@@ -59,6 +61,7 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -70,6 +73,13 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.PackageInfoWithoutStateUtils;
+import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
@@ -115,6 +125,7 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.File;
 import java.lang.ref.WeakReference;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
@@ -871,10 +882,10 @@
     @Override
     public void requestChecksums(@NonNull String packageName, boolean includeSplits,
             @Checksum.Type int required, @NonNull List<Certificate> trustedInstallers,
-            @NonNull IntentSender statusReceiver)
+            @NonNull OnChecksumsReadyListener onChecksumsReadyListener)
             throws CertificateEncodingException, NameNotFoundException {
         Objects.requireNonNull(packageName);
-        Objects.requireNonNull(statusReceiver);
+        Objects.requireNonNull(onChecksumsReadyListener);
         Objects.requireNonNull(trustedInstallers);
         try {
             if (trustedInstallers == TRUST_ALL) {
@@ -886,8 +897,17 @@
                         "trustedInstallers has to be one of TRUST_ALL/TRUST_NONE or a non-empty "
                                 + "list of certificates.");
             }
+            IOnChecksumsReadyListener onChecksumsReadyListenerDelegate =
+                    new IOnChecksumsReadyListener.Stub() {
+                        @Override
+                        public void onChecksumsReady(List<ApkChecksum> checksums)
+                                throws RemoteException {
+                            onChecksumsReadyListener.onChecksumsReady(checksums);
+                        }
+                    };
             mPM.requestChecksums(packageName, includeSplits, DEFAULT_CHECKSUMS, required,
-                    encodeCertificates(trustedInstallers), statusReceiver, getUserId());
+                    encodeCertificates(trustedInstallers), onChecksumsReadyListenerDelegate,
+                    getUserId());
         } catch (ParcelableException e) {
             e.maybeRethrow(PackageManager.NameNotFoundException.class);
             throw new RuntimeException(e);
@@ -1691,20 +1711,29 @@
     @Override
     public Resources getResourcesForApplication(@NonNull ApplicationInfo app)
             throws NameNotFoundException {
+        return getResourcesForApplication(app, null);
+    }
+
+    @Override
+    public Resources getResourcesForApplication(@NonNull ApplicationInfo app,
+            @Nullable Configuration configuration) throws NameNotFoundException {
         if (app.packageName.equals("system")) {
-            return mContext.mMainThread.getSystemUiContext().getResources();
+            Context sysuiContext = mContext.mMainThread.getSystemUiContext();
+            if (configuration != null) {
+                sysuiContext = sysuiContext.createConfigurationContext(configuration);
+            }
+            return sysuiContext.getResources();
         }
         final boolean sameUid = (app.uid == Process.myUid());
         final Resources r = mContext.mMainThread.getTopLevelResources(
-                    sameUid ? app.sourceDir : app.publicSourceDir,
-                    sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
-                    app.resourceDirs, app.sharedLibraryFiles,
-                    mContext.mPackageInfo);
+                sameUid ? app.sourceDir : app.publicSourceDir,
+                sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
+                app.resourceDirs, app.sharedLibraryFiles,
+                mContext.mPackageInfo, configuration);
         if (r != null) {
             return r;
         }
         throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
-
     }
 
     @Override
@@ -2045,6 +2074,31 @@
         return info.loadLabel(this);
     }
 
+    @Nullable
+    public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
+            @PackageInfoFlags int flags) {
+        if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
+            // Caller expressed no opinion about what encryption
+            // aware/unaware components they want to see, so match both
+            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+        }
+
+        boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
+                || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
+
+        ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
+        ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
+                new File(archiveFilePath), 0, getPermissionManager().getSplitPermissions(),
+                collectCertificates);
+        if (result.isError()) {
+            return null;
+        }
+        return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
+                new PackageUserState(), UserHandle.getCallingUserId());
+    }
+
     @Override
     public int installExistingPackage(String packageName) throws NameNotFoundException {
         return installExistingPackage(packageName, INSTALL_REASON_UNKNOWN);
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index a16f6a8..445fdd8 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -16,14 +16,12 @@
 
 package android.app;
 
-import android.annotation.IntDef;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Build;
 import android.os.Bundle;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import android.os.PowerWhitelistManager;
+import android.os.PowerWhitelistManager.TempAllowListType;
 
 /**
  * Helper class for building an options Bundle that can be used with
@@ -75,25 +73,21 @@
             "android:broadcast.allowBackgroundActivityStarts";
 
     /**
-     * Allow the temp allowlist behavior, plus allow foreground service start from background.
-     */
-    public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0;
-    /**
-     * Only allow the temp allowlist behavior, not allow foreground service start from
-     * background.
-     */
-    public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1;
-
-    /**
-     * The list of temp allowlist types.
      * @hide
+     * @deprecated Use {@link android.os.PowerWhitelistManager#
+     * TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
      */
-    @IntDef(flag = true, prefix = { "TEMPORARY_WHITELIST_TYPE_" }, value = {
-            TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
-            TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TempAllowListType {}
+    @Deprecated
+    public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED =
+            PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+    /**
+     * @hide
+     * @deprecated Use {@link android.os.PowerWhitelistManager#
+     * TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} instead.
+     */
+    @Deprecated
+    public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED =
+            PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED;
 
     public static BroadcastOptions makeBasic() {
         BroadcastOptions opts = new BroadcastOptions();
@@ -125,7 +119,8 @@
             android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
     public void setTemporaryAppWhitelistDuration(long duration) {
         mTemporaryAppWhitelistDuration = duration;
-        mTemporaryAppWhitelistType = TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
+        mTemporaryAppWhitelistType =
+                PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
     }
 
     /**
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
new file mode 100644
index 0000000..8b6570f
--- /dev/null
+++ b/core/java/android/app/GameManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 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 android.app;
+
+import android.annotation.IntDef;
+import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
+import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The GameManager allows system apps to modify and query the game mode of apps.
+ *
+ * @hide
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+@SystemService(Context.GAME_SERVICE)
+public final class GameManager {
+
+    private static final String TAG = "GameManager";
+
+    private final Context mContext;
+    private final IGameManagerService mService;
+
+    @IntDef(flag = false, prefix = { "GAME_MODE_" }, value = {
+            GAME_MODE_UNSUPPORTED, // 0
+            GAME_MODE_STANDARD, // 1
+            GAME_MODE_PERFORMANCE, // 2
+            GAME_MODE_BATTERY, // 3
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GameMode {}
+
+    public static final int GAME_MODE_UNSUPPORTED = 0;
+    public static final int GAME_MODE_STANDARD = 1;
+    public static final int GAME_MODE_PERFORMANCE = 2;
+    public static final int GAME_MODE_BATTERY = 3;
+
+    public GameManager(Context context, Handler handler) throws ServiceNotFoundException {
+        mContext = context;
+        mService = IGameManagerService.Stub.asInterface(
+                ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
+    }
+
+    @VisibleForTesting
+    public GameManager(Context context, IGameManagerService gameManagerService) {
+        mContext = context;
+        mService = gameManagerService;
+    }
+
+    /**
+     * Returns the game mode for the given package.
+     */
+    // TODO(b/178111358): Add @RequiresPermission.
+    @UserHandleAware
+    public @GameMode int getGameMode(String packageName) {
+        try {
+            return mService.getGameMode(packageName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the game mode for the given package.
+     */
+    // TODO(b/178111358): Add @RequiresPermission.
+    @UserHandleAware
+    public void setGameMode(String packageName, @GameMode int gameMode) {
+        try {
+            mService.setGameMode(packageName, gameMode, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index ebf1027..9d3286f 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -25,6 +25,8 @@
 import android.os.PersistableBundle;
 import android.view.RemoteAnimationDefinition;
 
+import com.android.internal.policy.IKeyguardDismissCallback;
+
 /**
  * Interface for the callback and request from an activity to system.
  *
@@ -34,7 +36,13 @@
     oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
     oneway void activityResumed(in IBinder token);
     oneway void activityTopResumedStateLost();
-    oneway void activityPaused(in IBinder token);
+    /**
+     * Notifies that the activity has completed paused. This call is not one-way because it can make
+     * consecutive launch in the same process more coherent. About the order of binder call, it
+     * should be fine with other one-way calls because if pause hasn't completed on the server side,
+     * there won't be other lifecycle changes.
+     */
+    void activityPaused(in IBinder token);
     oneway void activityStopped(in IBinder token, in Bundle state,
             in PersistableBundle persistentState, in CharSequence description);
     oneway void activityDestroyed(in IBinder token);
@@ -88,13 +96,41 @@
     oneway void setInheritShowWhenLocked(in IBinder token, boolean setInheritShownWhenLocked);
     oneway void setTurnScreenOn(in IBinder token, boolean turnScreenOn);
     oneway void reportActivityFullyDrawn(in IBinder token, boolean restoredFromBundle);
-    oneway void overridePendingTransition(in IBinder token, in String packageName,
+    /**
+     * Overrides the animation of activity pending transition. This call is not one-way because
+     * the method is usually used after startActivity or Activity#finish. If this is non-blocking,
+     * the calling activity may proceed to complete pause and become stopping state, which will
+     * cause the request to be ignored. Besides, startActivity and Activity#finish are blocking
+     * calls, so this method should be the same as them to keep the invocation order.
+     */
+    void overridePendingTransition(in IBinder token, in String packageName,
             int enterAnim, int exitAnim);
     int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName);
 
     /** See {@link android.app.Activity#setDisablePreviewScreenshots}. */
     oneway void setDisablePreviewScreenshots(in IBinder token, boolean disable);
 
+    /**
+     * Restarts the activity by killing its process if it is visible. If the activity is not
+     * visible, the activity will not be restarted immediately and just keep the activity record in
+     * the stack. It also resets the current override configuration so the activity will use the
+     * configuration according to the latest state.
+     *
+     * @param activityToken The token of the target activity to restart.
+     */
+    void restartActivityProcessIfVisible(in IBinder activityToken);
+
+    /**
+     * It should only be called from home activity to remove its outdated snapshot. The home
+     * snapshot is used to speed up entering home from screen off. If the content of home activity
+     * is significantly different from before taking the snapshot, then the home activity can use
+     * this method to avoid inconsistent transition.
+     */
+    void invalidateHomeTaskSnapshot(IBinder homeToken);
+
+    void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback,
+            in CharSequence message);
+
     /** Registers remote animations for a specific activity. */
     void registerRemoteAnimations(in IBinder token, in RemoteAnimationDefinition definition);
 
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index b085340..38a3e70 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -72,7 +72,6 @@
 import android.window.IWindowOrganizerController;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.IResultReceiver;
-import com.android.internal.policy.IKeyguardDismissCallback;
 
 import java.util.List;
 
@@ -85,8 +84,6 @@
 // TODO(b/174040395): Make this interface private to ActivityTaskManager.java and have external
 // caller go through that call instead. This would help us better separate and control the API
 // surface exposed.
-// TODO(b/174041144): Move callback methods from Activity (Things that take param 'IBinder token')
-// to a separate interface that is only available to the Activity.
 // TODO(b/174041603): Create a builder interface for things like startActivityXXX(...) to reduce
 // interface duplication.
 // TODO(b/174040691): Clean-up/remove all obsolete or unused interfaces like things that should be
@@ -294,9 +291,6 @@
     // Get device configuration
     ConfigurationInfo getDeviceConfigurationInfo();
 
-    void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback,
-            in CharSequence message);
-
     /** Cancels the window transitions for the given task. */
     void cancelTaskWindowTransition(int taskId);
 
@@ -309,14 +303,6 @@
     android.window.TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution);
 
     /**
-     * It should only be called from home activity to remove its outdated snapshot. The home
-     * snapshot is used to speed up entering home from screen off. If the content of home activity
-     * is significantly different from before taking the snapshot, then the home activity can use
-     * this method to avoid inconsistent transition.
-     */
-    void invalidateHomeTaskSnapshot(IBinder homeToken);
-
-    /**
      * Return the user id of last resumed activity.
      */
     int getLastResumedActivityUserId();
@@ -362,14 +348,4 @@
      * Clears launch params for given packages.
      */
     void clearLaunchParamsForPackages(in List<String> packageNames);
-
-    /**
-     * Restarts the activity by killing its process if it is visible. If the activity is not
-     * visible, the activity will not be restarted immediately and just keep the activity record in
-     * the stack. It also resets the current override configuration so the activity will use the
-     * configuration according to the latest state.
-     *
-     * @param activityToken The token of the target activity to restart.
-     */
-    void restartActivityProcessIfVisible(in IBinder activityToken);
 }
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
new file mode 100644
index 0000000..c8e1478
--- /dev/null
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -0,0 +1,25 @@
+/*
+** Copyright 2021, 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 android.app;
+
+/**
+ * @hide
+ */
+interface IGameManagerService {
+    int getGameMode(String packageName, int userId);
+    void setGameMode(String packageName, int gameMode, int userId);
+}
\ No newline at end of file
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 9e1c505..a9e28bb 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -145,18 +145,6 @@
     void onTaskSnapshotChanged(int taskId, in TaskSnapshot snapshot);
 
     /**
-     * Called when the resumed activity is in size compatibility mode and its override configuration
-     * is different from the current one of system.
-     *
-     * @param displayId Id of the display where the activity resides.
-     * @param activityToken Token of the size compatibility mode activity. It will be null when
-     *                      switching to a activity that is not in size compatibility mode or the
-     *                      configuration of the activity.
-     * @see com.android.server.wm.ActivityRecord#inSizeCompatMode
-     */
-    void onSizeCompatModeActivityChanged(int displayId, in IBinder activityToken);
-
-    /**
      * Reports that an Activity received a back key press when there were no additional activities
      * on the back stack.
      *
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 545c3f7..b6d25cf 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -598,33 +598,29 @@
     @SystemApi
     public void requestDismissKeyguard(@NonNull Activity activity, @Nullable CharSequence message,
             @Nullable KeyguardDismissCallback callback) {
-        try {
-            ActivityTaskManager.getService().dismissKeyguard(
-                    activity.getActivityToken(), new IKeyguardDismissCallback.Stub() {
-                @Override
-                public void onDismissError() throws RemoteException {
-                    if (callback != null && !activity.isDestroyed()) {
-                        activity.mHandler.post(callback::onDismissError);
-                    }
+        ActivityClient.getInstance().dismissKeyguard(
+                activity.getActivityToken(), new IKeyguardDismissCallback.Stub() {
+            @Override
+            public void onDismissError() throws RemoteException {
+                if (callback != null && !activity.isDestroyed()) {
+                    activity.mHandler.post(callback::onDismissError);
                 }
+            }
 
-                @Override
-                public void onDismissSucceeded() throws RemoteException {
-                    if (callback != null && !activity.isDestroyed()) {
-                        activity.mHandler.post(callback::onDismissSucceeded);
-                    }
+            @Override
+            public void onDismissSucceeded() throws RemoteException {
+                if (callback != null && !activity.isDestroyed()) {
+                    activity.mHandler.post(callback::onDismissSucceeded);
                 }
+            }
 
-                @Override
-                public void onDismissCancelled() throws RemoteException {
-                    if (callback != null && !activity.isDestroyed()) {
-                        activity.mHandler.post(callback::onDismissCancelled);
-                    }
+            @Override
+            public void onDismissCancelled() throws RemoteException {
+                if (callback != null && !activity.isDestroyed()) {
+                    activity.mHandler.post(callback::onDismissCancelled);
                 }
-            }, message);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+            }
+        }, message);
     }
 
     /**
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 68d3a92..49f508d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5255,6 +5255,7 @@
                 // We still want a time to be set but gone, such that we can show and hide it
                 // on demand in case it's a child notification without anything in the header
                 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime);
+                setTextViewColorSecondary(contentView, R.id.time, p);
             }
         }
 
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 60bfac5..e6aa7a7 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -47,8 +47,22 @@
 per-file *Zen* = file:/packages/SystemUI/OWNERS
 per-file *StatusBar* = file:/packages/SystemUI/OWNERS
 
+# PackageManager
+per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file InstantAppResolverService.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file LoadedApk.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file PackageDeleteObserver.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file PackageInstallObserver.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file EphemeralResolveInfo.aidl = file:/services/core/java/com/android/server/pm/OWNERS
+per-file IEphemeralResolver.aidl = file:/services/core/java/com/android/server/pm/OWNERS
+per-file IInstantAppResolver.aidl = file:/services/core/java/com/android/server/pm/OWNERS
+per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/server/pm/OWNERS
+
 # ResourcesManager
-per-file ResourcesManager = rtmitchell@google.com, toddke@google.com
+per-file ResourcesManager.java = rtmitchell@google.com, toddke@google.com
+
+# VoiceInteraction
+per-file *VoiceInteract* = file:/core/java/android/service/voice/OWNERS
 
 # Wallpaper
 per-file *Wallpaper* = file:/core/java/android/service/wallpaper/OWNERS
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 8635222..671315f 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemApi.Client;
 import android.annotation.TestApi;
@@ -1275,8 +1276,10 @@
      * @param flags MATCH_* flags from {@link android.content.pm.PackageManager}.
      * @hide
      */
+    @SuppressLint("NullableCollection")
     @RequiresPermission(permission.GET_INTENT_SENDER_INTENT)
     @SystemApi(client = Client.MODULE_LIBRARIES)
+    @TestApi
     public @Nullable List<ResolveInfo> queryIntentComponents(@ResolveInfoFlags int flags) {
         try {
             return ActivityManager.getService()
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d5f0149..7404e53 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -34,6 +34,7 @@
 import android.app.role.RoleFrameworkInitializer;
 import android.app.search.SearchUiManager;
 import android.app.slice.SliceManager;
+import android.app.smartspace.SmartspaceManager;
 import android.app.time.TimeManager;
 import android.app.timedetector.TimeDetector;
 import android.app.timedetector.TimeDetectorImpl;
@@ -68,6 +69,9 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutManager;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationManagerImpl;
+import android.content.pm.verify.domain.IDomainVerificationManager;
 import android.content.res.Resources;
 import android.content.rollback.RollbackManagerFrameworkInitializer;
 import android.debug.AdbManager;
@@ -119,21 +123,16 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.tunerresourcemanager.ITunerResourceManager;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
-import android.net.ConnectivityDiagnosticsManager;
-import android.net.ConnectivityManager;
+import android.net.ConnectivityFrameworkInitializer;
 import android.net.EthernetManager;
-import android.net.IConnectivityManager;
 import android.net.IEthernetManager;
 import android.net.IIpSecService;
 import android.net.INetworkPolicyManager;
-import android.net.ITestNetworkManager;
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
 import android.net.NetworkWatchlistManager;
-import android.net.TestNetworkManager;
 import android.net.TetheringManager;
-import android.net.VpnManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
 import android.net.nsd.INsdManager;
@@ -162,7 +161,6 @@
 import android.os.IncidentManager;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.StatsFrameworkInitializer;
@@ -369,15 +367,6 @@
         // (which extends it).
         SYSTEM_SERVICE_NAMES.put(android.text.ClipboardManager.class, Context.CLIPBOARD_SERVICE);
 
-        registerService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class,
-                new StaticApplicationContextServiceFetcher<ConnectivityManager>() {
-            @Override
-            public ConnectivityManager createService(Context context) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
-                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
-                return new ConnectivityManager(context, service);
-            }});
-
         registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() {
             @Override
             public IBinder createService() throws ServiceNotFoundException {
@@ -411,50 +400,6 @@
                 return new IpSecManager(ctx, service);
             }});
 
-        registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class,
-                new CachedServiceFetcher<VpnManager>() {
-            @Override
-            public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
-                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
-                return new VpnManager(ctx, service);
-            }});
-
-        registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
-                ConnectivityDiagnosticsManager.class,
-                new CachedServiceFetcher<ConnectivityDiagnosticsManager>() {
-            @Override
-            public ConnectivityDiagnosticsManager createService(ContextImpl ctx)
-                    throws ServiceNotFoundException {
-                // ConnectivityDiagnosticsManager is backed by ConnectivityService
-                IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
-                IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
-                return new ConnectivityDiagnosticsManager(ctx, service);
-            }});
-
-        registerService(
-                Context.TEST_NETWORK_SERVICE,
-                TestNetworkManager.class,
-                new StaticApplicationContextServiceFetcher<TestNetworkManager>() {
-                    @Override
-                    public TestNetworkManager createService(Context context)
-                            throws ServiceNotFoundException {
-                        IBinder csBinder =
-                                ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE);
-                        IConnectivityManager csMgr =
-                                IConnectivityManager.Stub.asInterface(csBinder);
-
-                        final IBinder tnBinder;
-                        try {
-                            tnBinder = csMgr.startOrGetTestNetworkService();
-                        } catch (RemoteException e) {
-                            throw new ServiceNotFoundException(Context.TEST_NETWORK_SERVICE);
-                        }
-                        ITestNetworkManager tnMgr = ITestNetworkManager.Stub.asInterface(tnBinder);
-                        return new TestNetworkManager(tnMgr);
-                    }
-                });
-
         registerService(Context.COUNTRY_DETECTOR, CountryDetector.class,
                 new StaticServiceFetcher<CountryDetector>() {
             @Override
@@ -1223,6 +1168,16 @@
                 }
             });
 
+        registerService(Context.SMARTSPACE_SERVICE, SmartspaceManager.class,
+            new CachedServiceFetcher<SmartspaceManager>() {
+                @Override
+                public SmartspaceManager createService(ContextImpl ctx)
+                    throws ServiceNotFoundException {
+                    IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+                    return b == null ? null : new SmartspaceManager(ctx);
+                }
+            });
+
         registerService(Context.APP_PREDICTION_SERVICE, AppPredictionManager.class,
                 new CachedServiceFetcher<AppPredictionManager>() {
             @Override
@@ -1426,10 +1381,35 @@
                         return new MediaMetricsManager(service, ctx.getUserId());
                     }});
 
+        registerService(Context.GAME_SERVICE, GameManager.class,
+                new CachedServiceFetcher<GameManager>() {
+                    @Override
+                    public GameManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        return new GameManager(ctx.getOuterContext(),
+                                ctx.mMainThread.getHandler());
+                    }
+                });
+
+        // TODO(b/159952358): Only register this service for the domain verification agent?
+        registerService(Context.DOMAIN_VERIFICATION_SERVICE, DomainVerificationManager.class,
+                new CachedServiceFetcher<DomainVerificationManager>() {
+                    @Override
+                    public DomainVerificationManager createService(ContextImpl context)
+                            throws ServiceNotFoundException {
+                        IBinder binder = ServiceManager.getServiceOrThrow(
+                                Context.DOMAIN_VERIFICATION_SERVICE);
+                        IDomainVerificationManager service =
+                                IDomainVerificationManager.Stub.asInterface(binder);
+                        return new DomainVerificationManagerImpl(context, service);
+                    }
+                });
+
         sInitializing = true;
         try {
             // Note: the following functions need to be @SystemApis, once they become mainline
             // modules.
+            ConnectivityFrameworkInitializer.registerServiceWrappers();
             JobSchedulerFrameworkInitializer.registerServiceWrappers();
             BlobStoreManagerFrameworkInitializer.initialize();
             TelephonyFrameworkInitializer.registerServiceWrappers();
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 623c878d..390d921 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -36,7 +36,6 @@
 import android.window.WindowContainerToken;
 
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Objects;
 
 /**
@@ -180,6 +179,19 @@
     public ActivityInfo topActivityInfo;
 
     /**
+     * The top activity in this task.
+     * @hide
+     */
+    @Nullable
+    public IBinder topActivityToken;
+
+    /**
+     * Whether the direct top activity is in size compat mode on foreground.
+     * @hide
+     */
+    public boolean topActivityInSizeCompat;
+
+    /**
      * Whether this task is resizable. Unlike {@link #resizeMode} (which is what the top activity
      * supports), this is what the system actually uses for resizability based on other policy and
      * developer options.
@@ -325,6 +337,23 @@
     }
 
     /**
+     * @return {@code true} if parameters that are important for size compat have changed.
+     * @hide
+     */
+    public boolean equalsForSizeCompat(@Nullable TaskInfo that) {
+        if (that == null) {
+            return false;
+        }
+        return displayId == that.displayId
+                && taskId == that.taskId
+                && topActivityInSizeCompat == that.topActivityInSizeCompat
+                // TopActivityToken and bounds are important if top activity is in size compat
+                && (!topActivityInSizeCompat || topActivityToken.equals(that.topActivityToken))
+                && (!topActivityInSizeCompat || configuration.windowConfiguration.getBounds()
+                    .equals(that.configuration.windowConfiguration.getBounds()));
+    }
+
+    /**
      * Reads the TaskInfo from a parcel.
      */
     void readFromParcel(Parcel source) {
@@ -356,6 +385,8 @@
         parentTaskId = source.readInt();
         isFocused = source.readBoolean();
         isVisible = source.readBoolean();
+        topActivityToken = source.readStrongBinder();
+        topActivityInSizeCompat = source.readBoolean();
     }
 
     /**
@@ -391,6 +422,8 @@
         dest.writeInt(parentTaskId);
         dest.writeBoolean(isFocused);
         dest.writeBoolean(isVisible);
+        dest.writeStrongBinder(topActivityToken);
+        dest.writeBoolean(topActivityInSizeCompat);
     }
 
     @Override
@@ -415,6 +448,8 @@
                 + " parentTaskId=" + parentTaskId
                 + " isFocused=" + isFocused
                 + " isVisible=" + isVisible
+                + " topActivityToken=" + topActivityToken
+                + " topActivityInSizeCompat=" + topActivityInSizeCompat
                 + "}";
     }
 }
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index d1b544d..517ae24 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -21,7 +21,6 @@
 import android.content.ComponentName;
 import android.os.Binder;
 import android.os.Build;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.window.TaskSnapshot;
 
@@ -163,12 +162,6 @@
     }
 
     @Override
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken)
-            throws RemoteException {
-    }
-
-    @Override
     public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo)
             throws RemoteException {
     }
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index 14ed414..cbe2995 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -15,8 +15,7 @@
  */
 package android.app;
 
-import static android.view.WindowManagerGlobal.ADD_OKAY;
-import static android.view.WindowManagerGlobal.ADD_TOO_MANY_TOKENS;
+import static android.view.WindowManagerImpl.createWindowContextWindowManager;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,8 +27,8 @@
 import android.os.RemoteException;
 import android.view.Display;
 import android.view.IWindowManager;
+import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.WindowManagerImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -46,10 +45,10 @@
  */
 @UiContext
 public class WindowContext extends ContextWrapper {
-    private final WindowManagerImpl mWindowManager;
+    private final WindowManager mWindowManager;
     private final IWindowManager mWms;
     private final WindowTokenClient mToken;
-    private boolean mOwnsToken;
+    private boolean mListenerRegistered;
 
     /**
      * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -86,25 +85,14 @@
 
         mToken.attachContext(this);
 
-        mWindowManager = new WindowManagerImpl(this);
-        mWindowManager.setDefaultToken(mToken);
+        mWindowManager = createWindowContextWindowManager(this);
 
-        int result;
         try {
-            // Register the token with WindowManager. This will also call back with the current
-            // config back to the client.
-            result = mWms.addWindowTokenWithOptions(
-                    mToken, type, getDisplayId(), options, getPackageName());
+            mListenerRegistered = mWms.registerWindowContextListener(mToken, type, getDisplayId(),
+                    options);
         }  catch (RemoteException e) {
-            mOwnsToken = false;
             throw e.rethrowFromSystemServer();
         }
-        if (result == ADD_TOO_MANY_TOKENS) {
-            throw new UnsupportedOperationException("createWindowContext failed! Too many unused "
-                    + "window contexts. Please see Context#createWindowContext documentation for "
-                    + "detail.");
-        }
-        mOwnsToken = result == ADD_OKAY;
         Reference.reachabilityFence(this);
     }
 
@@ -131,10 +119,10 @@
     /** Used for test to invoke because we can't invoke finalize directly. */
     @VisibleForTesting
     public void release() {
-        if (mOwnsToken) {
+        if (mListenerRegistered) {
+            mListenerRegistered = false;
             try {
-                mWms.removeWindowToken(mToken, getDisplayId());
-                mOwnsToken = false;
+                mWms.unregisterWindowContextListener(mToken);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java
index 9092ef36..29792ac 100644
--- a/core/java/android/app/WindowTokenClient.java
+++ b/core/java/android/app/WindowTokenClient.java
@@ -44,8 +44,8 @@
      * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
      * can only attach one {@link Context}.
      * <p>This method must be called before invoking
-     * {@link android.view.IWindowManager#addWindowTokenWithOptions(IBinder, int, int, Bundle,
-     * String)}.<p/>
+     * {@link android.view.IWindowManager#registerWindowContextListener(IBinder, int, int,
+     * Bundle, boolean)}.<p/>
      *
      * @param context context to be attached
      * @throws IllegalStateException if attached context has already existed.
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 15ff531..9c07f85 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -51,6 +51,19 @@
     public abstract int getPasswordQuality(@UserIdInt int userHandle);
 
     /**
+     * Caches {@link DevicePolicyManager#getPermissionPolicy(android.content.ComponentName)} of
+     * the given user.
+     */
+    public abstract int getPermissionPolicy(@UserIdInt int userHandle);
+
+    /**
+     * Caches {@link DevicePolicyManager#canAdminGrantSensorsPermissionsForUser(int)} for the
+     * given user.
+     */
+    public abstract boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle);
+
+
+    /**
      * Empty implementation.
      */
     private static class EmptyDevicePolicyCache extends DevicePolicyCache {
@@ -66,5 +79,15 @@
         public int getPasswordQuality(int userHandle) {
             return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
         }
+
+        @Override
+        public int getPermissionPolicy(int userHandle) {
+            return DevicePolicyManager.PERMISSION_POLICY_PROMPT;
+        }
+
+        @Override
+        public boolean canAdminGrantSensorsPermissionsForUser(int userHandle) {
+            return false;
+        }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ada703b..82255c8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -453,59 +453,6 @@
             "android.app.action.PROVISION_FINANCED_DEVICE";
 
     /**
-     * Activity action: Starts the provisioning flow which sets up a managed device.
-     * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
-     *
-     * <p>NOTE: This is only supported on split system user devices, and puts the device into a
-     * management state that is distinct from that reached by
-     * {@link #ACTION_PROVISION_MANAGED_DEVICE} - specifically the device owner runs on the system
-     * user, and only has control over device-wide policies, not individual users and their data.
-     * The primary benefit is that multiple non-system users are supported when provisioning using
-     * this form of device management.
-     *
-     * <p>During device owner provisioning a device admin app is set as the owner of the device.
-     * A device owner has full control over the device. The device owner can not be modified by the
-     * user.
-     *
-     * <p>A typical use case would be a device that is owned by a company, but used by either an
-     * employee or client.
-     *
-     * <p>An intent with this action can be sent only on an unprovisioned device.
-     * It is possible to check if provisioning is allowed or not by querying the method
-     * {@link #isProvisioningAllowed(String)}.
-     *
-     * <p>The intent contains the following extras:
-     * <ul>
-     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
-     * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
-     * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
-     * </ul>
-     *
-     * <p>When device owner provisioning has completed, an intent of the type
-     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
-     * device owner.
-     *
-     * <p>From version {@link android.os.Build.VERSION_CODES#O}, when device owner provisioning has
-     * completed, along with the above broadcast, activity intent
-     * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the device owner.
-     *
-     * <p>If provisioning fails, the device is factory reset.
-     *
-     * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
-     * of the provisioning flow was successful, although this doesn't guarantee the full flow will
-     * succeed. Conversely a result code of {@link android.app.Activity#RESULT_CANCELED} implies
-     * that the user backed-out of provisioning, or some precondition for provisioning wasn't met.
-     *
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
-        = "android.app.action.PROVISION_MANAGED_SHAREABLE_DEVICE";
-
-    /**
      * Activity action: Finalizes management provisioning, should be used after user-setup
      * has been completed and {@link #getUserProvisioningState()} returns one of:
      * <ul>
@@ -1033,6 +980,19 @@
         = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM";
 
     /**
+     * A boolean extra indicating the admin of a fully-managed device opts out of controlling
+     * permission grants for sensor-related permissions,
+     * see {@link #setPermissionGrantState(ComponentName, String, String, int)}.
+     *
+     * The default for this extra is {@code false} - by default, the admin of a fully-managed
+     * device has the ability to grant sensors-related permissions.
+     *
+     * <p>Use with {@link #ACTION_PROVISION_MANAGED_DEVICE} only.
+     */
+    public static final String EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT =
+            "android.app.extra.PROVISIONING_PERMISSION_GRANT_OPT_OUT";
+
+    /**
      * A String extra holding the URL-safe base64 encoded SHA-256 checksum of any signature of the
      * android package archive at the download location specified in {@link
      * #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}.
@@ -1783,8 +1743,12 @@
      * Broadcast action to notify ManagedProvisioning that
      * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has changed.
      * @hide
+     * @deprecated No longer needed as ManagedProvisioning no longer handles
+     * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction changing.
      */
+    // TODO(b/177221010): Remove when Managed Provisioning no longer depend on it.
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @Deprecated
     public static final String ACTION_DATA_SHARING_RESTRICTION_CHANGED =
             "android.app.action.DATA_SHARING_RESTRICTION_CHANGED";
 
@@ -1990,8 +1954,8 @@
      * Result code for {@link #checkProvisioningPreCondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when provisioning is allowed.
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}
+     * when provisioning is allowed.
      *
      * @hide
      */
@@ -2001,9 +1965,8 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the device already has a device
-     * owner.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the device already has a
+     * device owner.
      *
      * @hide
      */
@@ -2013,9 +1976,8 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user has a profile owner and for
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user has a profile owner
+     *  and for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set.
      *
      * @hide
      */
@@ -2025,8 +1987,7 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} when the user isn't running.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user isn't running.
      *
      * @hide
      */
@@ -2036,9 +1997,8 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the device has already been setup and
-     * for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the device has already been
+     * setup and for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup.
      *
      * @hide
      */
@@ -2064,8 +2024,7 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} if the user is not a system user.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the user is not a system user.
      *
      * @hide
      */
@@ -2075,9 +2034,8 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} and {@link #ACTION_PROVISION_MANAGED_USER}
-     * when the device is a watch and is already paired.
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and
+     * {@link #ACTION_PROVISION_MANAGED_USER} when the device is a watch and is already paired.
      *
      * @hide
      */
@@ -2121,14 +2079,11 @@
 
     /**
      * TODO (b/137101239): clean up split system user codes
-     * Result code for {@link #checkProvisioningPreCondition}.
-     *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices not running with split system
-     * user.
      *
      * @hide
-     */
+     * @deprecated not used anymore but can't be removed since it's a @TestApi.
+     **/
+    @Deprecated
     @TestApi
     public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
 
@@ -2136,8 +2091,7 @@
      * Result code for {@link #checkProvisioningPreCondition}.
      *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     * {@link #ACTION_PROVISION_MANAGED_PROFILE}, {@link #ACTION_PROVISION_MANAGED_USER} and
-     * {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE} on devices which do not support device
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support device
      * admins.
      *
      * @hide
@@ -2149,11 +2103,10 @@
      * TODO (b/137101239): clean up split system user codes
      * Result code for {@link #checkProvisioningPreCondition}.
      *
-     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the device the user is a
-     * system user on a split system user device.
-     *
      * @hide
+     * @deprecated not used anymore but can't be removed since it's a @TestApi.
      */
+    @Deprecated
     @TestApi
     public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
 
@@ -2969,6 +2922,35 @@
         return DebugUtils.constantToString(DevicePolicyManager.class, PREFIX_OPERATION, operation);
     }
 
+    private static final String PREFIX_UNSAFE_OPERATION_REASON = "UNSAFE_OPERATION_REASON_";
+
+    /** @hide */
+    @IntDef(prefix = PREFIX_UNSAFE_OPERATION_REASON, value = {
+            UNSAFE_OPERATION_REASON_NONE,
+            UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public static @interface UnsafeOperationReason {
+    }
+
+    /** @hide */
+    @TestApi
+    public static final int UNSAFE_OPERATION_REASON_NONE = -1;
+
+    /**
+     * Indicates that a {@link UnsafeStateException} was thrown because the operation would distract
+     * the driver of the vehicle.
+     */
+    public static final int UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION = 1;
+
+    /** @hide */
+    @NonNull
+    @TestApi
+    public static String unsafeOperationReasonToString(@UnsafeOperationReason int reason) {
+        return DebugUtils.constantToString(DevicePolicyManager.class,
+                PREFIX_UNSAFE_OPERATION_REASON, reason);
+    }
+
     /** @hide */
     public void resetNewUserDisclaimer() {
         if (mService != null) {
@@ -5504,26 +5486,6 @@
             "android.app.action.ACTION_SHOW_NEW_USER_DISCLAIMER";
 
     /**
-     * Broadcast action: notify managed provisioning that the device has been provisioned.
-     *
-     * @hide
-     */
-    @TestApi
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_PROVISIONED_MANAGED_DEVICE =
-            "android.app.action.PROVISIONED_MANAGED_DEVICE";
-
-    /**
-     * Broadcast action: notify managed provisioning that a new managed profile is created.
-     *
-     * @hide
-     */
-    @TestApi
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_MANAGED_PROFILE_CREATED =
-            "android.app.action.MANAGED_PROFILE_CREATED";
-
-    /**
      * Widgets are enabled in keyguard
      */
     public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
@@ -7147,17 +7109,9 @@
     }
 
     /**
+     * TODO (b/137101239): remove this method in follow-up CL
+     * since it's only used for split system user.
      * Called by a device owner to set whether all users created on the device should be ephemeral.
-     * <p>
-     * The system user is exempt from this policy - it is never ephemeral.
-     * <p>
-     * The calling device admin must be the device owner. If it is not, a security exception will be
-     * thrown.
-     *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
-     * @param forceEphemeralUsers If true, all the existing users will be deleted and all
-     *            subsequently created users will be ephemeral.
-     * @throws SecurityException if {@code admin} is not a device owner.
      * @hide
      */
     public void setForceEphemeralUsers(
@@ -7173,6 +7127,8 @@
     }
 
     /**
+     * TODO (b/137101239): remove this method in follow-up CL
+     * since it's only used for split system user.
      * @return true if all users are created ephemeral.
      * @throws SecurityException if {@code admin} is not a device owner.
      * @hide
@@ -10577,6 +10533,13 @@
      * As this policy only acts on runtime permission requests, it only applies to applications
      * built with a {@code targetSdkVersion} of {@link android.os.Build.VERSION_CODES#M} or later.
      *
+     * <p>
+     * NOTE: On devices running {@link android.os.Build.VERSION_CODES#S} and above, an auto-grant
+     * policy will not apply to certain sensors-related permissions on some configurations.
+     * See {@link #setPermissionGrantState(ComponentName, String, String, int)} for the list of
+     * permissions affected, and the behavior change for managed profiles and fully-managed
+     * devices.
+     *
      * @param admin Which profile or device owner this request is associated with.
      * @param policy One of the policy constants {@link #PERMISSION_POLICY_PROMPT},
      *            {@link #PERMISSION_POLICY_AUTO_GRANT} and {@link #PERMISSION_POLICY_AUTO_DENY}.
@@ -10635,6 +10598,31 @@
      * application built with a {@code targetSdkVersion} &lt;
      * {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to
      * {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted.
+     * <p>
+     * NOTE: On devices running {@link android.os.Build.VERSION_CODES#S} and above, control over
+     * the following, sensors-related, permissions is restricted:
+     * <ul>
+     *    <li>Manifest.permission.ACCESS_FINE_LOCATION</li>
+     *    <li>Manifest.permission.ACCESS_BACKGROUND_LOCATION</li>
+     *    <li>Manifest.permission.ACCESS_COARSE_LOCATION</li>
+     *    <li>Manifest.permission.CAMERA</li>
+     *    <li>Manifest.permission.RECORD_AUDIO</li>
+     *    <li>Manifest.permission.RECORD_BACKGROUND_AUDIO</li>
+     *    <li>Manifest.permission.ACTIVITY_RECOGNITION</li>
+     *    <li>Manifest.permission.BODY_SENSORS</li>
+     * </ul>
+     * <p>
+     * A profile owner may not grant these permissions (i.e. call this method with any of the
+     * permissions listed above and {@code grantState} of {@code #PERMISSION_GRANT_STATE_GRANTED}),
+     * but may deny them.
+     * <p>
+     * A device owner, by default, may continue granting these permissions. However, for increased
+     * user control, the admin may opt out of controlling grants for these permissions by including
+     * {@link #EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT} in the provisioning parameters. In that
+     * case the device owner's control will be limited do denying these permissions.
+     * <p>
+     * Attempts by the admin to grant these permissions, when the admin is restricted from doing
+     * so, will be silently ignored (no exception will be thrown).
      *
      * @param admin Which profile or device owner this request is associated with.
      * @param packageName The application to grant or revoke a permission to.
@@ -10731,9 +10719,7 @@
      * profile or user, setting the given package as owner.
      *
      * @param action One of {@link #ACTION_PROVISION_MANAGED_DEVICE},
-     *        {@link #ACTION_PROVISION_MANAGED_PROFILE},
-     *        {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE},
-     *        {@link #ACTION_PROVISION_MANAGED_USER}
+     *        {@link #ACTION_PROVISION_MANAGED_PROFILE}
      * @param packageName The package of the component that would be set as device, user, or profile
      *        owner.
      * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
@@ -13170,10 +13156,11 @@
      */
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS)
-    public void setNextOperationSafety(@DevicePolicyOperation int operation, boolean safe) {
+    public void setNextOperationSafety(@DevicePolicyOperation int operation,
+            @UnsafeOperationReason int reason) {
         if (mService != null) {
             try {
-                mService.setNextOperationSafety(operation, safe);
+                mService.setNextOperationSafety(operation, reason);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
@@ -13270,7 +13257,8 @@
             return null;
         }
         try {
-            return mService.createAndProvisionManagedProfile(provisioningParams);
+            return mService.createAndProvisionManagedProfile(
+                    provisioningParams, mContext.getPackageName());
         } catch (ServiceSpecificException e) {
             throw new ProvisioningException(e, e.errorCode);
         } catch (RemoteException e) {
@@ -13301,7 +13289,7 @@
             throws ProvisioningException {
         if (mService != null) {
             try {
-                mService.provisionFullyManagedDevice(provisioningParams);
+                mService.provisionFullyManagedDevice(provisioningParams, mContext.getPackageName());
             } catch (ServiceSpecificException e) {
                 throw new ProvisioningException(e, e.errorCode);
             } catch (RemoteException re) {
@@ -13309,4 +13297,57 @@
             }
         }
     }
+
+    /**
+     * Resets the default cross profile intent filters that were set during
+     * {@link #createAndProvisionManagedProfile} between {@code userId} and all it's managed
+     * profiles if any.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) {
+        if (mService != null) {
+            try {
+                mService.resetDefaultCrossProfileIntentFilters(userId);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+    }
+    /**
+     * Returns true if the caller is running on a device where the admin can grant
+     * permissions related to device sensors.
+     * This is a signal that the device is a fully-managed device where personal usage is
+     * discouraged.
+     * The list of permissions is listed in
+     * {@link #setPermissionGrantState(ComponentName, String, String, int)}.
+     *
+     * May be called by any app.
+     * @return true if the app can grant device sensors-related permissions, false otherwise.
+     */
+    public boolean canAdminGrantSensorsPermissions() {
+        return canAdminGrantSensorsPermissionsForUser(myUserId());
+    }
+
+    /**
+     * Returns true if the admin can control grants of sensors-related permissions, for
+     * a given user.
+     *
+     * @hide
+     * @param userId The ID of the user to check.
+     * @return if the admin may grant these permissions, false otherwise.
+     */
+    @SystemApi
+    public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
+        if (mService == null) {
+            return false;
+        }
+        try {
+            return mService.canAdminGrantSensorsPermissionsForUser(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicySafetyChecker.java b/core/java/android/app/admin/DevicePolicySafetyChecker.java
index b1a80c5..6c6f2aa 100644
--- a/core/java/android/app/admin/DevicePolicySafetyChecker.java
+++ b/core/java/android/app/admin/DevicePolicySafetyChecker.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -30,14 +31,16 @@
     /**
      * Returns whether the given {@code operation} can be safely executed at the moment.
      */
-    boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation);
+    @UnsafeOperationReason
+    int getUnsafeOperationReason(@DevicePolicyOperation int operation);
 
     /**
      * Returns a new exception for when the given {@code operation} cannot be safely executed.
      */
     @NonNull
-    default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation) {
-        return new UnsafeStateException(operation);
+    default UnsafeStateException newUnsafeStateException(@DevicePolicyOperation int operation,
+            @UnsafeOperationReason int reason) {
+        return new UnsafeStateException(operation, reason);
     }
 
     /**
diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 83af019..5e1cbad 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -42,6 +42,7 @@
     private final long mLocalTime;
     @SuppressLint("UseIcu")
     @Nullable private final Locale mLocale;
+    private final boolean mDeviceOwnerCanGrantSensorsPermissions;
 
     private FullyManagedDeviceProvisioningParams(
             @NonNull ComponentName deviceAdminComponentName,
@@ -49,13 +50,16 @@
             boolean leaveAllSystemAppsEnabled,
             @Nullable String timeZone,
             long localTime,
-            @Nullable @SuppressLint("UseIcu") Locale locale) {
+            @Nullable @SuppressLint("UseIcu") Locale locale,
+            boolean deviceOwnerCanGrantSensorsPermissions) {
         this.mDeviceAdminComponentName = requireNonNull(deviceAdminComponentName);
         this.mOwnerName = requireNonNull(ownerName);
         this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
         this.mTimeZone = timeZone;
         this.mLocalTime = localTime;
         this.mLocale = locale;
+        this.mDeviceOwnerCanGrantSensorsPermissions =
+                deviceOwnerCanGrantSensorsPermissions;
     }
 
     private FullyManagedDeviceProvisioningParams(
@@ -64,13 +68,15 @@
             boolean leaveAllSystemAppsEnabled,
             @Nullable String timeZone,
             long localTime,
-            @Nullable String localeStr) {
+            @Nullable String localeStr,
+            boolean deviceOwnerCanGrantSensorsPermissions) {
         this(deviceAdminComponentName,
                 ownerName,
                 leaveAllSystemAppsEnabled,
                 timeZone,
                 localTime,
-                getLocale(localeStr));
+                getLocale(localeStr),
+                deviceOwnerCanGrantSensorsPermissions);
     }
 
     @Nullable
@@ -107,6 +113,14 @@
     }
 
     /**
+     * @return true if the device owner can control sensor-related permission grants, false
+     * if the device owner has opted out of it.
+     */
+    public boolean canDeviceOwnerGrantSensorsPermissions() {
+        return mDeviceOwnerCanGrantSensorsPermissions;
+    }
+
+    /**
      * Builder class for {@link FullyManagedDeviceProvisioningParams} objects.
      */
     public static final class Builder {
@@ -117,6 +131,8 @@
         private long mLocalTime;
         @SuppressLint("UseIcu")
         @Nullable private Locale mLocale;
+        // Default to allowing control over sensor permission grants.
+        boolean mDeviceOwnerCanGrantSensorsPermissions = true;
 
         /**
          * Initialize a new {@link Builder} to construct a
@@ -181,6 +197,17 @@
         }
 
         /**
+         * Marks that the Device Owner may grant permissions related to device sensors.
+         * See {@link DevicePolicyManager#EXTRA_PROVISIONING_PERMISSION_GRANT_OPT_OUT}.
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setDeviceOwnerCanGrantSensorsPermissions(boolean mayGrant) {
+            mDeviceOwnerCanGrantSensorsPermissions = mayGrant;
+            return this;
+        }
+
+        /**
          * Combines all of the attributes that have been set on this {@code Builder}
          *
          * @return a new {@link FullyManagedDeviceProvisioningParams} object.
@@ -193,7 +220,8 @@
                     mLeaveAllSystemAppsEnabled,
                     mTimeZone,
                     mLocalTime,
-                    mLocale);
+                    mLocale,
+                    mDeviceOwnerCanGrantSensorsPermissions);
         }
     }
 
@@ -211,6 +239,8 @@
                 + ", mTimeZone=" + (mTimeZone == null ? "null" : mTimeZone)
                 + ", mLocalTime=" + mLocalTime
                 + ", mLocale=" + (mLocale == null ? "null" : mLocale)
+                + ", mDeviceOwnerCanGrantSensorsPermissions="
+                + mDeviceOwnerCanGrantSensorsPermissions
                 + '}';
     }
 
@@ -222,6 +252,7 @@
         dest.writeString(mTimeZone);
         dest.writeLong(mLocalTime);
         dest.writeString(mLocale == null ? null : mLocale.toLanguageTag());
+        dest.writeBoolean(mDeviceOwnerCanGrantSensorsPermissions);
     }
 
     @NonNull
@@ -235,6 +266,7 @@
                     String timeZone = in.readString();
                     long localtime = in.readLong();
                     String locale = in.readString();
+                    boolean deviceOwnerCanGrantSensorsPermissions = in.readBoolean();
 
                     return new FullyManagedDeviceProvisioningParams(
                             componentName,
@@ -242,7 +274,8 @@
                             leaveAllSystemAppsEnabled,
                             timeZone,
                             localtime,
-                            locale);
+                            locale,
+                            deviceOwnerCanGrantSensorsPermissions);
                 }
 
                 @Override
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3765a67..89f30cc 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -491,11 +491,14 @@
     void setManagedProfileMaximumTimeOff(in ComponentName admin, long timeoutMs);
     boolean canProfileOwnerResetPasswordWhenLocked(int userId);
 
-    void setNextOperationSafety(int operation, boolean safe);
+    void setNextOperationSafety(int operation, int reason);
 
     String getEnrollmentSpecificId(String callerPackage);
     void setOrganizationIdForUser(in String callerPackage, in String enterpriseId, int userId);
 
-    UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams);
-    void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams);
+    UserHandle createAndProvisionManagedProfile(in ManagedProfileProvisioningParams provisioningParams, in String callerPackage);
+    void provisionFullyManagedDevice(in FullyManagedDeviceProvisioningParams provisioningParams, in String callerPackage);
+
+    void resetDefaultCrossProfileIntentFilters(int userId);
+    boolean canAdminGrantSensorsPermissionsForUser(int userId);
 }
diff --git a/core/java/android/app/admin/UnsafeStateException.java b/core/java/android/app/admin/UnsafeStateException.java
index 9dcaae4..56eeb06 100644
--- a/core/java/android/app/admin/UnsafeStateException.java
+++ b/core/java/android/app/admin/UnsafeStateException.java
@@ -15,15 +15,21 @@
  */
 package android.app.admin;
 
+import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION;
+import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
+
 import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
 /**
- * Exception thrown when a {@link DevicePolicyManager} operation failed because it was not safe
- * to be executed at that moment.
+ * Exception thrown when a {@link android.app.admin.DevicePolicyManager} operation failed because it
+ * was not safe to be executed at that moment.
  *
  * <p>For example, it can be thrown on
  * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE automotive devices} when the vehicle
@@ -33,12 +39,19 @@
 public final class UnsafeStateException extends IllegalStateException implements Parcelable {
 
     private final @DevicePolicyOperation int mOperation;
+    private final @UnsafeOperationReason int mReason;
 
     /** @hide */
-    public UnsafeStateException(@DevicePolicyOperation int operation) {
+    @TestApi
+    public UnsafeStateException(@DevicePolicyOperation int operation,
+            @UnsafeOperationReason int reason) {
         super();
-
+        Preconditions.checkArgument(reason == UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
+                "invalid reason %d, must be %d (%s)", reason,
+                UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION,
+                unsafeOperationReasonToString(UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION));
         mOperation = operation;
+        mReason = reason;
     }
 
     /** @hide */
@@ -47,6 +60,22 @@
         return mOperation;
     }
 
+    /**
+     * Gets the reason the operation is unsafe.
+     *
+     * @return currently, only valid reason is
+     * {@link android.app.admin.DevicePolicyManager#UNSAFE_OPERATION_REASON_DRIVING_DISTRACTION}.
+     */
+    public @UnsafeOperationReason int getReason() {
+        return mReason;
+    }
+
+    /** @hide */
+    @Override
+    public String getMessage() {
+        return DevicePolicyManager.unsafeOperationReasonToString(mReason);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -55,6 +84,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mOperation);
+        dest.writeInt(mReason);
     }
 
     @NonNull
@@ -63,7 +93,7 @@
 
         @Override
         public UnsafeStateException createFromParcel(Parcel source) {
-            return new UnsafeStateException(source.readInt());
+            return new UnsafeStateException(source.readInt(), source.readInt());
         }
 
         @Override
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 65a2164..2b52875 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -2,6 +2,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Activity;
 import android.content.ComponentName;
@@ -1542,6 +1543,7 @@
          * {@link View#getOnReceiveContentMimeTypes()} for details.
          */
         @Nullable
+        @SuppressLint("NullableCollection")
         public String[] getOnReceiveContentMimeTypes() {
             return mOnReceiveContentMimeTypes;
         }
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index 587e883..742d05c 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -41,6 +41,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -460,24 +461,40 @@
                                                    Set<PathWithRequiredFlags> excludes,
                                                    Map<String, Set<PathWithRequiredFlags>> includes)
                 throws IOException, XmlPullParserException {
+            verifyTopLevelTag(parser, "full-backup-content");
+
+            parseRules(parser, excludes, includes, Optional.empty());
+
+            logParsingResults(excludes, includes);
+        }
+
+        private void verifyTopLevelTag(XmlPullParser parser, String tag)
+                throws XmlPullParserException, IOException {
             int event = parser.getEventType(); // START_DOCUMENT
             while (event != XmlPullParser.START_TAG) {
                 event = parser.next();
             }
 
-            if (!"full-backup-content".equals(parser.getName())) {
+            if (!tag.equals(parser.getName())) {
                 throw new XmlPullParserException("Xml file didn't start with correct tag" +
-                        " (<full-backup-content>). Found \"" + parser.getName() + "\"");
+                        " (" + tag + " ). Found \"" + parser.getName() + "\"");
             }
 
             if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
                 Log.v(TAG_XML_PARSER, "\n");
                 Log.v(TAG_XML_PARSER, "====================================================");
-                Log.v(TAG_XML_PARSER, "Found valid fullBackupContent; parsing xml resource.");
+                Log.v(TAG_XML_PARSER, "Found valid " + tag + "; parsing xml resource.");
                 Log.v(TAG_XML_PARSER, "====================================================");
                 Log.v(TAG_XML_PARSER, "");
             }
+        }
 
+        private void parseRules(XmlPullParser parser,
+                Set<PathWithRequiredFlags> excludes,
+                Map<String, Set<PathWithRequiredFlags>> includes,
+                Optional<Integer> maybeRequiredFlags)
+                throws IOException, XmlPullParserException {
+            int event;
             while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 switch (event) {
                     case XmlPullParser.START_TAG:
@@ -498,13 +515,7 @@
                             break;
                         }
 
-                        int requiredFlags = 0; // no transport flags are required by default
-                        if (TAG_INCLUDE.equals(parser.getName())) {
-                            // requiredFlags are only supported for <include /> tag, for <exclude />
-                            // we should always leave them as the default = 0
-                            requiredFlags = getRequiredFlagsFromString(
-                                    parser.getAttributeValue(null, "requireFlags"));
-                        }
+                        int requiredFlags = getRequiredFlagsForRule(parser, maybeRequiredFlags);
 
                         // retrieve the include/exclude set we'll be adding this rule to
                         Set<PathWithRequiredFlags> activeSet = parseCurrentTagForDomain(
@@ -542,7 +553,7 @@
 
                         // Special case for sharedpref files (not dirs) also add ".xml" suffix file.
                         if ("sharedpref".equals(domainFromXml) && !canonicalFile.isDirectory() &&
-                            !canonicalFile.getCanonicalPath().endsWith(".xml")) {
+                                !canonicalFile.getCanonicalPath().endsWith(".xml")) {
                             final String canonicalXmlPath =
                                     canonicalFile.getCanonicalPath() + ".xml";
                             activeSet.add(new PathWithRequiredFlags(canonicalXmlPath,
@@ -554,6 +565,10 @@
                         }
                 }
             }
+        }
+
+        private void logParsingResults(Set<PathWithRequiredFlags> excludes,
+                Map<String, Set<PathWithRequiredFlags>> includes) {
             if (Log.isLoggable(TAG_XML_PARSER, Log.VERBOSE)) {
                 Log.v(TAG_XML_PARSER, "\n");
                 Log.v(TAG_XML_PARSER, "Xml resource parsing complete.");
@@ -613,6 +628,24 @@
             return flags;
         }
 
+        private int getRequiredFlagsForRule(XmlPullParser parser,
+                Optional<Integer> maybeRequiredFlags) {
+            if (maybeRequiredFlags.isPresent()) {
+                // This is the new config format where required flags are specified for the whole
+                // section, not per rule.
+                return maybeRequiredFlags.get();
+            }
+
+            if (TAG_INCLUDE.equals(parser.getName())) {
+                // In the legacy config, requiredFlags are only supported for <include /> tag,
+                // for <exclude /> we should always leave them as the default = 0.
+                return getRequiredFlagsFromString(
+                        parser.getAttributeValue(null, "requireFlags"));
+            }
+
+            return 0;
+        }
+
         private Set<PathWithRequiredFlags> parseCurrentTagForDomain(XmlPullParser parser,
                 Set<PathWithRequiredFlags> excludes,
                 Map<String, Set<PathWithRequiredFlags>> includes, String domain)
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/app/people/ConversationChannel.aidl
similarity index 74%
copy from core/java/com/android/internal/net/VpnInfo.aidl
copy to core/java/android/app/people/ConversationChannel.aidl
index 6fc97be..78df2f1 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/core/java/android/app/people/ConversationChannel.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
+/**
+ * Copyright (c) 2021, 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
+ *     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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.app.people;
 
-parcelable VpnInfo;
+parcelable ConversationChannel;
\ No newline at end of file
diff --git a/core/java/android/app/people/IPeopleManager.aidl b/core/java/android/app/people/IPeopleManager.aidl
index 0d12ed0..ebe9f60 100644
--- a/core/java/android/app/people/IPeopleManager.aidl
+++ b/core/java/android/app/people/IPeopleManager.aidl
@@ -17,6 +17,7 @@
 package android.app.people;
 
 import android.app.people.ConversationStatus;
+import android.app.people.ConversationChannel;
 import android.content.pm.ParceledListSlice;
 import android.net.Uri;
 import android.os.IBinder;
@@ -26,6 +27,13 @@
  * {@hide}
  */
 interface IPeopleManager {
+
+    /**
+    * Returns the specified conversation from the conversations list. If the conversation can't be
+    * found, returns null.
+    */
+    ConversationChannel getConversation(in String packageName, int userId, in String shortcutId);
+
     /**
      * Returns the recent conversations. The conversations that have customized notification
      * settings are excluded from the returned list.
diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java
index 447ca31..3ab20bb 100644
--- a/core/java/android/app/search/Query.java
+++ b/core/java/android/app/search/Query.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -46,6 +47,7 @@
 
     public Query(@NonNull String input,
             long timestamp,
+            @SuppressLint("NullableCollection")
             @Nullable Bundle extras) {
         mInput = input;
         mTimestamp = timestamp;
@@ -69,6 +71,7 @@
     }
 
     @Nullable
+    @SuppressLint("NullableCollection")
     public Bundle getExtras() {
         return mExtras;
     }
diff --git a/core/java/android/app/search/SearchAction.java b/core/java/android/app/search/SearchAction.java
index a76154a..9e40e7e 100644
--- a/core/java/android/app/search/SearchAction.java
+++ b/core/java/android/app/search/SearchAction.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Intent;
@@ -167,6 +168,7 @@
     /**
      * Returns the extra bundle for this object.
      */
+    @SuppressLint("NullableCollection")
     public @Nullable Bundle getExtras() {
         return mExtras;
     }
@@ -325,7 +327,8 @@
          * Sets the extra.
          */
         @NonNull
-        public SearchAction.Builder setExtras(@Nullable Bundle extras) {
+        public SearchAction.Builder setExtras(
+                @SuppressLint("NullableCollection") @Nullable Bundle extras) {
             mExtras = extras;
             return this;
         }
diff --git a/core/java/android/app/search/SearchContext.java b/core/java/android/app/search/SearchContext.java
index 9bf766d..548b7da 100644
--- a/core/java/android/app/search/SearchContext.java
+++ b/core/java/android/app/search/SearchContext.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -52,7 +53,7 @@
 
     public SearchContext(int resultTypes,
             int queryTimeoutMillis,
-            @Nullable Bundle extras) {
+            @SuppressLint("NullableCollection") @Nullable Bundle extras) {
         mResultTypes = resultTypes;
         mTimeoutMillis = queryTimeoutMillis;
         mExtras = extras;
@@ -83,6 +84,7 @@
     }
 
     @Nullable
+    @SuppressLint("NullableCollection")
     public Bundle getExtras() {
         return mExtras;
     }
diff --git a/core/java/android/app/search/SearchTarget.java b/core/java/android/app/search/SearchTarget.java
index cac22d81..6a80f8b 100644
--- a/core/java/android/app/search/SearchTarget.java
+++ b/core/java/android/app/search/SearchTarget.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.pm.ShortcutInfo;
@@ -224,6 +225,7 @@
      * Return extra bundle.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public Bundle getExtras() {
         return mExtras;
     }
@@ -386,7 +388,7 @@
          * TODO: add comment
          */
         @NonNull
-        public Builder setExtras(@Nullable Bundle extras) {
+        public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
             mExtras = extras;
             return this;
         }
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
similarity index 78%
rename from apex/permission/service/java/com/android/role/package-info.java
rename to core/java/android/app/smartspace/ISmartspaceCallback.aidl
index 8b5b251..df105f9 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/core/java/android/app/smartspace/ISmartspaceCallback.aidl
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 
+package android.app.smartspace;
+
+import android.content.pm.ParceledListSlice;
+
 /**
  * @hide
- * TODO(b/146466118) remove this javadoc tag
  */
-@android.annotation.Hide
-package com.android.role;
+oneway interface ISmartspaceCallback {
+
+    void onResult(in ParceledListSlice result);
+}
diff --git a/core/java/android/app/smartspace/ISmartspaceManager.aidl b/core/java/android/app/smartspace/ISmartspaceManager.aidl
new file mode 100644
index 0000000..e7ec889
--- /dev/null
+++ b/core/java/android/app/smartspace/ISmartspaceManager.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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 android.app.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * @hide
+ */
+interface ISmartspaceManager {
+
+    void createSmartspaceSession(in SmartspaceConfig config, in SmartspaceSessionId sessionId,
+            in IBinder token);
+
+    void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+    void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+    void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+            in ISmartspaceCallback callback);
+
+    void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+            in ISmartspaceCallback callback);
+
+    void destroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/app/smartspace/OWNERS b/core/java/android/app/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/core/java/android/app/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceAction.java b/core/java/android/app/smartspace/SmartspaceAction.java
new file mode 100644
index 0000000..439d851
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceAction.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2021 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 android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceAction} represents an action which can be taken by a user by tapping on either
+ * the title, the subtitle or on the icon. Supported instances are Intents, PendingIntents or a
+ * ShortcutInfo (by putting the ShortcutInfoId in the bundle). These actions can be called from
+ * another process or within the client process.
+ *
+ * Clients can also receive conditional Intents/PendingIntents in the extras bundle which are
+ * supposed to be fired when the conditions are met. For example, a user can invoke a dismiss/block
+ * action on a game score card but the intention is to only block the team and not the entire
+ * feature.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceAction implements Parcelable {
+
+    private static final String TAG = "SmartspaceAction";
+
+    /** A unique Id of this {@link SmartspaceAction}. */
+    @NonNull
+    private final String mId;
+
+    /** An Icon which can be displayed in the UI. */
+    @Nullable
+    private final Icon mIcon;
+
+    /** Title associated with an action. */
+    @NonNull
+    private final CharSequence mTitle;
+
+    /** Subtitle associated with an action. */
+    @Nullable
+    private final CharSequence mSubtitle;
+
+    @Nullable
+    private final CharSequence mContentDescription;
+
+    @Nullable
+    private final PendingIntent mPendingIntent;
+
+    @Nullable
+    private final Intent mIntent;
+
+    @Nullable
+    private final UserHandle mUserHandle;
+
+    @Nullable
+    private Bundle mExtras;
+
+    SmartspaceAction(Parcel in) {
+        mId = in.readString();
+        mIcon = in.readTypedObject(Icon.CREATOR);
+        mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+        mIntent = in.readTypedObject(Intent.CREATOR);
+        mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+        mExtras = in.readTypedObject(Bundle.CREATOR);
+    }
+
+    private SmartspaceAction(
+            @NonNull String id,
+            @Nullable Icon icon,
+            @NonNull CharSequence title,
+            @Nullable CharSequence subtitle,
+            @Nullable CharSequence contentDescription,
+            @Nullable PendingIntent pendingIntent,
+            @Nullable Intent intent,
+            @Nullable UserHandle userHandle,
+            @Nullable Bundle extras) {
+        mId = Objects.requireNonNull(id);
+        mIcon = icon;
+        mTitle = Objects.requireNonNull(title);
+        mSubtitle = subtitle;
+        mContentDescription = contentDescription;
+        mPendingIntent = pendingIntent;
+        mIntent = intent;
+        mUserHandle = userHandle;
+        mExtras = extras;
+    }
+
+    /**
+     * Returns the unique id of this object.
+     */
+    public @NonNull String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns an icon representing the action.
+     */
+    public @Nullable Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Returns a title representing the action.
+     */
+    public @NonNull CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Returns a subtitle representing the action.
+     */
+    public @Nullable CharSequence getSubtitle() {
+        return mSubtitle;
+    }
+
+    /**
+     * Returns a content description representing the action.
+     */
+    public @Nullable CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * Returns the action intent.
+     */
+    public @Nullable PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    /**
+     * Returns the intent.
+     */
+    public @Nullable Intent getIntent() {
+        return mIntent;
+    }
+
+    /**
+     * Returns the user handle.
+     */
+    public @Nullable UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    /**
+     * Returns the extra bundle for this object.
+     */
+    @SuppressLint("NullableCollection")
+    public @Nullable Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SmartspaceAction)) return false;
+        SmartspaceAction that = (SmartspaceAction) o;
+        return mId.equals(that.mId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mId);
+        out.writeTypedObject(mIcon, flags);
+        TextUtils.writeToParcel(mTitle, out, flags);
+        TextUtils.writeToParcel(mSubtitle, out, flags);
+        TextUtils.writeToParcel(mContentDescription, out, flags);
+        out.writeTypedObject(mPendingIntent, flags);
+        out.writeTypedObject(mIntent, flags);
+        out.writeTypedObject(mUserHandle, flags);
+        out.writeBundle(mExtras);
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceAction{"
+                + "mId='" + mId + '\''
+                + ", mIcon=" + mIcon
+                + ", mTitle=" + mTitle
+                + ", mSubtitle=" + mSubtitle
+                + ", mContentDescription=" + mContentDescription
+                + ", mPendingIntent=" + mPendingIntent
+                + ", mIntent=" + mIntent
+                + ", mUserHandle=" + mUserHandle
+                + ", mExtras=" + mExtras
+                + '}';
+    }
+
+    public static final @NonNull Creator<SmartspaceAction> CREATOR =
+            new Creator<SmartspaceAction>() {
+                public SmartspaceAction createFromParcel(Parcel in) {
+                    return new SmartspaceAction(in);
+                }
+                public SmartspaceAction[] newArray(int size) {
+                    return new SmartspaceAction[size];
+                }
+            };
+
+    /**
+     * A builder for Smartspace action object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        @NonNull
+        private String mId;
+
+        @Nullable
+        private Icon mIcon;
+
+        @NonNull
+        private CharSequence mTitle;
+
+        @Nullable
+        private CharSequence mSubtitle;
+
+        @Nullable
+        private CharSequence mContentDescription;
+
+        @Nullable
+        private PendingIntent mPendingIntent;
+
+        @Nullable
+        private Intent mIntent;
+
+        @Nullable
+        private UserHandle mUserHandle;
+
+        @Nullable
+        private Bundle mExtras;
+
+        /**
+         * Id and title are required.
+         */
+        public Builder(@NonNull String id, @NonNull String title) {
+            mId = Objects.requireNonNull(id);
+            mTitle = Objects.requireNonNull(title);
+        }
+
+        /**
+         * Sets the icon.
+         */
+        @NonNull
+        public Builder setIcon(
+                @Nullable Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets the subtitle.
+         */
+        @NonNull
+        public Builder setSubtitle(
+                @Nullable CharSequence subtitle) {
+            mSubtitle = subtitle;
+            return this;
+        }
+
+        /**
+         * Sets the content description.
+         */
+        @NonNull
+        public Builder setContentDescription(
+                @Nullable CharSequence contentDescription) {
+            mContentDescription = contentDescription;
+            return this;
+        }
+
+        /**
+         * Sets the pending intent.
+         */
+        @NonNull
+        public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+            mPendingIntent = pendingIntent;
+            return this;
+        }
+
+        /**
+         * Sets the user handle.
+         */
+        @NonNull
+        public Builder setUserHandle(@Nullable UserHandle userHandle) {
+            mUserHandle = userHandle;
+            return this;
+        }
+
+        /**
+         * Sets the intent.
+         */
+        @NonNull
+        public Builder setIntent(@Nullable Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Sets the extra.
+         */
+        @NonNull
+        public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds a new SmartspaceAction instance.
+         *
+         * @throws IllegalStateException if no target is set
+         */
+        @NonNull
+        public SmartspaceAction build() {
+            return new SmartspaceAction(mId, mIcon, mTitle, mSubtitle, mContentDescription,
+                    mPendingIntent, mIntent, mUserHandle, mExtras);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/app/smartspace/SmartspaceConfig.aidl
similarity index 74%
copy from core/java/com/android/internal/net/VpnInfo.aidl
copy to core/java/android/app/smartspace/SmartspaceConfig.aidl
index 6fc97be..136b6f4 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/core/java/android/app/smartspace/SmartspaceConfig.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
+/**
+ * Copyright (c) 2021, 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
+ *     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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.app.smartspace;
 
-parcelable VpnInfo;
+parcelable SmartspaceConfig;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceConfig.java b/core/java/android/app/smartspace/SmartspaceConfig.java
new file mode 100644
index 0000000..07d7bf0
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceConfig.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 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 android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceConfig} instance is supposed to be created by a smartspace client for each
+ * UISurface. The client can specify some initialization conditions for the UISurface like its name,
+ * expected number of smartspace cards etc. The clients can also specify if they want periodic
+ * updates or their desired maximum refresh frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceConfig implements Parcelable {
+
+    /**
+     * The least number of smartspace targets expected to be predicted by the backend. The backend
+     * will always try to satisfy this threshold but it is not guaranteed to always meet it.
+     */
+    private final int mSmartspaceTargetCount;
+
+    /**
+     * A {@link mUiSurface} is the name of the surface which will be used to display the cards. A
+     * few examples are homescreen, lockscreen, aod etc.
+     */
+    @NonNull
+    private final String mUiSurface;
+
+    /** Package name of the client. */
+    @NonNull
+    private String mPackageName;
+
+    /** Send other client UI configurations in extras.
+     *
+     * This can include:
+     *
+     *  - Desired maximum update frequency
+     *  - Request to get periodic updates
+     *  - Request to support multiple clients for the same UISurface.
+     */
+    @Nullable
+    private final Bundle mExtras;
+
+    private SmartspaceConfig(@NonNull String uiSurface, int numPredictedTargets,
+            @NonNull String packageName, @Nullable Bundle extras) {
+        mUiSurface = uiSurface;
+        mSmartspaceTargetCount = numPredictedTargets;
+        mPackageName = packageName;
+        mExtras = extras;
+    }
+
+    private SmartspaceConfig(Parcel parcel) {
+        mUiSurface = parcel.readString();
+        mSmartspaceTargetCount = parcel.readInt();
+        mPackageName = parcel.readString();
+        mExtras = parcel.readBundle();
+    }
+
+    /** Returns the package name of the prediction context. */
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /** Returns the number of smartspace targets requested by the user. */
+    @NonNull
+    public int getSmartspaceTargetCount() {
+        return mSmartspaceTargetCount;
+    }
+
+    /** Returns the UISurface requested by the client. */
+    @NonNull
+    public String getUiSurface() {
+        return mUiSurface;
+    }
+
+    @Nullable
+    @SuppressLint("NullableCollection")
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mUiSurface);
+        dest.writeInt(mSmartspaceTargetCount);
+        dest.writeString(mPackageName);
+        dest.writeBundle(mExtras);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SmartspaceConfig that = (SmartspaceConfig) o;
+        return mSmartspaceTargetCount == that.mSmartspaceTargetCount
+                && Objects.equals(mUiSurface, that.mUiSurface)
+                && Objects.equals(mPackageName, that.mPackageName)
+                && Objects.equals(mExtras, that.mExtras);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSmartspaceTargetCount, mUiSurface, mPackageName, mExtras);
+    }
+
+    /**
+     * @see Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceConfig> CREATOR =
+            new Creator<SmartspaceConfig>() {
+                public SmartspaceConfig createFromParcel(Parcel parcel) {
+                    return new SmartspaceConfig(parcel);
+                }
+
+                public SmartspaceConfig[] newArray(int size) {
+                    return new SmartspaceConfig[size];
+                }
+            };
+
+    /**
+     * A builder for {@link SmartspaceConfig}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        @NonNull
+        private int mSmartspaceTargetCount = 5; // Default count is 5
+        @NonNull
+        private final String mUiSurface;
+        @NonNull
+        private final String mPackageName;
+        @NonNull
+        private Bundle mExtras = Bundle.EMPTY;
+
+        /**
+         * @param context The {@link Context} which is used to fetch the package name.
+         * @param uiSurface the UI Surface name associated with this context.
+         * @hide
+         */
+        @SystemApi
+        public Builder(@NonNull Context context, @NonNull String uiSurface) {
+            mPackageName = context.getPackageName();
+            this.mUiSurface = uiSurface;
+        }
+
+        /**
+         * Used to set the expected number of cards for this context.
+         */
+        @NonNull
+        public Builder setSmartspaceTargetCount(int smartspaceTargetCount) {
+            this.mSmartspaceTargetCount = smartspaceTargetCount;
+            return this;
+        }
+
+        /**
+         * Used to send a bundle containing extras for the {@link SmartspaceConfig}.
+         */
+        @NonNull
+        public Builder setExtras(@SuppressLint("NullableCollection") @NonNull Bundle extras) {
+            this.mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Returns an instance of {@link SmartspaceConfig}.
+         */
+        @NonNull
+        public SmartspaceConfig build() {
+            return new SmartspaceConfig(mUiSurface, mSmartspaceTargetCount, mPackageName, mExtras);
+        }
+    }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceManager.java b/core/java/android/app/smartspace/SmartspaceManager.java
new file mode 100644
index 0000000..ff5eb10
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceManager.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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 android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+
+import java.util.Objects;
+
+/**
+ * Smartspace is a container in Android which is used to show contextual content powered by the
+ * intelligence service running on the device. A smartspace container can be on AoD, lockscreen or
+ * on the homescreen and can show personalized cards which are either derived from on device or
+ * online signals.
+ *
+ * {@link SmartspaceManager} is a system service that provides methods to create Smartspace session
+ * clients. An instance of this class is returned when a client calls
+ * <code> context.getSystemService("smartspace"); </code>.
+ *
+ * After receiving the service, a client must call
+ * {@link SmartspaceManager#createSmartspaceSession(SmartspaceConfig)} with a corresponding
+ * {@link SmartspaceConfig} to get an instance of {@link SmartspaceSession}.
+ * This session is then a client's point of contact with the api. They can send events, request for
+ * updates using the session. It is client's duty to call {@link SmartspaceSession#destroy()} to
+ * destroy the session once they no longer need it.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceManager {
+
+    private final Context mContext;
+
+    /**
+     * @hide
+     */
+    public SmartspaceManager(Context context) {
+        mContext = Objects.requireNonNull(context);
+    }
+
+    /**
+     * Creates a new Smartspace session.
+     */
+    @NonNull
+    public SmartspaceSession createSmartspaceSession(
+            @NonNull SmartspaceConfig smartspaceConfig) {
+        return new SmartspaceSession(mContext, smartspaceConfig);
+    }
+}
diff --git a/core/java/android/app/smartspace/SmartspaceSession.java b/core/java/android/app/smartspace/SmartspaceSession.java
new file mode 100644
index 0000000..16def61
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSession.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2021 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 android.app.smartspace;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.app.smartspace.ISmartspaceCallback.Stub;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import dalvik.system.CloseGuard;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * Client API to share information about the Smartspace UI state and execute query.
+ *
+ * <p>
+ * Usage: <pre> {@code
+ *
+ * class MyActivity {
+ *    private SmartspaceSession mSmartspaceSession;
+ *
+ *    void onCreate() {
+ *         mSmartspaceSession = mSmartspaceManager.createSmartspaceSession(smartspaceConfig)
+ *         mSmartspaceSession.registerSmartspaceUpdates(...)
+ *    }
+ *
+ *    void onStart() {
+ *        mSmartspaceSession.requestSmartspaceUpdate()
+ *    }
+ *
+ *    void onTouch(...) OR
+ *    void onStateTransitionStarted(...) OR
+ *    void onResume(...) OR
+ *    void onStop(...) {
+ *        mSmartspaceSession.notifyEvent(event);
+ *    }
+ *
+ *    void onDestroy() {
+ *        mSmartspaceSession.unregisterPredictionUpdates()
+ *        mSmartspaceSession.destroy();
+ *    }
+ *
+ * }</pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSession implements AutoCloseable {
+
+    private static final String TAG = SmartspaceSession.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private final android.app.smartspace.ISmartspaceManager mInterface;
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+    private final SmartspaceSessionId mSessionId;
+    private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
+    private final IBinder mToken = new Binder();
+
+    /**
+     * Creates a new Smartspace ui client.
+     * <p>
+     * The caller should call {@link SmartspaceSession#destroy()} to dispose the client once it
+     * no longer used.
+     *
+     * @param context          the {@link Context} of the user of this {@link SmartspaceSession}.
+     * @param smartspaceConfig the Smartspace context.
+     */
+    // b/177858121 Create weak reference child objects to not leak context.
+    SmartspaceSession(@NonNull Context context, @NonNull SmartspaceConfig smartspaceConfig) {
+        IBinder b = ServiceManager.getService(Context.SMARTSPACE_SERVICE);
+        mInterface = android.app.smartspace.ISmartspaceManager.Stub.asInterface(b);
+        mSessionId = new SmartspaceSessionId(
+                context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+        try {
+            mInterface.createSmartspaceSession(smartspaceConfig, mSessionId, mToken);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to cerate Smartspace session", e);
+            e.rethrowFromSystemServer();
+        }
+
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * Notifies the Smartspace service of a Smartspace target event.
+     *
+     * @param event The {@link SmartspaceTargetEvent} that represents the Smartspace target event.
+     */
+    public void notifySmartspaceEvent(@NonNull SmartspaceTargetEvent event) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+        try {
+            mInterface.notifySmartspaceEvent(mSessionId, event);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to notify event", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests the smartspace service for an update.
+     */
+    public void requestSmartspaceUpdate() {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+        try {
+            mInterface.requestSmartspaceUpdate(mSessionId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to request update.", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests the smartspace service provide continuous updates of smartspace cards via the
+     * provided callback, until the given callback is unregistered.
+     *
+     * @param callbackExecutor The callback executor to use when calling the callback.
+     * @param callback         The Callback to be called when updates of Smartspace targets are
+     *                         available.
+     */
+    public void registerSmartspaceUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull Callback callback) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
+        if (mRegisteredCallbacks.containsKey(callback)) {
+            // Skip if this callback is already registered
+            return;
+        }
+        try {
+            final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+                    callback::onTargetsAvailable);
+            mRegisteredCallbacks.put(callback, callbackWrapper);
+            mInterface.registerSmartspaceUpdates(mSessionId, callbackWrapper);
+            mInterface.requestSmartspaceUpdate(mSessionId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register for smartspace updates", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Requests the smartspace service to stop providing continuous updates to the provided
+     * callback until the callback is re-registered.
+     *
+     * @see {@link SmartspaceSession#registerSmartspaceUpdates(Executor, Callback)}.
+     *
+     * @param callback The callback to be unregistered.
+     */
+    public void unregisterSmartspaceUpdates(@NonNull Callback callback) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
+        if (!mRegisteredCallbacks.containsKey(callback)) {
+            // Skip if this callback was never registered
+            return;
+        }
+        try {
+            final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+            mInterface.unregisterSmartspaceUpdates(mSessionId, callbackWrapper);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unregister for smartspace updates", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Destroys the client and unregisters the callback. Any method on this class after this call
+     * will throw {@link IllegalStateException}.
+     */
+    public void destroy() {
+        if (!mIsClosed.getAndSet(true)) {
+            mCloseGuard.close();
+
+            // Do destroy;
+            try {
+                mInterface.destroySmartspaceSession(mSessionId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to notify Smartspace target event", e);
+                e.rethrowFromSystemServer();
+            }
+        } else {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        try {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+            if (!mIsClosed.get()) {
+                destroy();
+            }
+        } finally {
+            try {
+                super.finalize();
+            } catch (Throwable throwable) {
+                throwable.printStackTrace();
+            }
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            finalize();
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+    }
+
+    /**
+     * Callback for receiving smartspace updates.
+     */
+    public interface Callback {
+
+        /**
+         * Called when a new set of smartspace targets are available.
+         *
+         * @param targets Sorted list of smartspace targets.
+         */
+        void onTargetsAvailable(@NonNull List<SmartspaceTarget> targets);
+    }
+
+    static class CallbackWrapper extends Stub {
+
+        private final Consumer<List<SmartspaceTarget>> mCallback;
+        private final Executor mExecutor;
+
+        CallbackWrapper(@NonNull Executor callbackExecutor,
+                @NonNull Consumer<List<SmartspaceTarget>> callback) {
+            mCallback = callback;
+            mExecutor = callbackExecutor;
+        }
+
+        @Override
+        public void onResult(ParceledListSlice result) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG) {
+                    Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
+                }
+                mExecutor.execute(() -> mCallback.accept(result.getList()));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
similarity index 74%
copy from core/java/com/android/internal/net/VpnInfo.aidl
copy to core/java/android/app/smartspace/SmartspaceSessionId.aidl
index 6fc97be..a864412 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
+/**
+ * Copyright (c) 2021, 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
+ *     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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.app.smartspace;
 
-parcelable VpnInfo;
+parcelable SmartspaceSessionId;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceSessionId.java b/core/java/android/app/smartspace/SmartspaceSessionId.java
new file mode 100644
index 0000000..5220c35
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceSessionId.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 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 android.app.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The id for an Smartspace session. See {@link SmartspaceSession}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceSessionId implements Parcelable {
+
+    @NonNull
+    private final String mId;
+
+    @NonNull
+    private final int mUserId;
+
+    /**
+     * Creates a new id for a Smartspace session.
+     *
+     * @hide
+     */
+    public SmartspaceSessionId(@NonNull final String id, @NonNull final int userId) {
+        mId = id;
+        mUserId = userId;
+    }
+
+    private SmartspaceSessionId(Parcel p) {
+        mId = p.readString();
+        mUserId = p.readInt();
+    }
+
+    /**
+     * Returns a {@link String} Id of this sessionId.
+     */
+    @Nullable
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the userId associated with this sessionId.
+     */
+    @NonNull
+    public int getUserId() {
+        return mUserId;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!getClass().equals(o != null ? o.getClass() : null)) return false;
+
+        SmartspaceSessionId other = (SmartspaceSessionId) o;
+        return mId.equals(other.mId) && mUserId == other.mUserId;
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceSessionId{"
+                + "mId='" + mId + '\''
+                + ", mUserId=" + mUserId
+                + '}';
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId, mUserId);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeInt(mUserId);
+    }
+
+    public static final @NonNull Creator<SmartspaceSessionId> CREATOR =
+            new Creator<SmartspaceSessionId>() {
+                public SmartspaceSessionId createFromParcel(Parcel parcel) {
+                    return new SmartspaceSessionId(parcel);
+                }
+
+                public SmartspaceSessionId[] newArray(int size) {
+                    return new SmartspaceSessionId[size];
+                }
+            };
+}
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/app/smartspace/SmartspaceTarget.aidl
similarity index 74%
copy from core/java/com/android/internal/net/VpnInfo.aidl
copy to core/java/android/app/smartspace/SmartspaceTarget.aidl
index 6fc97be..3442cf2 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/core/java/android/app/smartspace/SmartspaceTarget.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
+/**
+ * Copyright (c) 2021, 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
+ *     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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.app.smartspace;
 
-parcelable VpnInfo;
+parcelable SmartspaceTarget;
\ No newline at end of file
diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java
new file mode 100644
index 0000000..ce5040e
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTarget.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2021 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 android.app.smartspace;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link SmartspaceTarget} is a data class which holds all properties necessary to inflate a
+ * smartspace card. It contains data and related metadata which is supposed to be utilized by
+ * smartspace clients based on their own UI/UX requirements. Some of the properties have
+ * {@link SmartspaceAction} as their type because they can have associated actions.
+ *
+ * <p><b>NOTE: </b>
+ * If {@link mWidgetId} is set, it should be preferred over all other properties.
+ * Else, if {@link mSliceUri} is set, it should be preferred over all other data properties.
+ * Otherwise, the instance should be treated as a data object.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTarget implements Parcelable {
+
+    /** A unique Id for an instance of {@link SmartspaceTarget}. */
+    @NonNull
+    private final String mSmartspaceTargetId;
+
+    /** A {@link SmartspaceAction} for the header in the Smartspace card. */
+    @Nullable
+    private final SmartspaceAction mHeaderAction;
+
+    /** A {@link SmartspaceAction} for the base action in the Smartspace card. */
+    @Nullable
+    private final SmartspaceAction mBaseAction;
+
+    /** A timestamp indicating when the card was created. */
+    @NonNull
+    private final long mCreationTimeMillis;
+
+    /**
+     * A timestamp indicating when the card should be removed from view, in case the service
+     * disconnects or restarts.
+     */
+    @NonNull
+    private final long mExpiryTimeMillis;
+
+    /** A score assigned to a target. */
+    @NonNull
+    private final float mScore;
+
+    /** A {@link List<SmartspaceAction>} containing all action chips. */
+    @NonNull
+    private final List<SmartspaceAction> mActionChips;
+
+    /** A {@link List<SmartspaceAction>} containing all icons for the grid. */
+    @NonNull
+    private final List<SmartspaceAction> mIconGrid;
+
+    /**
+     * {@link FeatureType} indicating the feature type of this card.
+     *
+     * @see FeatureType
+     */
+    @FeatureType
+    @NonNull
+    private final int mFeatureType;
+
+    /**
+     * Indicates whether the content is sensitive. Certain UI surfaces may choose to skip rendering
+     * real content until the device is unlocked.
+     */
+    @NonNull
+    private final boolean mSensitive;
+
+    /** Indicating if the UI should show this target in its expanded state. */
+    @NonNull
+    private final boolean mShouldShowExpanded;
+
+    /** A Notification key if the target was generated using a notification. */
+    @Nullable
+    private final String mSourceNotificationKey;
+
+    /** {@link ComponentName} for this target. */
+    @NonNull
+    private final ComponentName mComponentName;
+
+    /** {@link UserHandle} for this target. */
+    @NonNull
+    private final UserHandle mUserHandle;
+
+    /** Target Ids of other {@link SmartspaceTarget}s if they are associated with this target. */
+    @Nullable
+    private final String mAssociatedSmartspaceTargetId;
+
+    /** {@link Uri} Slice Uri if this target is a slice. */
+    @Nullable
+    private final Uri mSliceUri;
+
+    /** {@link AppWidgetProviderInfo} if this target is a widget. */
+    @Nullable
+    private final AppWidgetProviderInfo mWidgetId;
+
+    public static final int FEATURE_UNDEFINED = 0;
+    public static final int FEATURE_WEATHER = 1;
+    public static final int FEATURE_CALENDAR = 2;
+    public static final int FEATURE_COMMUTE_TIME = 3;
+    public static final int FEATURE_FLIGHT = 4;
+    public static final int FEATURE_TIPS = 5;
+    public static final int FEATURE_REMINDER = 6;
+    public static final int FEATURE_ALARM = 7;
+    public static final int FEATURE_ONBOARDING = 8;
+    public static final int FEATURE_SPORTS = 9;
+    public static final int FEATURE_WEATHER_ALERT = 10;
+    public static final int FEATURE_CONSENT = 11;
+    public static final int FEATURE_STOCK_PRICE_CHANGE = 12;
+    public static final int FEATURE_SHOPPING_LIST = 13;
+    public static final int FEATURE_LOYALTY_CARD = 14;
+    public static final int FEATURE_MEDIA = 15;
+    public static final int FEATURE_BEDTIME_ROUTINE = 16;
+    public static final int FEATURE_FITNESS_TRACKING = 17;
+    public static final int FEATURE_ETA_MONITORING = 18;
+    public static final int FEATURE_MISSED_CALL = 19;
+    public static final int FEATURE_PACKAGE_TRACKING = 20;
+    public static final int FEATURE_TIMER = 21;
+    public static final int FEATURE_STOPWATCH = 22;
+    public static final int FEATURE_UPCOMING_ALARM = 23;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"FEATURE_"}, value = {
+            FEATURE_UNDEFINED,
+            FEATURE_WEATHER,
+            FEATURE_CALENDAR,
+            FEATURE_COMMUTE_TIME,
+            FEATURE_FLIGHT,
+            FEATURE_TIPS,
+            FEATURE_REMINDER,
+            FEATURE_ALARM,
+            FEATURE_ONBOARDING,
+            FEATURE_SPORTS,
+            FEATURE_WEATHER_ALERT,
+            FEATURE_CONSENT,
+            FEATURE_STOCK_PRICE_CHANGE,
+            FEATURE_SHOPPING_LIST,
+            FEATURE_LOYALTY_CARD,
+            FEATURE_MEDIA,
+            FEATURE_BEDTIME_ROUTINE,
+            FEATURE_FITNESS_TRACKING,
+            FEATURE_ETA_MONITORING,
+            FEATURE_MISSED_CALL,
+            FEATURE_PACKAGE_TRACKING,
+            FEATURE_TIMER,
+            FEATURE_STOPWATCH,
+            FEATURE_UPCOMING_ALARM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FeatureType {
+    }
+
+    private SmartspaceTarget(Parcel in) {
+        this.mSmartspaceTargetId = in.readString();
+        this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR);
+        this.mBaseAction = in.readTypedObject(SmartspaceAction.CREATOR);
+        this.mCreationTimeMillis = in.readLong();
+        this.mExpiryTimeMillis = in.readLong();
+        this.mScore = in.readFloat();
+        this.mActionChips = in.createTypedArrayList(SmartspaceAction.CREATOR);
+        this.mIconGrid = in.createTypedArrayList(SmartspaceAction.CREATOR);
+        this.mFeatureType = in.readInt();
+        this.mSensitive = in.readBoolean();
+        this.mShouldShowExpanded = in.readBoolean();
+        this.mSourceNotificationKey = in.readString();
+        this.mComponentName = in.readTypedObject(ComponentName.CREATOR);
+        this.mUserHandle = in.readTypedObject(UserHandle.CREATOR);
+        this.mAssociatedSmartspaceTargetId = in.readString();
+        this.mSliceUri = in.readTypedObject(Uri.CREATOR);
+        this.mWidgetId = in.readTypedObject(AppWidgetProviderInfo.CREATOR);
+    }
+
+    private SmartspaceTarget(String smartspaceTargetId,
+            SmartspaceAction headerAction, SmartspaceAction baseAction, long creationTimeMillis,
+            long expiryTimeMillis, float score,
+            List<SmartspaceAction> actionChips,
+            List<SmartspaceAction> iconGrid, int featureType, boolean sensitive,
+            boolean shouldShowExpanded, String sourceNotificationKey,
+            ComponentName componentName, UserHandle userHandle,
+            String associatedSmartspaceTargetId, Uri sliceUri,
+            AppWidgetProviderInfo widgetId) {
+        mSmartspaceTargetId = smartspaceTargetId;
+        mHeaderAction = headerAction;
+        mBaseAction = baseAction;
+        mCreationTimeMillis = creationTimeMillis;
+        mExpiryTimeMillis = expiryTimeMillis;
+        mScore = score;
+        mActionChips = actionChips;
+        mIconGrid = iconGrid;
+        mFeatureType = featureType;
+        mSensitive = sensitive;
+        mShouldShowExpanded = shouldShowExpanded;
+        mSourceNotificationKey = sourceNotificationKey;
+        mComponentName = componentName;
+        mUserHandle = userHandle;
+        mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+        mSliceUri = sliceUri;
+        mWidgetId = widgetId;
+    }
+
+    /**
+     * Returns the Id of the target.
+     */
+    @NonNull
+    public String getSmartspaceTargetId() {
+        return mSmartspaceTargetId;
+    }
+
+    /**
+     * Returns the header action of the target.
+     */
+    @Nullable
+    public SmartspaceAction getHeaderAction() {
+        return mHeaderAction;
+    }
+
+    /**
+     * Returns the base action of the target.
+     */
+    @Nullable
+    public SmartspaceAction getBaseAction() {
+        return mBaseAction;
+    }
+
+    /**
+     * Returns the creation time of the target.
+     */
+    @NonNull
+    public long getCreationTimeMillis() {
+        return mCreationTimeMillis;
+    }
+
+    /**
+     * Returns the expiry time of the target.
+     */
+    @NonNull
+    public long getExpiryTimeMillis() {
+        return mExpiryTimeMillis;
+    }
+
+    /**
+     * Returns the score of the target.
+     */
+    @NonNull
+    public float getScore() {
+        return mScore;
+    }
+
+    /**
+     * Return the action chips of the target.
+     */
+    @NonNull
+    public List<SmartspaceAction> getActionChips() {
+        return mActionChips;
+    }
+
+    /**
+     * Return the icons of the target.
+     */
+    @NonNull
+    public List<SmartspaceAction> getIconGrid() {
+        return mIconGrid;
+    }
+
+    /**
+     * Returns the feature type of the target.
+     */
+    @NonNull
+    public int getFeatureType() {
+        return mFeatureType;
+    }
+
+    /**
+     * Returns whether the target is sensitive or not.
+     */
+    @NonNull
+    public boolean isSensitive() {
+        return mSensitive;
+    }
+
+    /**
+     * Returns whether the target should be shown in expanded state.
+     */
+    @NonNull
+    public boolean shouldShowExpanded() {
+        return mShouldShowExpanded;
+    }
+
+    /**
+     * Returns the source notification key of the target.
+     */
+    @Nullable
+    public String getSourceNotificationKey() {
+        return mSourceNotificationKey;
+    }
+
+    /**
+     * Returns the component name of the target.
+     */
+    @NonNull
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * Returns the user handle of the target.
+     */
+    @NonNull
+    public UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    /**
+     * Returns the id of a target associated with this instance.
+     */
+    @Nullable
+    public String getAssociatedSmartspaceTargetId() {
+        return mAssociatedSmartspaceTargetId;
+    }
+
+    /**
+     * Returns the slice uri, if the target is a slice.
+     */
+    @Nullable
+    public Uri getSliceUri() {
+        return mSliceUri;
+    }
+
+    /**
+     * Returns the AppWidgetProviderInfo, if the target is a widget.
+     */
+    @Nullable
+    public AppWidgetProviderInfo getWidgetId() {
+        return mWidgetId;
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceTarget> CREATOR = new Creator<SmartspaceTarget>() {
+        @Override
+        public SmartspaceTarget createFromParcel(Parcel source) {
+            return new SmartspaceTarget(source);
+        }
+
+        @Override
+        public SmartspaceTarget[] newArray(int size) {
+            return new SmartspaceTarget[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(this.mSmartspaceTargetId);
+        dest.writeTypedObject(this.mHeaderAction, flags);
+        dest.writeTypedObject(this.mBaseAction, flags);
+        dest.writeLong(this.mCreationTimeMillis);
+        dest.writeLong(this.mExpiryTimeMillis);
+        dest.writeFloat(this.mScore);
+        dest.writeTypedList(this.mActionChips);
+        dest.writeTypedList(this.mIconGrid);
+        dest.writeInt(this.mFeatureType);
+        dest.writeBoolean(this.mSensitive);
+        dest.writeBoolean(this.mShouldShowExpanded);
+        dest.writeString(this.mSourceNotificationKey);
+        dest.writeTypedObject(this.mComponentName, flags);
+        dest.writeTypedObject(this.mUserHandle, flags);
+        dest.writeString(this.mAssociatedSmartspaceTargetId);
+        dest.writeTypedObject(this.mSliceUri, flags);
+        dest.writeTypedObject(this.mWidgetId, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "SmartspaceTarget{"
+                + "mSmartspaceTargetId='" + mSmartspaceTargetId + '\''
+                + ", mHeaderAction=" + mHeaderAction
+                + ", mBaseAction=" + mBaseAction
+                + ", mCreationTimeMillis=" + mCreationTimeMillis
+                + ", mExpiryTimeMillis=" + mExpiryTimeMillis
+                + ", mScore=" + mScore
+                + ", mActionChips=" + mActionChips
+                + ", mIconGrid=" + mIconGrid
+                + ", mFeatureType=" + mFeatureType
+                + ", mSensitive=" + mSensitive
+                + ", mShouldShowExpanded=" + mShouldShowExpanded
+                + ", mSourceNotificationKey='" + mSourceNotificationKey + '\''
+                + ", mComponentName=" + mComponentName
+                + ", mUserHandle=" + mUserHandle
+                + ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\''
+                + ", mSliceUri=" + mSliceUri
+                + ", mWidgetId=" + mWidgetId
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SmartspaceTarget that = (SmartspaceTarget) o;
+        return mCreationTimeMillis == that.mCreationTimeMillis
+                && mExpiryTimeMillis == that.mExpiryTimeMillis
+                && Float.compare(that.mScore, mScore) == 0
+                && mFeatureType == that.mFeatureType
+                && mSensitive == that.mSensitive
+                && mShouldShowExpanded == that.mShouldShowExpanded
+                && mSmartspaceTargetId.equals(that.mSmartspaceTargetId)
+                && Objects.equals(mHeaderAction, that.mHeaderAction)
+                && Objects.equals(mBaseAction, that.mBaseAction)
+                && Objects.equals(mActionChips, that.mActionChips)
+                && Objects.equals(mIconGrid, that.mIconGrid)
+                && Objects.equals(mSourceNotificationKey, that.mSourceNotificationKey)
+                && mComponentName.equals(that.mComponentName)
+                && mUserHandle.equals(that.mUserHandle)
+                && Objects.equals(mAssociatedSmartspaceTargetId,
+                that.mAssociatedSmartspaceTargetId)
+                && Objects.equals(mSliceUri, that.mSliceUri)
+                && Objects.equals(mWidgetId, that.mWidgetId);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis,
+                mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive,
+                mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle,
+                mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+    }
+
+    /**
+     * A builder for {@link SmartspaceTarget} object.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        private final String mSmartspaceTargetId;
+        private SmartspaceAction mHeaderAction;
+        private SmartspaceAction mBaseAction;
+        private long mCreationTimeMillis;
+        private long mExpiryTimeMillis;
+        private float mScore;
+        private List<SmartspaceAction> mActionChips = new ArrayList<>();
+        private List<SmartspaceAction> mIconGrid = new ArrayList<>();
+        private int mFeatureType;
+        private boolean mSensitive;
+        private boolean mShouldShowExpanded;
+        private String mSourceNotificationKey;
+        private final ComponentName mComponentName;
+        private final UserHandle mUserHandle;
+        private String mAssociatedSmartspaceTargetId;
+        private Uri mSliceUri;
+        private AppWidgetProviderInfo mWidgetId;
+
+        /**
+         * A builder for {@link SmartspaceTarget}.
+         *
+         * @param smartspaceTargetId the id of this target
+         * @param componentName      the componentName of this target
+         * @param userHandle         the userHandle of this target
+         */
+        public Builder(@NonNull String smartspaceTargetId,
+                @NonNull ComponentName componentName, @NonNull UserHandle userHandle) {
+            this.mSmartspaceTargetId = smartspaceTargetId;
+            this.mComponentName = componentName;
+            this.mUserHandle = userHandle;
+        }
+
+        /**
+         * Sets the header action.
+         */
+        @NonNull
+        public Builder setHeaderAction(@NonNull SmartspaceAction headerAction) {
+            this.mHeaderAction = headerAction;
+            return this;
+        }
+
+        /**
+         * Sets the base action.
+         */
+        @NonNull
+        public Builder setBaseAction(@NonNull SmartspaceAction baseAction) {
+            this.mBaseAction = baseAction;
+            return this;
+        }
+
+        /**
+         * Sets the creation time.
+         */
+        @NonNull
+        public Builder setCreationTimeMillis(@NonNull long creationTimeMillis) {
+            this.mCreationTimeMillis = creationTimeMillis;
+            return this;
+        }
+
+        /**
+         * Sets the expiration time.
+         */
+        @NonNull
+        public Builder setExpiryTimeMillis(@NonNull long expiryTimeMillis) {
+            this.mExpiryTimeMillis = expiryTimeMillis;
+            return this;
+        }
+
+        /**
+         * Sets the score.
+         */
+        @NonNull
+        public Builder setScore(@NonNull float score) {
+            this.mScore = score;
+            return this;
+        }
+
+        /**
+         * Sets the action chips.
+         */
+        @NonNull
+        public Builder setActionChips(@NonNull List<SmartspaceAction> actionChips) {
+            this.mActionChips = actionChips;
+            return this;
+        }
+
+        /**
+         * Sets the icon grid.
+         */
+        @NonNull
+        public Builder setIconGrid(@NonNull List<SmartspaceAction> iconGrid) {
+            this.mIconGrid = iconGrid;
+            return this;
+        }
+
+        /**
+         * Sets the feature type.
+         */
+        @NonNull
+        public Builder setFeatureType(@NonNull int featureType) {
+            this.mFeatureType = featureType;
+            return this;
+        }
+
+        /**
+         * Sets whether the contents are sensitive.
+         */
+        @NonNull
+        public Builder setSensitive(@NonNull boolean sensitive) {
+            this.mSensitive = sensitive;
+            return this;
+        }
+
+        /**
+         * Sets whether to show the card as expanded.
+         */
+        @NonNull
+        public Builder setShouldShowExpanded(@NonNull boolean shouldShowExpanded) {
+            this.mShouldShowExpanded = shouldShowExpanded;
+            return this;
+        }
+
+        /**
+         * Sets the source notification key.
+         */
+        @NonNull
+        public Builder setSourceNotificationKey(@NonNull String sourceNotificationKey) {
+            this.mSourceNotificationKey = sourceNotificationKey;
+            return this;
+        }
+
+        /**
+         * Sets the associated smartspace target id.
+         */
+        @NonNull
+        public Builder setAssociatedSmartspaceTargetId(
+                @NonNull String associatedSmartspaceTargetId) {
+            this.mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId;
+            return this;
+        }
+
+        /**
+         * Sets the slice uri.
+         *
+         * <p><b>NOTE: </b> If {@link mWidgetId} is also set, {@link mSliceUri} should be ignored.
+         */
+        @NonNull
+        public Builder setSliceUri(@NonNull Uri sliceUri) {
+            this.mSliceUri = sliceUri;
+            return this;
+        }
+
+        /**
+         * Sets the widget id.
+         *
+         * <p><b>NOTE: </b> If {@link mWidgetId} is set, all other @Nullable params should be
+         * ignored.
+         */
+        @NonNull
+        public Builder setWidgetId(@NonNull AppWidgetProviderInfo widgetId) {
+            this.mWidgetId = widgetId;
+            return this;
+        }
+
+        /**
+         * Builds a new {@link SmartspaceTarget}.
+         *
+         * @throws IllegalStateException when non null fields are set as null.
+         */
+        @NonNull
+        public SmartspaceTarget build() {
+            if (mSmartspaceTargetId == null
+                    || mComponentName == null
+                    || mUserHandle == null) {
+                throw new IllegalStateException("Please assign a value to all @NonNull args.");
+            }
+            return new SmartspaceTarget(mSmartspaceTargetId,
+                    mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore,
+                    mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded,
+                    mSourceNotificationKey, mComponentName, mUserHandle,
+                    mAssociatedSmartspaceTargetId, mSliceUri, mWidgetId);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
similarity index 74%
copy from core/java/com/android/internal/net/VpnInfo.aidl
copy to core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
index 6fc97be..e797a9b 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
+/**
+ * Copyright (c) 2021, 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
+ *     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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.app.smartspace;
 
-parcelable VpnInfo;
+parcelable SmartspaceTargetEvent;
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
new file mode 100644
index 0000000..1e0653d
--- /dev/null
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 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 android.app.smartspace;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A representation of a smartspace event.
+ *
+ * @hide
+ */
+@SystemApi
+public final class SmartspaceTargetEvent implements Parcelable {
+
+    /**
+     * User interacted with the target.
+     */
+    public static final int EVENT_TARGET_INTERACTION = 1;
+
+    /**
+     * Smartspace target was brought into view.
+     */
+    public static final int EVENT_TARGET_IN_VIEW = 2;
+    /**
+     * Smartspace target went out of view.
+     */
+    public static final int EVENT_TARGET_OUT_OF_VIEW = 3;
+    /**
+     * A dismiss action was issued by the user.
+     */
+    public static final int EVENT_TARGET_DISMISS = 4;
+    /**
+     * A block action was issued by the user.
+     */
+    public static final int EVENT_TARGET_BLOCK = 5;
+    /**
+     * The Ui surface came into view.
+     */
+    public static final int EVENT_UI_SURFACE_IN_VIEW = 6;
+    /**
+     * The Ui surface went out of view.
+     */
+    public static final int EVENT_UI_SURFACE_OUT_OF_VIEW = 7;
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<SmartspaceTargetEvent> CREATOR =
+            new Creator<SmartspaceTargetEvent>() {
+                public SmartspaceTargetEvent createFromParcel(Parcel parcel) {
+                    return new SmartspaceTargetEvent(parcel);
+                }
+
+                public SmartspaceTargetEvent[] newArray(int size) {
+                    return new SmartspaceTargetEvent[size];
+                }
+            };
+
+    @Nullable
+    private final SmartspaceTarget mSmartspaceTarget;
+
+    @Nullable
+    private final String mSmartspaceActionId;
+
+    @EventType
+    private final int mEventType;
+
+    private SmartspaceTargetEvent(@Nullable SmartspaceTarget smartspaceTarget,
+            @Nullable String smartspaceActionId,
+            @EventType int eventType) {
+        mSmartspaceTarget = smartspaceTarget;
+        mSmartspaceActionId = smartspaceActionId;
+        mEventType = eventType;
+    }
+
+    private SmartspaceTargetEvent(Parcel parcel) {
+        mSmartspaceTarget = parcel.readParcelable(null);
+        mSmartspaceActionId = parcel.readString();
+        mEventType = parcel.readInt();
+    }
+
+    /**
+     * Get the {@link SmartspaceTarget} associated with this event.
+     */
+    @Nullable
+    public SmartspaceTarget getSmartspaceTarget() {
+        return mSmartspaceTarget;
+    }
+
+    /**
+     * Get the action id of the Smartspace Action associated with this event.
+     */
+    @Nullable
+    public String getSmartspaceActionId() {
+        return mSmartspaceActionId;
+    }
+
+    /**
+     * Get the {@link EventType} of this event.
+     */
+    @NonNull
+    @EventType
+    public int getEventType() {
+        return mEventType;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mSmartspaceTarget, flags);
+        dest.writeString(mSmartspaceActionId);
+        dest.writeInt(mEventType);
+    }
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"EVENT_"}, value = {
+            EVENT_TARGET_INTERACTION,
+            EVENT_TARGET_IN_VIEW,
+            EVENT_TARGET_OUT_OF_VIEW,
+            EVENT_TARGET_DISMISS,
+            EVENT_TARGET_BLOCK,
+            EVENT_UI_SURFACE_IN_VIEW,
+            EVENT_UI_SURFACE_OUT_OF_VIEW
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EventType {
+    }
+
+    /**
+     * A builder for {@link SmartspaceTargetEvent}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Builder {
+        @EventType
+        private final int mEventType;
+        @Nullable
+        private SmartspaceTarget mSmartspaceTarget;
+        @Nullable
+        private String mSmartspaceActionId;
+
+        /**
+         * A builder for {@link SmartspaceTargetEvent}.
+         */
+        public Builder(@EventType int eventType) {
+            mEventType = eventType;
+        }
+
+        /**
+         * Sets the SmartspaceTarget for this event.
+         */
+        @NonNull
+        public Builder setSmartspaceTarget(@NonNull SmartspaceTarget smartspaceTarget) {
+            mSmartspaceTarget = smartspaceTarget;
+            return this;
+        }
+
+        /**
+         * Sets the Smartspace action id for this event.
+         */
+        @NonNull
+        public Builder setSmartspaceActionId(@NonNull String smartspaceActionId) {
+            mSmartspaceActionId = smartspaceActionId;
+            return this;
+        }
+
+        /**
+         * Builds a new {@link SmartspaceTargetEvent} instance.
+         */
+        @NonNull
+        public SmartspaceTargetEvent build() {
+            return new SmartspaceTargetEvent(mSmartspaceTarget, mSmartspaceActionId, mEventType);
+        }
+    }
+}
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 1ddfe0d..1d5dc1d 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -28,7 +28,6 @@
 import android.net.ConnectivityManager;
 import android.net.DataUsageRequest;
 import android.net.INetworkStatsService;
-import android.net.NetworkIdentity;
 import android.net.NetworkStack;
 import android.net.NetworkTemplate;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
@@ -47,6 +46,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.NetworkIdentityUtils;
 
 import java.util.Objects;
 
@@ -628,7 +628,7 @@
             default:
                 throw new IllegalArgumentException("Cannot create template for network type "
                         + networkType + ", subscriberId '"
-                        + NetworkIdentity.scrubSubscriberId(subscriberId) + "'.");
+                        + NetworkIdentityUtils.scrubSubscriberId(subscriberId) + "'.");
         }
         return template;
     }
diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java
new file mode 100644
index 0000000..7281d50
--- /dev/null
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 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 android.apphibernation;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * This class provides an API surface for system apps to manipulate the app hibernation
+ * state of a package for the user provided in the context.
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.APP_HIBERNATION_SERVICE)
+public final class AppHibernationManager {
+    private static final String TAG = "AppHibernationManager";
+    private final Context mContext;
+    private final IAppHibernationService mIAppHibernationService;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param context The current context associated with the user
+     *
+     * @hide
+     */
+    public AppHibernationManager(@NonNull Context context) {
+        mContext = context;
+        mIAppHibernationService = IAppHibernationService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_HIBERNATION_SERVICE));
+    }
+
+    /**
+     * Returns true if the package is hibernating for this context's user, false otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isHibernatingForUser(@NonNull String packageName) {
+        try {
+            return mIAppHibernationService.isHibernatingForUser(packageName, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set whether the package is hibernating for this context's user.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void setHibernatingForUser(@NonNull String packageName, boolean isHibernating) {
+        try {
+            mIAppHibernationService.setHibernatingForUser(packageName, mContext.getUserId(),
+                    isHibernating);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if app is hibernating globally / at the package level.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isHibernatingGlobally(@NonNull String packageName) {
+        try {
+            return mIAppHibernationService.isHibernatingGlobally(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set whether a package should be globally hibernating. This hibernates the package at a
+     * package level. User-level hibernation (e.g.. {@link #isHibernatingForUser} is independent
+     * from global hibernation.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void setHibernatingGlobally(@NonNull String packageName, boolean isHibernating) {
+        try {
+            mIAppHibernationService.setHibernatingGlobally(packageName, isHibernating);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl
new file mode 100644
index 0000000..6a068ee
--- /dev/null
+++ b/core/java/android/apphibernation/IAppHibernationService.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 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 android.apphibernation;
+
+/**
+ * Binder interface to communicate with AppHibernationService.
+ * @hide
+ */
+interface IAppHibernationService {
+    boolean isHibernatingForUser(String packageName, int userId);
+    void setHibernatingForUser(String packageName, int userId, boolean isHibernating);
+    boolean isHibernatingGlobally(String packageName);
+    void setHibernatingGlobally(String packageName, boolean isHibernating);
+}
\ No newline at end of file
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 93d96d0..e96e22c4 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -406,6 +406,14 @@
         return new UserHandle(UserHandle.getUserId(providerInfo.applicationInfo.uid));
     }
 
+    /**
+     * Returns the broadcast receiver that is providing this widget.
+     */
+    @NonNull
+    public ActivityInfo getProviderInfo() {
+        return providerInfo;
+    }
+
     @Override
     @SuppressWarnings("deprecation")
     public void writeToParcel(Parcel out, int flags) {
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index d6b38fd..4fb5577 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -80,7 +80,7 @@
 
     /**
      * Intent used to broadcast the change in the Audio Connection state of the
-     * HDP profile.
+     * HFP profile.
      *
      * <p>This intent will have 3 extras:
      * <ul>
diff --git a/core/java/android/bluetooth/le/AdvertiseData.java b/core/java/android/bluetooth/le/AdvertiseData.java
index 397326c..cec6580 100644
--- a/core/java/android/bluetooth/le/AdvertiseData.java
+++ b/core/java/android/bluetooth/le/AdvertiseData.java
@@ -44,7 +44,7 @@
     @Nullable
     private final List<ParcelUuid> mServiceUuids;
 
-    @Nullable
+    @NonNull
     private final List<ParcelUuid> mServiceSolicitationUuids;
 
     private final SparseArray<byte[]> mManufacturerSpecificData;
@@ -77,7 +77,7 @@
     /**
      * Returns a list of service solicitation UUIDs within the advertisement that we invite to connect.
      */
-    @Nullable
+    @NonNull
     public List<ParcelUuid> getServiceSolicitationUuids() {
         return mServiceSolicitationUuids;
     }
@@ -221,7 +221,7 @@
     public static final class Builder {
         @Nullable
         private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
-        @Nullable
+        @NonNull
         private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>();
         private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
         private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 083ce96..102c98f 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -55,6 +55,8 @@
         genBuilder = false)
 public final class AssociationRequest implements Parcelable {
 
+    private static final String LOG_TAG = AssociationRequest.class.getSimpleName();
+
     /**
      * Device profile: watch.
      *
@@ -115,13 +117,6 @@
         mDeviceProfilePrivilegesDescription = desc;
     }
 
-    private void onConstructed() {
-        if (mDeviceProfile != null
-                && !Objects.equals(mDeviceProfile, DEVICE_PROFILE_WATCH)) {
-            throw new IllegalArgumentException("Invalid device profile: " + mDeviceProfile);
-        }
-    }
-
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean isSingleDevice() {
@@ -252,7 +247,7 @@
         this.mCallingPackage = callingPackage;
         this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
 
-        onConstructed();
+        // onConstructed(); // You can define this method to get a callback
     }
 
     /**
@@ -386,7 +381,7 @@
         this.mCallingPackage = callingPackage;
         this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
 
-        onConstructed();
+        // onConstructed(); // You can define this method to get a callback
     }
 
     @DataClass.Generated.Member
@@ -404,10 +399,10 @@
     };
 
     @DataClass.Generated(
-            time = 1610132130920L,
+            time = 1611692924843L,
             codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
-            inputSignatures = "public static final  java.lang.String DEVICE_PROFILE_WATCH\nprivate  boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\npublic  void setCallingPackage(java.lang.String)\npublic  void setDeviceProfilePrivilegesDescription(java.lang.String)\nprivate  void onConstructed()\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)")
+            inputSignatures = "private static final  java.lang.String LOG_TAG\npublic static final  java.lang.String DEVICE_PROFILE_WATCH\nprivate  boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\npublic  void setCallingPackage(java.lang.String)\npublic  void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/AutofillOptions.java b/core/java/android/content/AutofillOptions.java
index 80a7b16..0581ed5 100644
--- a/core/java/android/content/AutofillOptions.java
+++ b/core/java/android/content/AutofillOptions.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.os.Parcel;
@@ -62,6 +63,7 @@
      * List of allowlisted activities.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public ArraySet<ComponentName> whitelistedActivitiesForAugmentedAutofill;
 
     /**
@@ -73,6 +75,7 @@
      * The disabled Activities of the package. key is component name string, value is when they
      * will be enabled.
      */
+    @SuppressLint("NullableCollection")
     @Nullable
     public ArrayMap<String, Long> disabledActivities;
 
diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java
index ef49e02..c296bb5 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.os.Parcel;
@@ -73,6 +74,7 @@
      * for all acitivites in the package).
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public final ArraySet<ComponentName> whitelistedComponents;
 
     /**
@@ -96,6 +98,7 @@
      */
     public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs,
             int textChangeFlushingFrequencyMs, int logHistorySize,
+            @SuppressLint("NullableCollection")
             @Nullable ArraySet<ComponentName> whitelistedComponents) {
         this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs,
                 textChangeFlushingFrequencyMs, logHistorySize, whitelistedComponents);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fcb0f4f..2a402b2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -37,6 +37,7 @@
 import android.annotation.UiContext;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.GameManager;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.VrManager;
@@ -64,7 +65,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.StatFs;
-import android.os.StrictMode;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -75,6 +75,7 @@
 import android.view.DisplayAdjustments;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams.WindowType;
 import android.view.autofill.AutofillManager.AutofillClient;
@@ -3551,6 +3552,7 @@
             //@hide: NETWORK_SCORE_SERVICE,
             USAGE_STATS_SERVICE,
             MEDIA_SESSION_SERVICE,
+            MEDIA_COMMUNICATION_SERVICE,
             BATTERY_SERVICE,
             JOB_SCHEDULER_SERVICE,
             //@hide: PERSISTENT_DATA_BLOCK_SERVICE,
@@ -3575,6 +3577,7 @@
             LIGHTS_SERVICE,
             //@hide: PEOPLE_SERVICE,
             //@hide: DEVICE_STATE_SERVICE,
+            //@hide: SPEECH_RECOGNITION_SERVICE,
             UWB_SERVICE,
             MEDIA_METRICS_SERVICE,
     })
@@ -4371,6 +4374,16 @@
     public static final String BIOMETRIC_SERVICE = "biometric";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.media.MediaCommunicationManager}
+     * for managing {@link android.media.MediaSession2}.
+     *
+     * @see #getSystemService(String)
+     * @see android.media.MediaCommunicationManager
+     */
+    public static final String MEDIA_COMMUNICATION_SERVICE = "media_communication";
+
+    /**
      * Use with {@link #getSystemService} to retrieve a
      * {@link android.media.MediaRouter} for controlling and managing
      * routing of media.
@@ -4615,6 +4628,18 @@
     public static final String SEARCH_UI_SERVICE = "search_ui";
 
     /**
+     * Used for getting the smartspace service.
+     *
+     * <p><b>NOTE: </b> this service is optional; callers of
+     * {@code Context.getSystemServiceName(SMARTSPACE_SERVICE)} should check for {@code null}.
+     *
+     * @hide
+     * @see #getSystemService(String)
+     */
+    @SystemApi
+    public static final String SMARTSPACE_SERVICE = "smartspace";
+
+    /**
      * Use with {@link #getSystemService(String)} to access the
      * {@link com.android.server.voiceinteraction.SoundTriggerService}.
      *
@@ -4668,6 +4693,17 @@
     public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
 
     /**
+     * Use with {@link #getSystemService(String) to retrieve an
+     * {@link android.apphibernation.AppHibernationManager}} for
+     * communicating with the hibernation service.
+     * @hide
+     *
+     * @see #getSystemService(String)
+     */
+    @SystemApi
+    public static final String APP_HIBERNATION_SERVICE = "app_hibernation";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve an
      * {@link android.app.backup.IBackupManager IBackupManager} for communicating
      * with the backup mechanism.
@@ -5399,6 +5435,33 @@
     public static final String MEDIA_METRICS_SERVICE = "media_metrics";
 
     /**
+     * Use with {@link #getSystemService(String)} to access system speech recognition service.
+     *
+     * @see #getSystemService(String)
+     * @hide
+    */
+    public static final String SPEECH_RECOGNITION_SERVICE = "speech_recognition";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link GameManager}.
+     *
+     * @see #getSystemService(String)
+     *
+     * @hide
+     */
+    public static final String GAME_SERVICE = "game";
+
+    /**
+     * Use with {@link #getSystemService(String)} to access domain verification service.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    @SystemApi
+    public static final String DOMAIN_VERIFICATION_SERVICE = "domain_verification";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6049,18 +6112,19 @@
      *
      * // WindowManager.LayoutParams initialization
      * ...
+     * // The types used in addView and createWindowContext must match.
      * mParams.type = TYPE_APPLICATION_OVERLAY;
      * ...
      *
-     * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
+     * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
      * </pre>
      *
      * <p>
-     * This context's configuration and resources are adjusted to a display area where the windows
-     * with provided type will be added. <b>Note that all windows associated with the same context
-     * will have an affinity and can only be moved together between different displays or areas on a
-     * display.</b> If there is a need to add different window types, or non-associated windows,
-     * separate Contexts should be used.
+     * This context's configuration and resources are adjusted to an area of the display where
+     * the windows with provided type will be added. <b>Note that all windows associated with the
+     * same context will have an affinity and can only be moved together between different displays
+     * or areas on a display.</b> If there is a need to add different window types, or
+     * non-associated windows, separate Contexts should be used.
      * </p>
      * <p>
      * Creating a window context is an expensive operation. Misuse of this API may lead to a huge
@@ -6068,7 +6132,43 @@
      * An approach is to create one window context with specific window type and display and
      * use it everywhere it's needed.
      * </p>
+     * <p>
+     * After {@link Build.VERSION_CODES#S}, window context provides the capability to receive
+     * configuration changes for existing token by overriding the
+     * {@link android.view.WindowManager.LayoutParams#token token} of the
+     * {@link android.view.WindowManager.LayoutParams} passed in
+     * {@link WindowManager#addView(View, LayoutParams)}. This is useful when an application needs
+     * to attach its window to an existing activity for window token sharing use-case.
+     * </p>
+     * <p>
+     * Note that the window context in {@link Build.VERSION_CODES#R} didn't have this
+     * capability. This is a no-op for the window context in {@link Build.VERSION_CODES#R}.
+     * </p>
+     * Below is sample code to <b>attach an existing token to a window context:</b>
+     * <pre class="prettyprint">
+     * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class);
+     * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+     * final Context windowContext = anyContext.createWindowContext(primaryDisplay,
+     *         TYPE_APPLICATION, null);
      *
+     * // Get an existing token.
+     * final IBinder existingToken = activity.getWindow().getAttributes().token;
+     *
+     * // The types used in addView() and createWindowContext() must match.
+     * final WindowManager.LayoutParams params = new WindowManager.LayoutParams(TYPE_APPLICATION);
+     * params.token = existingToken;
+     *
+     * // After WindowManager#addView(), the server side will extract the provided token from
+     * // LayoutParams#token (existingToken in the sample code), and switch to propagate
+     * // configuration changes from the node associated with the provided token.
+     * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
+     * </pre>
+     * <p>
+     * Note that using {@link android.app.Application} or {@link android.app.Service} context for
+     * UI-related queries may result in layout or continuity issues on devices with variable screen
+     * sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect
+     * the {@link Configuration} changes for the visual container.
+     * </p>
      * @param type Window type in {@link WindowManager.LayoutParams}
      * @param options A bundle used to pass window-related options
      * @return A {@link Context} that can be used to create
@@ -6080,9 +6180,7 @@
      * @see #LAYOUT_INFLATER_SERVICE
      * @see #WALLPAPER_SERVICE
      * @throws UnsupportedOperationException if this {@link Context} does not attach to a display,
-     * such as {@link android.app.Application Application} or {@link android.app.Service Service},
-     * or the current number of window contexts without adding any view by
-     * {@link WindowManager#addView} <b>exceeds five</b>.
+     * such as {@link android.app.Application Application} or {@link android.app.Service Service}.
      */
     @UiContext
     @NonNull
@@ -6115,6 +6213,7 @@
     @UiContext
     @NonNull
     public Context createWindowContext(@NonNull Display display, @WindowType int type,
+            @SuppressLint("NullableCollection")
             @Nullable Bundle options) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
@@ -6458,7 +6557,7 @@
      * {@link WindowManager}, {@link android.view.LayoutInflater LayoutInflater} or
      * {@link android.app.WallpaperManager WallpaperManager}. Accessing UI components from non-UI
      * contexts throws {@link android.os.strictmode.Violation} if
-     * {@link StrictMode.VmPolicy.Builder#detectIncorrectContextUse()} is enabled.
+     * {@link android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()} is enabled.
      * <p>
      * Examples of UI contexts are
      * an {@link android.app.Activity Activity}, a context created from
@@ -6468,7 +6567,7 @@
      *
      * @see #getDisplay()
      * @see #getSystemService(String)
-     * @see StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
+     * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
      */
     public static boolean isUiContext(@NonNull Context context) {
         return context.isUiContext();
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1752b48..30b2404 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -37,6 +37,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
@@ -2841,10 +2842,28 @@
      * </p>
      *
      * @hide
+     * @deprecated Superseded by domain verification APIs. See {@link DomainVerificationManager}.
+     */
+    @Deprecated
+    @SystemApi
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION =
+            "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+
+
+    /**
+     * Broadcast Action: Sent to the system domain verification agent when an app's domains need
+     * to be verified. The data contains the domains hosts to be verified against.
+     * <p class="note">
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
      */
     @SystemApi
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+    public static final String ACTION_DOMAINS_NEED_VERIFICATION =
+            "android.intent.action.DOMAINS_NEED_VERIFICATION";
 
     /**
      * Broadcast Action: Resources for a set of packages (which were
diff --git a/core/java/android/content/integrity/OWNERS b/core/java/android/content/integrity/OWNERS
new file mode 100644
index 0000000..20c758a
--- /dev/null
+++ b/core/java/android/content/integrity/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 722021
+
+toddke@android.com
+toddke@google.com
+patb@google.com
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index e32068f..6ec1169 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -38,6 +38,8 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Parcelling;
+import com.android.internal.util.Parcelling.BuiltIn.ForBoolean;
 import com.android.server.SystemConfig;
 
 import java.lang.annotation.Retention;
@@ -56,6 +58,8 @@
  * &lt;application&gt; tag.
  */
 public class ApplicationInfo extends PackageItemInfo implements Parcelable {
+    private static ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
+
     /**
      * Default task affinity of all activities in this application. See 
      * {@link ActivityInfo#taskAffinity} for more information.  This comes 
@@ -1336,6 +1340,51 @@
     private @GwpAsanMode int gwpAsanMode;
 
     /**
+     * Default (unspecified) setting of Memtag.
+     */
+    public static final int MEMTAG_DEFAULT = -1;
+
+    /**
+     * Do not enable Memtag in this application or process.
+     */
+    public static final int MEMTAG_OFF = 0;
+
+    /**
+     * Enable Memtag in Async mode in this application or process.
+     */
+    public static final int MEMTAG_ASYNC = 1;
+
+    /**
+     * Enable Memtag in Sync mode in this application or process.
+     */
+    public static final int MEMTAG_SYNC = 2;
+
+    /**
+     * These constants need to match the values of memtagMode in application manifest.
+     * @hide
+     */
+    @IntDef(prefix = {"MEMTAG_"}, value = {
+            MEMTAG_DEFAULT,
+            MEMTAG_OFF,
+            MEMTAG_ASYNC,
+            MEMTAG_SYNC,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MemtagMode {}
+
+    /**
+     * Indicates if the application has requested Memtag to be enabled, disabled, or left
+     * unspecified. Processes can override this setting.
+     */
+    private @MemtagMode int memtagMode;
+
+    /**
+     * Enable automatic zero-initialization of native heap memory allocations.
+     */
+    @Nullable
+    private Boolean nativeHeapZeroInit;
+
+    /**
      * Represents the default policy. The actual policy used will depend on other properties of
      * the application, e.g. the target SDK version.
      * @hide
@@ -1479,6 +1528,12 @@
             if (gwpAsanMode != GWP_ASAN_DEFAULT) {
                 pw.println(prefix + "gwpAsanMode=" + gwpAsanMode);
             }
+            if (memtagMode != MEMTAG_DEFAULT) {
+                pw.println(prefix + "memtagMode=" + memtagMode);
+            }
+            if (nativeHeapZeroInit != null) {
+                pw.println(prefix + "nativeHeapZeroInit=" + nativeHeapZeroInit);
+            }
         }
         super.dumpBack(pw, prefix);
     }
@@ -1580,6 +1635,12 @@
             if (gwpAsanMode != GWP_ASAN_DEFAULT) {
                 proto.write(ApplicationInfoProto.Detail.ENABLE_GWP_ASAN, gwpAsanMode);
             }
+            if (memtagMode != MEMTAG_DEFAULT) {
+                proto.write(ApplicationInfoProto.Detail.ENABLE_MEMTAG, memtagMode);
+            }
+            if (nativeHeapZeroInit != null) {
+                proto.write(ApplicationInfoProto.Detail.NATIVE_HEAP_ZERO_INIT, nativeHeapZeroInit);
+            }
             proto.end(detailToken);
         }
         proto.end(token);
@@ -1690,6 +1751,8 @@
         hiddenUntilInstalled = orig.hiddenUntilInstalled;
         zygotePreloadName = orig.zygotePreloadName;
         gwpAsanMode = orig.gwpAsanMode;
+        memtagMode = orig.memtagMode;
+        nativeHeapZeroInit = orig.nativeHeapZeroInit;
     }
 
     public String toString() {
@@ -1774,6 +1837,8 @@
         dest.writeInt(hiddenUntilInstalled ? 1 : 0);
         dest.writeString8(zygotePreloadName);
         dest.writeInt(gwpAsanMode);
+        dest.writeInt(memtagMode);
+        sForBoolean.parcel(nativeHeapZeroInit, dest, parcelableFlags);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1855,6 +1920,8 @@
         hiddenUntilInstalled = source.readInt() != 0;
         zygotePreloadName = source.readString8();
         gwpAsanMode = source.readInt();
+        memtagMode = source.readInt();
+        nativeHeapZeroInit = sForBoolean.unparcel(source);
     }
 
     /**
@@ -2237,6 +2304,8 @@
     /** {@hide} */ public void setBaseResourcePath(String baseResourcePath) { publicSourceDir = baseResourcePath; }
     /** {@hide} */ public void setSplitResourcePaths(String[] splitResourcePaths) { splitPublicSourceDirs = splitResourcePaths; }
     /** {@hide} */ public void setGwpAsanMode(@GwpAsanMode int value) { gwpAsanMode = value; }
+    /** {@hide} */ public void setMemtagMode(@MemtagMode int value) { memtagMode = value; }
+    /** {@hide} */ public void setNativeHeapZeroInit(@Nullable Boolean value) { nativeHeapZeroInit = value; }
 
     /** {@hide} */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2250,4 +2319,8 @@
     /** {@hide} */ public String[] getSplitResourcePaths() { return splitPublicSourceDirs; }
     @GwpAsanMode
     public int getGwpAsanMode() { return gwpAsanMode; }
+    @MemtagMode
+    public int getMemtagMode() { return memtagMode; }
+    @Nullable
+    public Boolean isNativeHeapZeroInit() { return nativeHeapZeroInit; }
 }
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/core/java/android/content/pm/IOnChecksumsReadyListener.aidl
similarity index 71%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to core/java/android/content/pm/IOnChecksumsReadyListener.aidl
index 8b5b251..7963ce1 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/core/java/android/content/pm/IOnChecksumsReadyListener.aidl
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 
+package android.content.pm;
+
+import android.content.pm.ApkChecksum;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Listener that gets notified when checksums are available.
+ * {@hide}
  */
-@android.annotation.Hide
-package com.android.role;
+oneway interface IOnChecksumsReadyListener {
+    void onChecksumsReady(in List<ApkChecksum> checksums);
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 34d1003..a46876e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -27,6 +27,7 @@
 import android.content.pm.FeatureInfo;
 import android.content.pm.IDexModuleRegisterCallback;
 import android.content.pm.InstallSourceInfo;
+import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageDeleteObserver2;
@@ -627,9 +628,13 @@
     void verifyPendingInstall(int id, int verificationCode);
     void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
 
+    /** @deprecated */
     void verifyIntentFilter(int id, int verificationCode, in List<String> failedDomains);
+    /** @deprecated */
     int getIntentVerificationStatus(String packageName, int userId);
+    /** @deprecated */
     boolean updateIntentVerificationStatus(String packageName, int status, int userId);
+    /** @deprecated */
     ParceledListSlice getIntentFilterVerifications(String packageName);
     ParceledListSlice getAllIntentFilters(String packageName);
 
@@ -750,7 +755,7 @@
 
     void notifyPackagesReplacedReceived(in String[] packages);
 
-    void requestChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IntentSender statusReceiver, int userId);
+    void requestChecksums(in String packageName, boolean includeSplits, int optional, int required, in List trustedInstallers, in IOnChecksumsReadyListener onChecksumsReadyListener, int userId);
 
     //------------------------------------------------------------------------
     //
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 82d7b63..16e720e 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -17,6 +17,7 @@
 package android.content.pm;
 
 import android.annotation.FloatRange;
+import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -32,8 +33,6 @@
  * and badged icon for the activity.
  */
 public class LauncherActivityInfo {
-    private static final String TAG = "LauncherActivityInfo";
-
     private final PackageManager mPm;
     private UserHandle mUser;
     private final LauncherActivityInfoInternal mInternal;
@@ -81,7 +80,7 @@
      */
     public CharSequence getLabel() {
         // TODO: Go through LauncherAppsService
-        return mInternal.getActivityInfo().loadLabel(mPm);
+        return getActivityInfo().loadLabel(mPm);
     }
 
     /**
@@ -101,20 +100,20 @@
      */
     public Drawable getIcon(int density) {
         // TODO: Go through LauncherAppsService
-        final int iconRes = mInternal.getActivityInfo().getIconResource();
+        final int iconRes = getActivityInfo().getIconResource();
         Drawable icon = null;
         // Get the preferred density icon from the app's resources
         if (density != 0 && iconRes != 0) {
             try {
                 final Resources resources = mPm.getResourcesForApplication(
-                        mInternal.getActivityInfo().applicationInfo);
+                        getActivityInfo().applicationInfo);
                 icon = resources.getDrawableForDensity(iconRes, density);
             } catch (NameNotFoundException | Resources.NotFoundException exc) {
             }
         }
         // Get the default density icon
         if (icon == null) {
-            icon = mInternal.getActivityInfo().loadIcon(mPm);
+            icon = getActivityInfo().loadIcon(mPm);
         }
         return icon;
     }
@@ -126,25 +125,25 @@
      * @hide remove before shipping
      */
     public int getApplicationFlags() {
-        return mInternal.getActivityInfo().flags;
+        return getActivityInfo().flags;
     }
 
     /**
      * Returns the ActivityInfo of the activity.
      *
      * @return Activity Info
-     * @hide
      */
+    @NonNull
     public ActivityInfo getActivityInfo() {
         return mInternal.getActivityInfo();
     }
 
     /**
-     * Returns the application info for the appliction this activity belongs to.
+     * Returns the application info for the application this activity belongs to.
      * @return
      */
     public ApplicationInfo getApplicationInfo() {
-        return mInternal.getActivityInfo().applicationInfo;
+        return getActivityInfo().applicationInfo;
     }
 
     /**
@@ -155,7 +154,7 @@
     public long getFirstInstallTime() {
         try {
             // TODO: Go through LauncherAppsService
-            return mPm.getPackageInfo(mInternal.getActivityInfo().packageName,
+            return mPm.getPackageInfo(getActivityInfo().packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES).firstInstallTime;
         } catch (NameNotFoundException nnfe) {
             // Sorry, can't find package
@@ -164,11 +163,11 @@
     }
 
     /**
-     * Returns the name for the acitivty from  android:name in the manifest.
-     * @return the name from android:name for the acitivity.
+     * Returns the name for the activity from  android:name in the manifest.
+     * @return the name from android:name for the activity.
      */
     public String getName() {
-        return mInternal.getActivityInfo().name;
+        return getActivityInfo().name;
     }
 
     /**
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 6292575..0c0e402 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -450,6 +450,7 @@
                 FLAG_MATCH_PINNED,
                 FLAG_MATCH_MANIFEST,
                 FLAG_MATCH_CACHED,
+                FLAG_MATCH_PINNED_BY_ANY_LAUNCHER,
                 FLAG_GET_KEY_FIELDS_ONLY,
                 FLAG_GET_PERSONS_DATA,
         })
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index f991306..e8ef077 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -222,7 +222,7 @@
      * &lt;attribution&gt;} tags included under &lt;manifest&gt;, or null if there were none. This
      * is only filled if the flag {@link PackageManager#GET_ATTRIBUTIONS} was set.
      */
-    @SuppressWarnings("ArrayReturn")
+    @SuppressWarnings({"ArrayReturn", "NullableCollection"})
     public @Nullable Attribution[] attributions;
 
     /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4ffdf67..b95b991b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -47,18 +47,15 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.verify.domain.DomainVerificationManager;
 import android.content.pm.dex.ArtManager;
-import android.content.pm.parsing.PackageInfoWithoutStateUtils;
-import android.content.pm.parsing.ParsingPackage;
-import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.result.ParseInput;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
 import android.net.wifi.WifiManager;
 import android.os.Build;
 import android.os.Bundle;
@@ -74,6 +71,12 @@
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.permission.PermissionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.gba.GbaService;
+import android.telephony.ims.ImsService;
+import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsUceAdapter;
+import android.telephony.ims.SipDelegateManager;
 import android.util.AndroidException;
 import android.util.Log;
 
@@ -82,7 +85,6 @@
 
 import dalvik.system.VMRuntime;
 
-import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.cert.Certificate;
@@ -92,6 +94,7 @@
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * Class for retrieving various kinds of information related to the application
@@ -124,6 +127,19 @@
     }
 
     /**
+     * &lt;application&gt; level {@link android.content.pm.PackageManager.Property} tag specifying
+     * the XML resource ID containing an application's media capabilities XML file
+     *
+     * For example:
+     * &lt;application&gt;
+     *   &lt;property android:name="android.media.PROPERTY_MEDIA_CAPABILITIES"
+     *     android:resource="@xml/media_capabilities"&gt;
+     * &lt;application&gt;
+     */
+    public static final String PROPERTY_MEDIA_CAPABILITIES =
+            "android.media.PROPERTY_MEDIA_CAPABILITIES";
+
+    /**
      * A property value set within the manifest.
      * <p>
      * The value of a property will only have a single type, as defined by
@@ -2187,8 +2203,10 @@
      * {@link PackageManager#verifyIntentFilter} to indicate that the calling
      * IntentFilter Verifier confirms that the IntentFilter is verified.
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1;
 
@@ -2197,16 +2215,20 @@
      * {@link PackageManager#verifyIntentFilter} to indicate that the calling
      * IntentFilter Verifier confirms that the IntentFilter is NOT verified.
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1;
 
     /**
      * Internal status code to indicate that an IntentFilter verification result is not specified.
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
 
@@ -2216,8 +2238,10 @@
      * will always be prompted the Intent Disambiguation Dialog if there are two
      * or more Intent resolved for the IntentFilter's domain(s).
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
 
@@ -2228,8 +2252,10 @@
      * or more resolution of the Intent. The default App for the domain(s)
      * specified in the IntentFilter will also ALWAYS be used.
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
 
@@ -2240,8 +2266,10 @@
      * Intent resolved. The default App for the domain(s) specified in the
      * IntentFilter will also NEVER be presented to the User.
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
 
@@ -2254,8 +2282,10 @@
      * more than one candidate app, then a disambiguation is *always* presented
      * even if there is another candidate app with the 'always' state.
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK = 4;
 
@@ -2443,6 +2473,35 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports
+     * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware
+     * at the given feature version.
+     *
+     * <p>Known feature versions include:
+     * <ul>
+     * <li><code>202009</code>: corresponds to the features included in the Identity Credential
+     * API shipped in Android 11.
+     * <li><code>202101</code>: corresponds to the features included in the Identity Credential
+     * API shipped in Android 12.
+     * </ul>
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE =
+            "android.hardware.identity_credential";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports
+     * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware
+     * with direct access at the given feature version.
+     * See {@link #FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known feature versions.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS =
+            "android.hardware.identity_credential_direct_access";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports one or more methods of
      * reporting current location.
      */
@@ -2899,6 +2958,37 @@
     public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports a single IMS registration as defined by carrier networks in the IMS service
+     * implementation using the {@link ImsService} API, {@link GbaService} API, and IRadio 1.6 HAL.
+     * <p>
+     * When set, the device must fully support the following APIs for an application to implement
+     * IMS single registration:
+     * <ul>
+     * <li> Updating RCS provisioning status using the {@link ProvisioningManager} API to supply an
+     * RCC.14 defined XML and notify IMS applications of Auto Configuration Server (ACS) or
+     * proprietary server provisioning updates.</li>
+     * <li>Opening a delegate in the device IMS service to forward SIP traffic to the carrier's
+     * network using the {@link SipDelegateManager} API</li>
+     * <li>Listening to EPS dedicated bearer establishment via the
+     * {@link ConnectivityManager#registerQosCallback}
+     * API to indicate to the application when to start/stop media traffic.</li>
+     * <li>Implementing Generic Bootstrapping Architecture (GBA) and providing the associated
+     * authentication keys to applications
+     * requesting this information via the {@link TelephonyManager#bootstrapAuthenticationRequest}
+     * API</li>
+     * <li>Implementing RCS User Capability Exchange using the {@link RcsUceAdapter} API</li>
+     * </ul>
+     * <p>
+     * This feature should only be defined if {@link #FEATURE_TELEPHONY_IMS} is also defined.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION =
+            "android.hardware.telephony.ims.singlereg";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device is capable of communicating with
      * other devices via ultra wideband.
@@ -3356,6 +3446,7 @@
      * {@link #hasSystemFeature}: This device supports HDMI-CEC.
      * @hide
      */
+    @TestApi
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
 
@@ -3551,6 +3642,24 @@
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * a Keystore implementation that can only enforce limited use key in hardware with max usage
+     * count equals to 1.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY =
+            "android.hardware.keystore.single_use_key";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * a Keystore implementation that can enforce limited use key in hardware with any max usage
+     * count (including count equals to 1).
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
+            "android.hardware.keystore.limited_use_key";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
@@ -3650,8 +3759,10 @@
      * Passed to an intent filter verifier and is used to call back to
      * {@link #verifyIntentFilter}
      *
+     * @deprecated Use DomainVerificationManager APIs.
      * @hide
      */
+    @Deprecated
     public static final String EXTRA_INTENT_FILTER_VERIFICATION_ID
             = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_ID";
 
@@ -3661,8 +3772,10 @@
      *
      * Usually this is "https"
      *
+     * @deprecated Use DomainVerificationManager APIs.
      * @hide
      */
+    @Deprecated
     public static final String EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME
             = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_URI_SCHEME";
 
@@ -3673,8 +3786,10 @@
      *
      * This is a space delimited list of hosts.
      *
+     * @deprecated Use DomainVerificationManager APIs.
      * @hide
      */
+    @Deprecated
     public static final String EXTRA_INTENT_FILTER_VERIFICATION_HOSTS
             = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_HOSTS";
 
@@ -3684,8 +3799,10 @@
      * from the hosts. Each host response will need to include the package name of APK containing
      * the intent filter.
      *
+     * @deprecated Use DomainVerificationManager APIs.
      * @hide
      */
+    @Deprecated
     public static final String EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME
             = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_PACKAGE_NAME";
 
@@ -3741,13 +3858,6 @@
     public static final String EXTRA_FAILURE_EXISTING_PERMISSION
             = "android.content.pm.extra.FAILURE_EXISTING_PERMISSION";
 
-    /**
-     * Extra field name for the ID of a package pending verification. Passed to
-     * a package verifier and is used to call back to
-     * @see #requestChecksums
-     */
-    public static final String EXTRA_CHECKSUMS = "android.content.pm.extra.CHECKSUMS";
-
    /**
     * Permission flag: The permission is set in its current state
     * by the user and apps can still request it at runtime.
@@ -3942,6 +4052,15 @@
     public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT =  1 << 18;
 
     /**
+     * Permission flag: This location permission is selected as the level of granularity of
+     * location accuracy.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY =  1 << 19;
+
+    /**
      * Permission flags: Reserved for use by the permission controller. The platform and any
      * packages besides the permission controller should not assume any definition about these
      * flags.
@@ -5450,7 +5569,7 @@
      *
      * @hide
      */
-    @SuppressWarnings("HiddenAbstractMethod")
+    @SuppressWarnings({"HiddenAbstractMethod", "NullableCollection"})
     @TestApi
     public abstract @Nullable String[] getNamesForUids(int[] uids);
 
@@ -6664,6 +6783,22 @@
             throws NameNotFoundException;
 
     /**
+     * Retrieve the resources for an application for the provided configuration.
+     *
+     * @param app Information about the desired application.
+     * @param configuration Overridden configuration when loading the Resources
+     *
+     * @return Returns the application's Resources.
+     * @throws NameNotFoundException Thrown if the resources for the given
+     * application could not be loaded (most likely because it was uninstalled).
+     */
+    @NonNull
+    public Resources getResourcesForApplication(@NonNull ApplicationInfo app, @Nullable
+            Configuration configuration) throws NameNotFoundException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Retrieve the resources associated with an application.  Given the full
      * package name of an application, retrieves the information about it and
      * calls getResources() to return its application's resources.  If the
@@ -6722,25 +6857,8 @@
     @Nullable
     public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
             @PackageInfoFlags int flags) {
-        if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
-            // Caller expressed no opinion about what encryption
-            // aware/unaware components they want to see, so match both
-            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
-                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-        }
-
-        boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
-                || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
-
-        ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
-        ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
-                new File(archiveFilePath), 0, collectCertificates);
-        if (result.isError()) {
-            return null;
-        }
-        return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
-                new PackageUserState(), UserHandle.getCallingUserId());
+        throw new UnsupportedOperationException(
+                "getPackageArchiveInfo() not implemented in subclass");
     }
 
     /**
@@ -6855,8 +6973,10 @@
      * @throws SecurityException if the caller does not have the
      *            INTENT_FILTER_VERIFICATION_AGENT permission.
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @SystemApi
     @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT)
@@ -6881,8 +7001,10 @@
      *              {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} or
      *              {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED}
      *
+     * @deprecated Use {@link DomainVerificationManager} APIs.
      * @hide
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @SystemApi
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL)
@@ -6907,8 +7029,18 @@
      *
      * @return true if the status has been set. False otherwise.
      *
+     * @deprecated This API represents a very dangerous behavior where Settings or a system app with
+     * the right permissions can force an application to be verified for all of its declared
+     * domains. This has been removed to prevent unintended usage, and no longer does anything,
+     * always returning false. If a caller truly wishes to grant <i></i>every</i> declared web
+     * domain to an application, use
+     * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)},
+     * passing in all of the domains returned inside
+     * {@link DomainVerificationManager#getDomainVerificationUserSelection(String)}.
+     *
      * @hide
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @SystemApi
     @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
@@ -6925,8 +7057,10 @@
      *
      * @return a list of IntentFilterVerificationInfo for a specific package.
      *
+     * @deprecated Use {@link DomainVerificationManager} instead.
      * @hide
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @SystemApi
@@ -8568,9 +8702,20 @@
      */
     public static final @NonNull List<Certificate> TRUST_NONE = Collections.singletonList(null);
 
+    /** Listener that gets notified when checksums are available. */
+    @FunctionalInterface
+    public interface OnChecksumsReadyListener {
+        /**
+         * Called when the checksums are available.
+         *
+         * @param checksums array of checksums.
+         */
+        void onChecksumsReady(@NonNull List<ApkChecksum> checksums);
+    }
+
     /**
      * Requesting the checksums for APKs within a package.
-     * The checksums will be returned asynchronously via statusReceiver.
+     * The checksums will be returned asynchronously via onChecksumsReadyListener.
      *
      * By default returns all readily available checksums:
      * - enforced by platform,
@@ -8589,15 +8734,14 @@
      *                          {@link #TRUST_ALL} will return checksums from any installer,
      *                          {@link #TRUST_NONE} disables optimized installer-enforced checksums,
      *                          otherwise the list has to be non-empty list of certificates.
-     * @param statusReceiver called once when the results are available as
-     *                       {@link #EXTRA_CHECKSUMS} of type {@link ApkChecksum}[].
+     * @param onChecksumsReadyListener called once when the results are available.
      * @throws CertificateEncodingException if an encoding error occurs for trustedInstallers.
      * @throws IllegalArgumentException if the list of trusted installer certificates is empty.
      * @throws NameNotFoundException if a package with the given name cannot be found on the system.
      */
     public void requestChecksums(@NonNull String packageName, boolean includeSplits,
             @Checksum.Type int required, @NonNull List<Certificate> trustedInstallers,
-            @NonNull IntentSender statusReceiver)
+            @NonNull OnChecksumsReadyListener onChecksumsReadyListener)
             throws CertificateEncodingException, NameNotFoundException {
         throw new UnsupportedOperationException("requestChecksums not implemented in subclass");
     }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 45f072a..e6c0f6a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -53,8 +54,6 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.split.DefaultSplitAssetLoader;
-import android.content.pm.split.SplitAssetDependencyLoader;
 import android.content.pm.split.SplitAssetLoader;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
@@ -80,6 +79,7 @@
 import android.util.AttributeSet;
 import android.util.Base64;
 import android.util.DisplayMetrics;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.PackageUtils;
 import android.util.Pair;
@@ -118,6 +118,7 @@
 import java.security.spec.X509EncodedKeySpec;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
@@ -6264,6 +6265,55 @@
         }
 
         /**
+         * Returns whether this {@code SigningDetails} has a signer in common with the provided
+         * {@code otherDetails} with the specified {@code flags} capabilities provided by this
+         * signer.
+         *
+         * <p>Note this method allows for the signing lineage to diverge, so this should only be
+         * used for instances where the only requirement is a common signer in the lineage with
+         * the specified capabilities. If the current signer of this instance is an ancestor of
+         * {@code otherDetails} then {@code true} is immediately returned since the current signer
+         * has all capabilities granted.
+         */
+        public boolean hasCommonSignerWithCapability(SigningDetails otherDetails,
+                @CertCapabilities int flags) {
+            if (this == UNKNOWN || otherDetails == UNKNOWN) {
+                return false;
+            }
+            // If either is signed with more than one signer then both must be signed by the same
+            // signers to consider the capabilities granted.
+            if (signatures.length > 1 || otherDetails.signatures.length > 1) {
+                return signaturesMatchExactly(otherDetails);
+            }
+            // The Signature class does not use the granted capabilities in the hashCode
+            // computation, so a Set can be used to check for a common signer.
+            Set<Signature> otherSignatures = new ArraySet<>();
+            if (otherDetails.hasPastSigningCertificates()) {
+                otherSignatures.addAll(Arrays.asList(otherDetails.pastSigningCertificates));
+            } else {
+                otherSignatures.addAll(Arrays.asList(otherDetails.signatures));
+            }
+            // If the current signer of this instance is an ancestor of the other than return true
+            // since all capabilities are granted to the current signer.
+            if (otherSignatures.contains(signatures[0])) {
+                return true;
+            }
+            if (hasPastSigningCertificates()) {
+                // Since the current signer was checked above and the last signature in the
+                // pastSigningCertificates is the current signer skip checking the last element.
+                for (int i = 0; i < pastSigningCertificates.length - 1; i++) {
+                    if (otherSignatures.contains(pastSigningCertificates[i])) {
+                        // If the caller specified multiple capabilities ensure all are set.
+                        if ((pastSigningCertificates[i].getFlags() & flags) == flags) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+
+        /**
          * Determines if the provided {@code oldDetails} is an ancestor of this one, and whether or
          * not this one grants it the provided capability, represented by the {@code flags}
          * parameter.  In the event of signing certificate rotation, a package may still interact
@@ -8573,4 +8623,410 @@
             this.error = error;
         }
     }
+
+    // Duplicate the SplitAsset related classes with PackageParser.Package/ApkLite here, and
+    // change the original one using new Package/ApkLite. The propose is that we don't want to
+    // have two branches of methods in SplitAsset related classes so we can keep real classes
+    // clean and move all the legacy code to one place.
+
+    /**
+     * A helper class that implements the dependency tree traversal for splits. Callbacks
+     * are implemented by subclasses to notify whether a split has already been constructed
+     * and is cached, and to actually create the split requested.
+     *
+     * This helper is meant to be subclassed so as to reduce the number of allocations
+     * needed to make use of it.
+     *
+     * All inputs and outputs are assumed to be indices into an array of splits.
+     *
+     * @hide
+     * @deprecated Do not use. New changes should use
+     * {@link android.content.pm.split.SplitDependencyLoader} instead.
+     */
+    @Deprecated
+    private abstract static class SplitDependencyLoader<E extends Exception> {
+        private final @NonNull SparseArray<int[]> mDependencies;
+
+        /**
+         * Construct a new SplitDependencyLoader. Meant to be called from the
+         * subclass constructor.
+         * @param dependencies The dependency tree of splits.
+         */
+        protected SplitDependencyLoader(@NonNull SparseArray<int[]> dependencies) {
+            mDependencies = dependencies;
+        }
+
+        /**
+         * Traverses the dependency tree and constructs any splits that are not already
+         * cached. This routine short-circuits and skips the creation of splits closer to the
+         * root if they are cached, as reported by the subclass implementation of
+         * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass
+         * implementation of {@link #constructSplit(int, int[], int)}.
+         * @param splitIdx The index of the split to load. 0 represents the base Application.
+         */
+        protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E {
+            // Quick check before any allocations are done.
+            if (isSplitCached(splitIdx)) {
+                return;
+            }
+
+            // Special case the base, since it has no dependencies.
+            if (splitIdx == 0) {
+                final int[] configSplitIndices = collectConfigSplitIndices(0);
+                constructSplit(0, configSplitIndices, -1);
+                return;
+            }
+
+            // Build up the dependency hierarchy.
+            final IntArray linearDependencies = new IntArray();
+            linearDependencies.add(splitIdx);
+
+            // Collect all the dependencies that need to be constructed.
+            // They will be listed from leaf to root.
+            while (true) {
+                // Only follow the first index into the array. The others are config splits and
+                // get loaded with the split.
+                final int[] deps = mDependencies.get(splitIdx);
+                if (deps != null && deps.length > 0) {
+                    splitIdx = deps[0];
+                } else {
+                    splitIdx = -1;
+                }
+
+                if (splitIdx < 0 || isSplitCached(splitIdx)) {
+                    break;
+                }
+
+                linearDependencies.add(splitIdx);
+            }
+
+            // Visit each index, from right to left (root to leaf).
+            int parentIdx = splitIdx;
+            for (int i = linearDependencies.size() - 1; i >= 0; i--) {
+                final int idx = linearDependencies.get(i);
+                final int[] configSplitIndices = collectConfigSplitIndices(idx);
+                constructSplit(idx, configSplitIndices, parentIdx);
+                parentIdx = idx;
+            }
+        }
+
+        private @NonNull int[] collectConfigSplitIndices(int splitIdx) {
+            // The config splits appear after the first element.
+            final int[] deps = mDependencies.get(splitIdx);
+            if (deps == null || deps.length <= 1) {
+                return EmptyArray.INT;
+            }
+            return Arrays.copyOfRange(deps, 1, deps.length);
+        }
+
+        /**
+         * Subclass to report whether the split at `splitIdx` is cached and need not be constructed.
+         * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached.
+         * @param splitIdx The index of the split to check for in the cache.
+         * @return true if the split is cached and does not need to be constructed.
+         */
+        protected abstract boolean isSplitCached(@IntRange(from = 0) int splitIdx);
+
+        /**
+         * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`.
+         * The result is expected to be cached by the subclass in its own structures.
+         * @param splitIdx The index of the split to construct. 0 represents the base Application.
+         * @param configSplitIndices The array of configuration splits to load along with this
+         *                           split. May be empty (length == 0) but never null.
+         * @param parentSplitIdx The index of the parent split. -1 if there is no parent.
+         * @throws E Subclass defined exception representing failure to construct a split.
+         */
+        protected abstract void constructSplit(@IntRange(from = 0) int splitIdx,
+                @NonNull @IntRange(from = 1) int[] configSplitIndices,
+                @IntRange(from = -1) int parentSplitIdx) throws E;
+
+        public static class IllegalDependencyException extends Exception {
+            private IllegalDependencyException(String message) {
+                super(message);
+            }
+        }
+
+        private static int[] append(int[] src, int elem) {
+            if (src == null) {
+                return new int[] { elem };
+            }
+            int[] dst = Arrays.copyOf(src, src.length + 1);
+            dst[src.length] = elem;
+            return dst;
+        }
+
+        public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
+                PackageLite pkg)
+                throws SplitDependencyLoader.IllegalDependencyException {
+            // The data structure that holds the dependencies. In PackageParser, splits are stored
+            // in their own array, separate from the base. We treat all paths as equals, so
+            // we need to insert the base as index 0, and shift all other splits.
+            final SparseArray<int[]> splitDependencies = new SparseArray<>();
+
+            // The base depends on nothing.
+            splitDependencies.put(0, new int[] {-1});
+
+            // First write out the <uses-split> dependencies. These must appear first in the
+            // array of ints, as is convention in this class.
+            for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
+                if (!pkg.isFeatureSplits[splitIdx]) {
+                    // Non-feature splits don't have dependencies.
+                    continue;
+                }
+
+                // Implicit dependency on the base.
+                final int targetIdx;
+                final String splitDependency = pkg.usesSplitNames[splitIdx];
+                if (splitDependency != null) {
+                    final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+                    if (depIdx < 0) {
+                        throw new SplitDependencyLoader.IllegalDependencyException(
+                                "Split '" + pkg.splitNames[splitIdx] + "' requires split '"
+                                        + splitDependency + "', which is missing.");
+                    }
+                    targetIdx = depIdx + 1;
+                } else {
+                    // Implicitly depend on the base.
+                    targetIdx = 0;
+                }
+                splitDependencies.put(splitIdx + 1, new int[] {targetIdx});
+            }
+
+            // Write out the configForSplit reverse-dependencies. These appear after the
+            // <uses-split> dependencies and are considered leaves.
+            //
+            // At this point, all splits in splitDependencies have the first element in their
+            // array set.
+            for (int splitIdx = 0, size = pkg.splitNames.length; splitIdx < size; splitIdx++) {
+                if (pkg.isFeatureSplits[splitIdx]) {
+                    // Feature splits are not configForSplits.
+                    continue;
+                }
+
+                // Implicit feature for the base.
+                final int targetSplitIdx;
+                final String configForSplit = pkg.configForSplit[splitIdx];
+                if (configForSplit != null) {
+                    final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit);
+                    if (depIdx < 0) {
+                        throw new SplitDependencyLoader.IllegalDependencyException(
+                                "Split '" + pkg.splitNames[splitIdx] + "' targets split '"
+                                        + configForSplit + "', which is missing.");
+                    }
+
+                    if (!pkg.isFeatureSplits[depIdx]) {
+                        throw new SplitDependencyLoader.IllegalDependencyException(
+                                "Split '" + pkg.splitNames[splitIdx] + "' declares itself as "
+                                        + "configuration split for a non-feature split '"
+                                        + pkg.splitNames[depIdx] + "'");
+                    }
+                    targetSplitIdx = depIdx + 1;
+                } else {
+                    targetSplitIdx = 0;
+                }
+                splitDependencies.put(targetSplitIdx,
+                        append(splitDependencies.get(targetSplitIdx), splitIdx + 1));
+            }
+
+            // Verify that there are no cycles.
+            final BitSet bitset = new BitSet();
+            for (int i = 0, size = splitDependencies.size(); i < size; i++) {
+                int splitIdx = splitDependencies.keyAt(i);
+
+                bitset.clear();
+                while (splitIdx != -1) {
+                    // Check if this split has been visited yet.
+                    if (bitset.get(splitIdx)) {
+                        throw new SplitDependencyLoader.IllegalDependencyException(
+                                "Cycle detected in split dependencies.");
+                    }
+
+                    // Mark the split so that if we visit it again, we no there is a cycle.
+                    bitset.set(splitIdx);
+
+                    // Follow the first dependency only, the others are leaves by definition.
+                    final int[] deps = splitDependencies.get(splitIdx);
+                    splitIdx = deps != null ? deps[0] : -1;
+                }
+            }
+            return splitDependencies;
+        }
+    }
+
+    /**
+     * Loads the base and split APKs into a single AssetManager.
+     * @hide
+     * @deprecated Do not use. New changes should use
+     * {@link android.content.pm.split.DefaultSplitAssetLoader} instead.
+     */
+    @Deprecated
+    private static class DefaultSplitAssetLoader implements SplitAssetLoader {
+        private final String mBaseCodePath;
+        private final String[] mSplitCodePaths;
+        private final @ParseFlags int mFlags;
+        private AssetManager mCachedAssetManager;
+
+        DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
+            mBaseCodePath = pkg.baseCodePath;
+            mSplitCodePaths = pkg.splitCodePaths;
+            mFlags = flags;
+        }
+
+        private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+                throws PackageParserException {
+            if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                        "Invalid package file: " + path);
+            }
+
+            try {
+                return ApkAssets.loadFromPath(path);
+            } catch (IOException e) {
+                throw new PackageParserException(INSTALL_FAILED_INVALID_APK,
+                        "Failed to load APK at path " + path, e);
+            }
+        }
+
+        @Override
+        public AssetManager getBaseAssetManager() throws PackageParserException {
+            if (mCachedAssetManager != null) {
+                return mCachedAssetManager;
+            }
+
+            ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
+                    ? mSplitCodePaths.length : 0) + 1];
+
+            // Load the base.
+            int splitIdx = 0;
+            apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+
+            // Load any splits.
+            if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+                for (String apkPath : mSplitCodePaths) {
+                    apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
+                }
+            }
+
+            AssetManager assets = new AssetManager();
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+
+            mCachedAssetManager = assets;
+            return mCachedAssetManager;
+        }
+
+        @Override
+        public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException {
+            return getBaseAssetManager();
+        }
+
+        @Override
+        public void close() throws Exception {
+            IoUtils.closeQuietly(mCachedAssetManager);
+        }
+    }
+
+    /**
+     * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
+     * is to be used when an application opts-in to isolated split loading.
+     * @hide
+     * @deprecated Do not use. New changes should use
+     * {@link android.content.pm.split.SplitAssetDependencyLoader} instead.
+     */
+    @Deprecated
+    private static class SplitAssetDependencyLoader extends
+            SplitDependencyLoader<PackageParserException> implements SplitAssetLoader {
+        private final String[] mSplitPaths;
+        private final @ParseFlags int mFlags;
+        private final ApkAssets[][] mCachedSplitApks;
+        private final AssetManager[] mCachedAssetManagers;
+
+        SplitAssetDependencyLoader(PackageLite pkg,
+                SparseArray<int[]> dependencies, @ParseFlags int flags) {
+            super(dependencies);
+
+            // The base is inserted into index 0, so we need to shift all the splits by 1.
+            mSplitPaths = new String[pkg.splitCodePaths.length + 1];
+            mSplitPaths[0] = pkg.baseCodePath;
+            System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
+
+            mFlags = flags;
+            mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
+            mCachedAssetManagers = new AssetManager[mSplitPaths.length];
+        }
+
+        @Override
+        protected boolean isSplitCached(int splitIdx) {
+            return mCachedAssetManagers[splitIdx] != null;
+        }
+
+        private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+                throws PackageParserException {
+            if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                        "Invalid package file: " + path);
+            }
+
+            try {
+                return ApkAssets.loadFromPath(path);
+            } catch (IOException e) {
+                throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK,
+                        "Failed to load APK at path " + path, e);
+            }
+        }
+
+        private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
+            final AssetManager assets = new AssetManager();
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
+            assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+            return assets;
+        }
+
+        @Override
+        protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
+                int parentSplitIdx) throws PackageParserException {
+            final ArrayList<ApkAssets> assets = new ArrayList<>();
+
+            // Include parent ApkAssets.
+            if (parentSplitIdx >= 0) {
+                Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
+            }
+
+            // Include this ApkAssets.
+            assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
+
+            // Load and include all config splits for this feature.
+            for (int configSplitIdx : configSplitIndices) {
+                assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
+            }
+
+            // Cache the results.
+            mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
+            mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(
+                    mCachedSplitApks[splitIdx]);
+        }
+
+        @Override
+        public AssetManager getBaseAssetManager() throws PackageParserException {
+            loadDependenciesForSplit(0);
+            return mCachedAssetManagers[0];
+        }
+
+        @Override
+        public AssetManager getSplitAssetManager(int idx) throws PackageParserException {
+            // Since we insert the base at position 0, and PackageParser keeps splits separate from
+            // the base, we need to adjust the index.
+            loadDependenciesForSplit(idx + 1);
+            return mCachedAssetManagers[idx + 1];
+        }
+
+        @Override
+        public void close() throws Exception {
+            for (AssetManager assets : mCachedAssetManagers) {
+                IoUtils.closeQuietly(assets);
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 9925871..5cc74c0 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -77,8 +77,6 @@
     public boolean virtualPreload;
     public int enabled;
     public String lastDisableAppCaller;
-    public int domainVerificationStatus;
-    public int appLinkGeneration;
     public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
     public int installReason;
     public @PackageManager.UninstallReason int uninstallReason;
@@ -100,8 +98,6 @@
         hidden = false;
         suspended = false;
         enabled = COMPONENT_ENABLED_STATE_DEFAULT;
-        domainVerificationStatus =
-                PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
         installReason = PackageManager.INSTALL_REASON_UNKNOWN;
         uninstallReason = PackageManager.UNINSTALL_REASON_UNKNOWN;
     }
@@ -120,8 +116,6 @@
         virtualPreload = o.virtualPreload;
         enabled = o.enabled;
         lastDisableAppCaller = o.lastDisableAppCaller;
-        domainVerificationStatus = o.domainVerificationStatus;
-        appLinkGeneration = o.appLinkGeneration;
         categoryHint = o.categoryHint;
         installReason = o.installReason;
         uninstallReason = o.uninstallReason;
@@ -416,12 +410,6 @@
                         && !lastDisableAppCaller.equals(oldState.lastDisableAppCaller))) {
             return false;
         }
-        if (domainVerificationStatus != oldState.domainVerificationStatus) {
-            return false;
-        }
-        if (appLinkGeneration != oldState.appLinkGeneration) {
-            return false;
-        }
         if (categoryHint != oldState.categoryHint) {
             return false;
         }
@@ -481,8 +469,6 @@
         hashCode = 31 * hashCode + Boolean.hashCode(virtualPreload);
         hashCode = 31 * hashCode + enabled;
         hashCode = 31 * hashCode + Objects.hashCode(lastDisableAppCaller);
-        hashCode = 31 * hashCode + domainVerificationStatus;
-        hashCode = 31 * hashCode + appLinkGeneration;
         hashCode = 31 * hashCode + categoryHint;
         hashCode = 31 * hashCode + installReason;
         hashCode = 31 * hashCode + uninstallReason;
diff --git a/core/java/android/content/pm/ProcessInfo.java b/core/java/android/content/pm/ProcessInfo.java
index d45ff98..3dd5ee1 100644
--- a/core/java/android/content/pm/ProcessInfo.java
+++ b/core/java/android/content/pm/ProcessInfo.java
@@ -53,16 +53,30 @@
      */
     public @ApplicationInfo.GwpAsanMode int gwpAsanMode;
 
+    /**
+     * Indicates if the process has requested Memtag to be enabled (in sync or async mode),
+     * disabled, or left unspecified.
+     */
+    public @ApplicationInfo.MemtagMode int memtagMode;
+
+    /**
+     * Enable automatic zero-initialization of native heap memory allocations.
+     */
+    @Nullable
+    public Boolean nativeHeapZeroInit;
+
     @Deprecated
     public ProcessInfo(@NonNull ProcessInfo orig) {
         this.name = orig.name;
         this.deniedPermissions = orig.deniedPermissions;
         this.gwpAsanMode = orig.gwpAsanMode;
+        this.memtagMode = orig.memtagMode;
+        this.nativeHeapZeroInit = orig.nativeHeapZeroInit;
     }
 
 
 
-    // Code below generated by codegen v1.0.15.
+    // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -84,12 +98,19 @@
      *   If non-null, these are permissions that are not allowed in this process.
      * @param gwpAsanMode
      *   Indicates if the process has requested GWP-ASan to be enabled, disabled, or left unspecified.
+     * @param memtagMode
+     *   Indicates if the process has requested Memtag to be enabled (in sync or async mode),
+     *   disabled, or left unspecified.
+     * @param nativeHeapZeroInit
+     *   Enable automatic zero-initialization of native heap memory allocations.
      */
     @DataClass.Generated.Member
     public ProcessInfo(
             @NonNull String name,
             @Nullable ArraySet<String> deniedPermissions,
-            @ApplicationInfo.GwpAsanMode int gwpAsanMode) {
+            @ApplicationInfo.GwpAsanMode int gwpAsanMode,
+            @ApplicationInfo.MemtagMode int memtagMode,
+            @Nullable Boolean nativeHeapZeroInit) {
         this.name = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
@@ -97,6 +118,10 @@
         this.gwpAsanMode = gwpAsanMode;
         com.android.internal.util.AnnotationValidations.validate(
                 ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+        this.memtagMode = memtagMode;
+        com.android.internal.util.AnnotationValidations.validate(
+                ApplicationInfo.MemtagMode.class, null, memtagMode);
+        this.nativeHeapZeroInit = nativeHeapZeroInit;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -120,10 +145,13 @@
 
         byte flg = 0;
         if (deniedPermissions != null) flg |= 0x2;
+        if (nativeHeapZeroInit != null) flg |= 0x10;
         dest.writeByte(flg);
         dest.writeString(name);
         sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
         dest.writeInt(gwpAsanMode);
+        dest.writeInt(memtagMode);
+        if (nativeHeapZeroInit != null) dest.writeBoolean(nativeHeapZeroInit);
     }
 
     @Override
@@ -141,6 +169,8 @@
         String _name = in.readString();
         ArraySet<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
         int _gwpAsanMode = in.readInt();
+        int _memtagMode = in.readInt();
+        Boolean _nativeHeapZeroInit = (flg & 0x10) == 0 ? null : (Boolean) in.readBoolean();
 
         this.name = _name;
         com.android.internal.util.AnnotationValidations.validate(
@@ -149,6 +179,10 @@
         this.gwpAsanMode = _gwpAsanMode;
         com.android.internal.util.AnnotationValidations.validate(
                 ApplicationInfo.GwpAsanMode.class, null, gwpAsanMode);
+        this.memtagMode = _memtagMode;
+        com.android.internal.util.AnnotationValidations.validate(
+                ApplicationInfo.MemtagMode.class, null, memtagMode);
+        this.nativeHeapZeroInit = _nativeHeapZeroInit;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -168,10 +202,10 @@
     };
 
     @DataClass.Generated(
-            time = 1584555730519L,
-            codegenVersion = "1.0.15",
+            time = 1611614699049L,
+            codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/content/pm/ProcessInfo.java",
-            inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+            inputSignatures = "public @android.annotation.NonNull java.lang.String name\npublic @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringArraySet.class) android.util.ArraySet<java.lang.String> deniedPermissions\npublic @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\npublic @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\npublic @android.annotation.Nullable java.lang.Boolean nativeHeapZeroInit\nclass ProcessInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 02fb06b..bce4b87 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -256,6 +256,8 @@
         try {
             if (obj != null) {
                 Signature other = (Signature)obj;
+                // Note, some classes, such as PackageParser.SigningDetails, rely on equals
+                // only comparing the mSignature arrays without the flags.
                 return this == other || Arrays.equals(mSignature, other.mSignature);
             }
         } catch (ClassCastException e) {
@@ -268,6 +270,8 @@
         if (mHaveHashCode) {
             return mHashCode;
         }
+        // Note, similar to equals some classes rely on the hash code not including
+        // the flags for Set membership checks.
         mHashCode = Arrays.hashCode(mSignature);
         mHaveHashCode = true;
         return mHashCode;
diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java
index 982fce9..bf35c4d 100644
--- a/core/java/android/content/pm/dex/DexMetadataHelper.java
+++ b/core/java/android/content/pm/dex/DexMetadataHelper.java
@@ -17,11 +17,11 @@
 package android.content.pm.dex;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA;
-import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
+import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION;
 
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
 import android.util.ArrayMap;
 import android.util.jar.StrictJarFile;
 
@@ -87,7 +87,7 @@
      * NOTE: involves I/O checks.
      */
     private static Map<String, String> getPackageDexMetadata(PackageLite pkg) {
-        return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths());
+        return buildPackageApkToDexMetadataMap(pkg.getAllApkPaths());
     }
 
     /**
@@ -125,7 +125,7 @@
      * @throws IllegalArgumentException if the code path is not an .apk.
      */
     public static String buildDexMetadataPathForApk(String codePath) {
-        if (!PackageParser.isApkPath(codePath)) {
+        if (!ApkLiteParseUtils.isApkPath(codePath)) {
             throw new IllegalStateException(
                     "Corrupted package. Code path is not an apk " + codePath);
         }
@@ -140,7 +140,7 @@
      * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk').
      */
     private static String buildDexMetadataPathForFile(File targetFile) {
-        return PackageParser.isApkFile(targetFile)
+        return ApkLiteParseUtils.isApkFile(targetFile)
                 ? buildDexMetadataPathForApk(targetFile.getPath())
                 : targetFile.getPath() + DEX_METADATA_FILE_EXTENSION;
     }
@@ -179,7 +179,7 @@
     public static void validateDexPaths(String[] paths) {
         ArrayList<String> apks = new ArrayList<>();
         for (int i = 0; i < paths.length; i++) {
-            if (PackageParser.isApkPath(paths[i])) {
+            if (ApkLiteParseUtils.isApkPath(paths[i])) {
                 apks.add(paths[i]);
             }
         }
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 51b81b6..a3c2cbc 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -66,6 +66,8 @@
     private static final int PARSE_DEFAULT_INSTALL_LOCATION =
             PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
 
+    private static final Comparator<String> sSplitNameComparator = new SplitNameComparator();
+
     public static final String APK_FILE_EXTENSION = ".apk";
 
     /**
@@ -79,7 +81,7 @@
      *
      * @see PackageParser#parsePackage(File, int)
      */
-    public static ParseResult<PackageParser.PackageLite> parsePackageLite(ParseInput input,
+    public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
             File packageFile, int flags) {
         if (packageFile.isDirectory()) {
             return parseClusterPackageLite(input, packageFile, flags);
@@ -88,26 +90,32 @@
         }
     }
 
-    public static ParseResult<PackageParser.PackageLite> parseMonolithicPackageLite(
-            ParseInput input, File packageFile, int flags) {
+    /**
+     * Parse lightweight details about a single APK files.
+     */
+    public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input,
+            File packageFile, int flags) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
         try {
-            ParseResult<PackageParser.ApkLite> result = parseApkLite(input, packageFile, flags);
+            final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags);
             if (result.isError()) {
                 return input.error(result);
             }
 
-            final PackageParser.ApkLite baseApk = result.getResult();
+            final ApkLite baseApk = result.getResult();
             final String packagePath = packageFile.getAbsolutePath();
             return input.success(
-                    new PackageParser.PackageLite(packagePath, baseApk.codePath, baseApk, null,
+                    new PackageLite(packagePath, baseApk.getPath(), baseApk, null,
                             null, null, null, null, null));
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
     }
 
-    public static ParseResult<PackageParser.PackageLite> parseClusterPackageLite(ParseInput input,
+    /**
+     * Parse lightweight details about a directory of APKs.
+     */
+    public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
             File packageDir, int flags) {
         final File[] files = packageDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
@@ -122,39 +130,39 @@
         String packageName = null;
         int versionCode = 0;
 
-        final ArrayMap<String, PackageParser.ApkLite> apks = new ArrayMap<>();
+        final ArrayMap<String, ApkLite> apks = new ArrayMap<>();
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
         try {
             for (File file : files) {
-                if (PackageParser.isApkFile(file)) {
-                    ParseResult<PackageParser.ApkLite> result = parseApkLite(input, file, flags);
+                if (isApkFile(file)) {
+                    final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
                     if (result.isError()) {
                         return input.error(result);
                     }
 
-                    final PackageParser.ApkLite lite = result.getResult();
+                    final ApkLite lite = result.getResult();
                     // Assert that all package names and version codes are
                     // consistent with the first one we encounter.
                     if (packageName == null) {
-                        packageName = lite.packageName;
-                        versionCode = lite.versionCode;
+                        packageName = lite.getPackageName();
+                        versionCode = lite.getVersionCode();
                     } else {
-                        if (!packageName.equals(lite.packageName)) {
+                        if (!packageName.equals(lite.getPackageName())) {
                             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                    "Inconsistent package " + lite.packageName + " in " + file
+                                    "Inconsistent package " + lite.getPackageName() + " in " + file
                                             + "; expected " + packageName);
                         }
-                        if (versionCode != lite.versionCode) {
+                        if (versionCode != lite.getVersionCode()) {
                             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                    "Inconsistent version " + lite.versionCode + " in " + file
+                                    "Inconsistent version " + lite.getVersionCode() + " in " + file
                                             + "; expected " + versionCode);
                         }
                     }
 
                     // Assert that each split is defined only oncuses-static-libe
-                    if (apks.put(lite.splitName, lite) != null) {
+                    if (apks.put(lite.getSplitName(), lite) != null) {
                         return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                                "Split name " + lite.splitName
+                                "Split name " + lite.getSplitName()
                                         + " defined more than once; most recent was " + file);
                     }
                 }
@@ -163,7 +171,7 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
-        final PackageParser.ApkLite baseApk = apks.remove(null);
+        final ApkLite baseApk = apks.remove(null);
         return composePackageLiteFromApks(input, packageDir, baseApk, apks);
     }
 
@@ -176,9 +184,8 @@
      * @param splitApks Parsed split APKs
      * @return PackageLite
      */
-    public static ParseResult<PackageParser.PackageLite> composePackageLiteFromApks(
-            ParseInput input, File packageDir, PackageParser.ApkLite baseApk,
-            ArrayMap<String, PackageParser.ApkLite> splitApks) {
+    public static ParseResult<PackageLite> composePackageLiteFromApks(ParseInput input,
+            File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks) {
         return composePackageLiteFromApks(input, packageDir, baseApk, splitApks, false);
     }
 
@@ -192,9 +199,9 @@
      * @param apkRenamed Indicate whether the APKs are renamed after parsed.
      * @return PackageLite
      */
-    public static ParseResult<PackageParser.PackageLite> composePackageLiteFromApks(
-            ParseInput input, File packageDir, PackageParser.ApkLite baseApk,
-            ArrayMap<String, PackageParser.ApkLite> splitApks, boolean apkRenamed) {
+    public static ParseResult<PackageLite> composePackageLiteFromApks(
+            ParseInput input, File packageDir, ApkLite baseApk,
+            ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) {
         if (baseApk == null) {
             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                     "Missing base APK in " + packageDir);
@@ -217,26 +224,25 @@
             splitRevisionCodes = new int[size];
 
             splitNames = splitApks.keySet().toArray(splitNames);
-            Arrays.sort(splitNames, PackageParser.sSplitNameComparator);
+            Arrays.sort(splitNames, sSplitNameComparator);
 
             for (int i = 0; i < size; i++) {
-                final PackageParser.ApkLite apk = splitApks.get(splitNames[i]);
-                usesSplitNames[i] = apk.usesSplitName;
-                isFeatureSplits[i] = apk.isFeatureSplit;
-                configForSplits[i] = apk.configForSplit;
+                final ApkLite apk = splitApks.get(splitNames[i]);
+                usesSplitNames[i] = apk.getUsesSplitName();
+                isFeatureSplits[i] = apk.isFeatureSplit();
+                configForSplits[i] = apk.getConfigForSplit();
                 splitCodePaths[i] = apkRenamed ? new File(packageDir,
-                        splitNameToFileName(apk)).getAbsolutePath() : apk.codePath;
-                splitRevisionCodes[i] = apk.revisionCode;
+                        splitNameToFileName(apk)).getAbsolutePath() : apk.getPath();
+                splitRevisionCodes[i] = apk.getRevisionCode();
             }
         }
 
         final String codePath = packageDir.getAbsolutePath();
         final String baseCodePath = apkRenamed ? new File(packageDir,
-                splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.codePath;
+                splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath();
         return input.success(
-                new PackageParser.PackageLite(codePath, baseCodePath, baseApk, splitNames,
-                        isFeatureSplits, usesSplitNames, configForSplits, splitCodePaths,
-                        splitRevisionCodes));
+                new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
+                        usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes));
     }
 
     /**
@@ -245,9 +251,9 @@
      * @param apk Parsed APK
      * @return The canonical file name
      */
-    public static String splitNameToFileName(@NonNull PackageParser.ApkLite apk) {
+    public static String splitNameToFileName(@NonNull ApkLite apk) {
         Objects.requireNonNull(apk);
-        final String fileName = apk.splitName == null ? "base" : "split_" + apk.splitName;
+        final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName();
         return fileName + APK_FILE_EXTENSION;
     }
 
@@ -257,10 +263,9 @@
      *
      * @param apkFile path to a single APK
      * @param flags optional parse flags, such as
-     *            {@link PackageParser#PARSE_COLLECT_CERTIFICATES}
+     *            {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
      */
-    public static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input, File apkFile,
-            int flags) {
+    public static ParseResult<ApkLite> parseApkLite(ParseInput input, File apkFile, int flags) {
         return parseApkLiteInner(input, apkFile, null, null, flags);
     }
 
@@ -271,14 +276,14 @@
      * @param fd already open file descriptor of an apk file
      * @param debugPathName arbitrary text name for this file, for debug output
      * @param flags optional parse flags, such as
-     *            {@link PackageParser#PARSE_COLLECT_CERTIFICATES}
+     *            {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES}
      */
-    public static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input,
+    public static ParseResult<ApkLite> parseApkLite(ParseInput input,
             FileDescriptor fd, String debugPathName, int flags) {
         return parseApkLiteInner(input, null, fd, debugPathName, flags);
     }
 
-    private static ParseResult<PackageParser.ApkLite> parseApkLiteInner(ParseInput input,
+    private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input,
             File apkFile, FileDescriptor fd, String debugPathName, int flags) {
         final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
 
@@ -294,11 +299,11 @@
                         "Failed to parse " + apkPath, e);
             }
 
-            parser = apkAssets.openXml(PackageParser.ANDROID_MANIFEST_FILENAME);
+            parser = apkAssets.openXml(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME);
 
             final PackageParser.SigningDetails signingDetails;
-            if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) {
-                final boolean skipVerify = (flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0;
+            if ((flags & ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES) != 0) {
+                final boolean skipVerify = (flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
                 try {
                     ParseResult<PackageParser.SigningDetails> result =
@@ -335,9 +340,8 @@
         }
     }
 
-    private static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input,
-            String codePath, XmlPullParser parser, AttributeSet attrs,
-            PackageParser.SigningDetails signingDetails)
+    private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath,
+            XmlPullParser parser, AttributeSet attrs, PackageParser.SigningDetails signingDetails)
             throws IOException, XmlPullParserException {
         ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser, attrs);
         if (result.isError()) {
@@ -421,12 +425,12 @@
                 continue;
             }
 
-            if (PackageParser.TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
+            if (ParsingPackageUtils.TAG_PACKAGE_VERIFIER.equals(parser.getName())) {
                 final VerifierInfo verifier = parseVerifier(attrs);
                 if (verifier != null) {
                     verifiers.add(verifier);
                 }
-            } else if (PackageParser.TAG_APPLICATION.equals(parser.getName())) {
+            } else if (ParsingPackageUtils.TAG_APPLICATION.equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
                     switch (attr) {
@@ -464,7 +468,7 @@
                         continue;
                     }
 
-                    if (PackageParser.TAG_PROFILEABLE.equals(parser.getName())) {
+                    if (ParsingPackageUtils.TAG_PROFILEABLE.equals(parser.getName())) {
                         for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                             final String attr = attrs.getAttributeName(i);
                             if ("shell".equals(attr)) {
@@ -474,7 +478,7 @@
                         }
                     }
                 }
-            } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) {
+            } else if (ParsingPackageUtils.TAG_OVERLAY.equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
                     if ("requiredSystemPropertyName".equals(attr)) {
@@ -489,7 +493,7 @@
                         overlayPriority = attrs.getAttributeIntValue(i, 0);
                     }
                 }
-            } else if (PackageParser.TAG_USES_SPLIT.equals(parser.getName())) {
+            } else if (ParsingPackageUtils.TAG_USES_SPLIT.equals(parser.getName())) {
                 if (usesSplitName != null) {
                     Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
                     continue;
@@ -500,7 +504,7 @@
                     return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                             "<uses-split> tag requires 'android:name' attribute");
                 }
-            } else if (PackageParser.TAG_USES_SDK.equals(parser.getName())) {
+            } else if (ParsingPackageUtils.TAG_USES_SDK.equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
                     if ("targetSdkVersion".equals(attr)) {
@@ -526,8 +530,8 @@
         }
 
         return input.success(
-                new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second,
-                        isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode,
+                new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
+                        configForSplit, usesSplitName, isSplitRequired, versionCode,
                         versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
                         coreApp, debuggable, profilableByShell, multiArch, use32bitAbi,
                         useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage,
@@ -546,7 +550,7 @@
             return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                     "No start tag found");
         }
-        if (!parser.getName().equals(PackageParser.TAG_MANIFEST)) {
+        if (!parser.getName().equals(ParsingPackageUtils.TAG_MANIFEST)) {
             return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                     "No <manifest> tag");
         }
@@ -625,4 +629,24 @@
             }
         }
     }
+
+    /**
+     * Check if the given file is an APK file.
+     *
+     * @param file the file to check.
+     * @return {@code true} if the given file is an APK file.
+     */
+    public static boolean isApkFile(File file) {
+        return isApkPath(file.getName());
+    }
+
+    /**
+     * Check if the given path ends with APK file extension.
+     *
+     * @param path the path to check.
+     * @return {@code true} if the given path ends with APK file extension.
+     */
+    public static boolean isApkPath(String path) {
+        return path.endsWith(APK_FILE_EXTENSION);
+    }
 }
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index f8fd4a5..b7365b3 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -385,7 +385,7 @@
         }
 
         // CompatibilityMode is global state.
-        if (!PackageParser.sCompatibilityModeEnabled) {
+        if (!ParsingPackageUtils.sCompatibilityModeEnabled) {
             ai.disableCompatibilityMode();
         }
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 8147599..7a01392 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -252,6 +252,10 @@
 
     ParsingPackage setGwpAsanMode(int gwpAsanMode);
 
+    ParsingPackage setMemtagMode(int memtagMode);
+
+    ParsingPackage setNativeHeapZeroInit(@Nullable Boolean nativeHeapZeroInit);
+
     ParsingPackage setCrossProfile(boolean crossProfile);
 
     ParsingPackage setFullBackupContent(int fullBackupContent);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 38d3940..c1a93d8 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -335,7 +335,7 @@
 
     private int fullBackupContent;
     private int iconRes;
-    private int installLocation = PackageParser.PARSE_DEFAULT_INSTALL_LOCATION;
+    private int installLocation = ParsingPackageUtils.PARSE_DEFAULT_INSTALL_LOCATION;
     private int labelRes;
     private int largestWidthLimitDp;
     private int logo;
@@ -380,6 +380,11 @@
     private int autoRevokePermissions;
 
     protected int gwpAsanMode;
+    protected int memtagMode;
+
+    @Nullable
+    @DataClass.ParcelWith(ForBoolean.class)
+    private Boolean nativeHeapZeroInit;
 
     // TODO(chiuwinson): Non-null
     @Nullable
@@ -1013,7 +1018,8 @@
         // TODO(b/135203078): See ParsingPackageImpl#getHiddenApiEnforcementPolicy
 //        appInfo.mHiddenApiPolicy
 //        appInfo.hiddenUntilInstalled
-        appInfo.icon = (PackageParser.sUseRoundIcon && roundIconRes != 0) ? roundIconRes : iconRes;
+        appInfo.icon =
+                (ParsingPackageUtils.sUseRoundIcon && roundIconRes != 0) ? roundIconRes : iconRes;
         appInfo.iconRes = iconRes;
         appInfo.roundIconRes = roundIconRes;
         appInfo.installLocation = installLocation;
@@ -1057,6 +1063,8 @@
         appInfo.volumeUuid = volumeUuid;
         appInfo.zygotePreloadName = zygotePreloadName;
         appInfo.setGwpAsanMode(gwpAsanMode);
+        appInfo.setMemtagMode(memtagMode);
+        appInfo.setNativeHeapZeroInit(nativeHeapZeroInit);
         appInfo.setBaseCodePath(mBaseApkPath);
         appInfo.setBaseResourcePath(mBaseApkPath);
         appInfo.setCodePath(mPath);
@@ -1189,6 +1197,8 @@
         dest.writeSparseIntArray(this.minExtensionVersions);
         dest.writeLong(this.mBooleans);
         dest.writeMap(this.mProperties);
+        dest.writeInt(this.memtagMode);
+        sForBoolean.parcel(this.nativeHeapZeroInit, dest, flags);
     }
 
     public ParsingPackageImpl(Parcel in) {
@@ -1309,6 +1319,8 @@
         this.minExtensionVersions = in.readSparseIntArray();
         this.mBooleans = in.readLong();
         this.mProperties = in.createTypedArrayMap(Property.CREATOR);
+        this.memtagMode = in.readInt();
+        this.nativeHeapZeroInit = sForBoolean.unparcel(in);
         assignDerivedFields();
     }
 
@@ -2061,6 +2073,17 @@
     }
 
     @Override
+    public int getMemtagMode() {
+        return memtagMode;
+    }
+
+    @Nullable
+    @Override
+    public Boolean isNativeHeapZeroInit() {
+        return nativeHeapZeroInit;
+    }
+
+    @Override
     public boolean isPartiallyDirectBootAware() {
         return getBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE);
     }
@@ -2492,6 +2515,18 @@
     }
 
     @Override
+    public ParsingPackageImpl setMemtagMode(int value) {
+        memtagMode = value;
+        return this;
+    }
+
+    @Override
+    public ParsingPackageImpl setNativeHeapZeroInit(@Nullable Boolean value) {
+        nativeHeapZeroInit = value;
+        return this;
+    }
+
+    @Override
     public ParsingPackageImpl setPartiallyDirectBootAware(boolean value) {
         return setBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE, value);
     }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 13ae7a2..ff4cebd 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -66,7 +66,7 @@
 
     /**
      * The names of packages to adopt ownership of permissions from, parsed under
-     * {@link PackageParser#TAG_ADOPT_PERMISSIONS}.
+     * {@link ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
      * @see R.styleable#AndroidManifestOriginalPackage_name
      */
     @NonNull
@@ -105,7 +105,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
-     * {@link PackageParser#TAG_KEY_SETS}.
+     * {@link ParsingPackageUtils#TAG_KEY_SETS}.
      * @see R.styleable#AndroidManifestKeySet
      * @see R.styleable#AndroidManifestPublicKey
      */
@@ -816,7 +816,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
-     * {@link PackageParser#TAG_KEY_SETS}.
+     * {@link ParsingPackageUtils#TAG_KEY_SETS}.
      * @see R.styleable#AndroidManifestUpgradeKeySet
      */
     @NonNull
@@ -874,6 +874,19 @@
      */
     int getGwpAsanMode();
 
+    /**
+     * @see ApplicationInfo#memtagMode
+     * @see R.styleable#AndroidManifest_memtagMode
+     */
+    int getMemtagMode();
+
+      /**
+     * @see ApplicationInfo#nativeHeapZeroInit
+     * @see R.styleable#AndroidManifest_nativeHeapZeroInit
+     */
+    @Nullable
+    Boolean isNativeHeapZeroInit();
+
     // TODO(b/135203078): Hide and enforce going through PackageInfoUtils
     ApplicationInfo toAppInfoWithoutState();
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 494b3cc..66bdb9b 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -34,7 +34,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StyleableRes;
-import android.app.ActivityThread;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
@@ -189,6 +188,12 @@
             PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
     public static final int PARSE_DEFAULT_TARGET_SANDBOX = 1;
 
+    /** If set to true, we will only allow package files that exactly match
+     *  the DTD. Otherwise, we try to get as much from the package as we
+     *  can without failing. This should normally be set to false, to
+     *  support extensions to the DTD in future versions. */
+    public static final boolean RIGID_PARSER = false;
+
     public static final int PARSE_MUST_BE_APK = 1 << 0;
     public static final int PARSE_IGNORE_PROCESSES = 1 << 1;
     public static final int PARSE_EXTERNAL_STORAGE = 1 << 3;
@@ -216,13 +221,15 @@
     private static final int MAX_FILE_NAME_SIZE = 223;
 
     /**
-     * @see #parseDefault(ParseInput, File, int, boolean)
+     * @see #parseDefault(ParseInput, File, int, List, boolean)
      */
     @NonNull
     public static ParseResult<ParsingPackage> parseDefaultOneTime(File file,
-            @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) {
+            @ParseFlags int parseFlags,
+            @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+            boolean collectCertificates) {
         ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
-        return parseDefault(input, file, parseFlags, collectCertificates);
+        return parseDefault(input, file, parseFlags, splitPermissions, collectCertificates);
     }
 
     /**
@@ -232,28 +239,32 @@
      */
     @NonNull
     public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file,
-            @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) {
+            @ParseFlags int parseFlags,
+            @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+            boolean collectCertificates) {
         ParseResult<ParsingPackage> result;
 
-        ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() {
-            @Override
-            public boolean hasFeature(String feature) {
-                // Assume the device doesn't support anything. This will affect permission parsing
-                // and will force <uses-permission/> declarations to include all requiredNotFeature
-                // permissions and exclude all requiredFeature permissions. This mirrors the old
-                // behavior.
-                return false;
-            }
+        ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, splitPermissions,
+                new Callback() {
+                    @Override
+                    public boolean hasFeature(String feature) {
+                        // Assume the device doesn't support anything. This will affect permission
+                        // parsing and will force <uses-permission/> declarations to include all
+                        // requiredNotFeature permissions and exclude all requiredFeature
+                        // permissions. This mirrors the old behavior.
+                        return false;
+                    }
 
-            @Override
-            public ParsingPackage startParsingPackage(
-                    @NonNull String packageName,
-                    @NonNull String baseApkPath,
-                    @NonNull String path,
-                    @NonNull TypedArray manifestArray, boolean isCoreApp) {
-                return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray);
-            }
-        });
+                    @Override
+                    public ParsingPackage startParsingPackage(
+                            @NonNull String packageName,
+                            @NonNull String baseApkPath,
+                            @NonNull String path,
+                            @NonNull TypedArray manifestArray, boolean isCoreApp) {
+                        return new ParsingPackageImpl(packageName, baseApkPath, path,
+                                manifestArray);
+                    }
+                });
         try {
             result = parser.parsePackage(input, file, parseFlags);
             if (result.isError()) {
@@ -284,13 +295,18 @@
     private boolean mOnlyCoreApps;
     private String[] mSeparateProcesses;
     private DisplayMetrics mDisplayMetrics;
+    @NonNull
+    private List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
     private Callback mCallback;
 
     public ParsingPackageUtils(boolean onlyCoreApps, String[] separateProcesses,
-            DisplayMetrics displayMetrics, @NonNull Callback callback) {
+            DisplayMetrics displayMetrics,
+            @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+            @NonNull Callback callback) {
         mOnlyCoreApps = onlyCoreApps;
         mSeparateProcesses = separateProcesses;
         mDisplayMetrics = displayMetrics;
+        mSplitPermissionInfos = splitPermissions;
         mCallback = callback;
     }
 
@@ -334,14 +350,14 @@
      */
     private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
             int flags) {
-        ParseResult<PackageParser.PackageLite> liteResult =
+        final ParseResult<PackageLite> liteResult =
                 ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
 
-        final PackageParser.PackageLite lite = liteResult.getResult();
-        if (mOnlyCoreApps && !lite.coreApp) {
+        final PackageLite lite = liteResult.getResult();
+        if (mOnlyCoreApps && !lite.isCoreApp()) {
             return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
                     "Not a coreApp: " + packageDir);
         }
@@ -349,7 +365,7 @@
         // Build the split dependency tree.
         SparseArray<int[]> splitDependencies = null;
         final SplitAssetLoader assetLoader;
-        if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) {
+        if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {
             try {
                 splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);
                 assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);
@@ -362,22 +378,22 @@
 
         try {
             final AssetManager assets = assetLoader.getBaseAssetManager();
-            final File baseApk = new File(lite.baseCodePath);
-            ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
-                    lite.codePath, assets, flags);
+            final File baseApk = new File(lite.getBaseApkPath());
+            final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
+                    lite.getPath(), assets, flags);
             if (result.isError()) {
                 return input.error(result);
             }
 
             ParsingPackage pkg = result.getResult();
-            if (!ArrayUtils.isEmpty(lite.splitNames)) {
+            if (!ArrayUtils.isEmpty(lite.getSplitNames())) {
                 pkg.asSplit(
-                        lite.splitNames,
-                        lite.splitCodePaths,
-                        lite.splitRevisionCodes,
+                        lite.getSplitNames(),
+                        lite.getSplitApkPaths(),
+                        lite.getSplitRevisionCodes(),
                         splitDependencies
                 );
-                final int num = lite.splitNames.length;
+                final int num = lite.getSplitNames().length;
 
                 for (int i = 0; i < num; i++) {
                     final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);
@@ -385,11 +401,11 @@
                 }
             }
 
-            pkg.setUse32BitAbi(lite.use32bitAbi);
+            pkg.setUse32BitAbi(lite.isUse32bitAbi());
             return input.success(pkg);
         } catch (PackageParserException e) {
             return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
-                    "Failed to load assets: " + lite.baseCodePath, e);
+                    "Failed to load assets: " + lite.getBaseApkPath(), e);
         } finally {
             IoUtils.closeQuietly(assetLoader);
         }
@@ -403,21 +419,21 @@
      */
     private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
             int flags) throws PackageParserException {
-        ParseResult<PackageParser.PackageLite> liteResult =
+        final ParseResult<PackageLite> liteResult =
                 ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
 
-        final PackageParser.PackageLite lite = liteResult.getResult();
-        if (mOnlyCoreApps && !lite.coreApp) {
+        final PackageLite lite = liteResult.getResult();
+        if (mOnlyCoreApps && !lite.isCoreApp()) {
             return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
                     "Not a coreApp: " + apkFile);
         }
 
         final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
         try {
-            ParseResult<ParsingPackage> result = parseBaseApk(input,
+            final ParseResult<ParsingPackage> result = parseBaseApk(input,
                     apkFile,
                     apkFile.getCanonicalPath(),
                     assetLoader.getBaseAssetManager(), flags);
@@ -426,7 +442,7 @@
             }
 
             return input.success(result.getResult()
-                    .setUse32BitAbi(lite.use32bitAbi));
+                    .setUse32BitAbi(lite.isUse32bitAbi()));
         } catch (IOException e) {
             return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                     "Failed to get path: " + apkFile, e);
@@ -440,12 +456,12 @@
         final String apkPath = apkFile.getAbsolutePath();
 
         String volumeUuid = null;
-        if (apkPath.startsWith(PackageParser.MNT_EXPAND)) {
-            final int end = apkPath.indexOf('/', PackageParser.MNT_EXPAND.length());
-            volumeUuid = apkPath.substring(PackageParser.MNT_EXPAND.length(), end);
+        if (apkPath.startsWith(MNT_EXPAND)) {
+            final int end = apkPath.indexOf('/', MNT_EXPAND.length());
+            volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
         }
 
-        if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
+        if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
 
         final int cookie = assets.findCookieForPath(apkPath);
         if (cookie == 0) {
@@ -454,7 +470,7 @@
         }
 
         try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
-                PackageParser.ANDROID_MANIFEST_FILENAME)) {
+                ANDROID_MANIFEST_FILENAME)) {
             final Resources res = new Resources(assets, mDisplayMetrics, null);
 
             ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,
@@ -495,7 +511,7 @@
 
             pkg.setVolumeUuid(volumeUuid);
 
-            if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) {
+            if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
                 pkg.setSigningDetails(getSigningDetails(pkg, false));
             } else {
                 pkg.setSigningDetails(SigningDetails.UNKNOWN);
@@ -512,7 +528,7 @@
             ParsingPackage pkg, int splitIndex, AssetManager assets, int flags) {
         final String apkPath = pkg.getSplitCodePaths()[splitIndex];
 
-        if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
+        if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
 
         // This must always succeed, as the path has been added to the AssetManager before.
         final int cookie = assets.findCookieForPath(apkPath);
@@ -521,7 +537,7 @@
                     "Failed adding asset path: " + apkPath);
         }
         try (XmlResourceParser parser = assets.openXmlResourceParser(cookie,
-                PackageParser.ANDROID_MANIFEST_FILENAME)) {
+                ANDROID_MANIFEST_FILENAME)) {
             Resources res = new Resources(assets, mDisplayMetrics, null);
             ParseResult<ParsingPackage> parseResult = parseSplitApk(input, pkg, res,
                     parser, flags, splitIndex);
@@ -620,9 +636,9 @@
 
             final ParseResult result;
             String tagName = parser.getName();
-            if (PackageParser.TAG_APPLICATION.equals(tagName)) {
+            if (TAG_APPLICATION.equals(tagName)) {
                 if (foundApp) {
-                    if (PackageParser.RIGID_PARSER) {
+                    if (RIGID_PARSER) {
                         result = input.error("<manifest> has more than one <application>");
                     } else {
                         Slog.w(TAG, "<manifest> has more than one <application>");
@@ -701,7 +717,7 @@
                     ParseResult<ParsedActivity> activityResult =
                             ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
                                     res,
-                                    parser, flags, PackageParser.sUseRoundIcon, input);
+                                    parser, flags, sUseRoundIcon, input);
                     if (activityResult.isSuccess()) {
                         ParsedActivity activity = activityResult.getResult();
                         if (isActivity) {
@@ -716,7 +732,7 @@
                 case "service":
                     ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(
                             mSeparateProcesses, pkg, res, parser, flags,
-                            PackageParser.sUseRoundIcon, input);
+                            sUseRoundIcon, input);
                     if (serviceResult.isSuccess()) {
                         ParsedService service = serviceResult.getResult();
                         pkg.addService(service);
@@ -727,7 +743,7 @@
                 case "provider":
                     ParseResult<ParsedProvider> providerResult =
                             ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
-                                    flags, PackageParser.sUseRoundIcon, input);
+                                    flags, sUseRoundIcon, input);
                     if (providerResult.isSuccess()) {
                         ParsedProvider provider = providerResult.getResult();
                         pkg.addProvider(provider);
@@ -737,7 +753,7 @@
                     break;
                 case "activity-alias":
                     activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser,
-                            PackageParser.sUseRoundIcon, input);
+                            sUseRoundIcon, input);
                     if (activityResult.isSuccess()) {
                         ParsedActivity activity = activityResult.getResult();
                         pkg.addActivity(activity);
@@ -815,12 +831,12 @@
             return sharedUserResult;
         }
 
-        pkg.setInstallLocation(anInteger(PackageParser.PARSE_DEFAULT_INSTALL_LOCATION,
+        pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
                 R.styleable.AndroidManifest_installLocation, sa))
-                .setTargetSandboxVersion(anInteger(PackageParser.PARSE_DEFAULT_TARGET_SANDBOX,
+                .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
                         R.styleable.AndroidManifest_targetSandboxVersion, sa))
                 /* Set the global "on SD card" flag */
-                .setExternalStorage((flags & PackageParser.PARSE_EXTERNAL_STORAGE) != 0);
+                .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
 
         boolean foundApp = false;
         final int depth = parser.getDepth();
@@ -836,9 +852,9 @@
             final ParseResult result;
 
             // <application> has special logic, so it's handled outside the general method
-            if (PackageParser.TAG_APPLICATION.equals(tagName)) {
+            if (TAG_APPLICATION.equals(tagName)) {
                 if (foundApp) {
-                    if (PackageParser.RIGID_PARSER) {
+                    if (RIGID_PARSER) {
                         result = input.error("<manifest> has more than one <application>");
                     } else {
                         Slog.w(TAG, "<manifest> has more than one <application>");
@@ -897,51 +913,51 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
             throws IOException, XmlPullParserException {
         switch (tag) {
-            case PackageParser.TAG_OVERLAY:
+            case TAG_OVERLAY:
                 return parseOverlay(input, pkg, res, parser);
-            case PackageParser.TAG_KEY_SETS:
+            case TAG_KEY_SETS:
                 return parseKeySets(input, pkg, res, parser);
             case "feature": // TODO moltmann: Remove
-            case PackageParser.TAG_ATTRIBUTION:
+            case TAG_ATTRIBUTION:
                 return parseAttribution(input, pkg, res, parser);
-            case PackageParser.TAG_PERMISSION_GROUP:
+            case TAG_PERMISSION_GROUP:
                 return parsePermissionGroup(input, pkg, res, parser);
-            case PackageParser.TAG_PERMISSION:
+            case TAG_PERMISSION:
                 return parsePermission(input, pkg, res, parser);
-            case PackageParser.TAG_PERMISSION_TREE:
+            case TAG_PERMISSION_TREE:
                 return parsePermissionTree(input, pkg, res, parser);
-            case PackageParser.TAG_USES_PERMISSION:
-            case PackageParser.TAG_USES_PERMISSION_SDK_M:
-            case PackageParser.TAG_USES_PERMISSION_SDK_23:
+            case TAG_USES_PERMISSION:
+            case TAG_USES_PERMISSION_SDK_M:
+            case TAG_USES_PERMISSION_SDK_23:
                 return parseUsesPermission(input, pkg, res, parser);
-            case PackageParser.TAG_USES_CONFIGURATION:
+            case TAG_USES_CONFIGURATION:
                 return parseUsesConfiguration(input, pkg, res, parser);
-            case PackageParser.TAG_USES_FEATURE:
+            case TAG_USES_FEATURE:
                 return parseUsesFeature(input, pkg, res, parser);
-            case PackageParser.TAG_FEATURE_GROUP:
+            case TAG_FEATURE_GROUP:
                 return parseFeatureGroup(input, pkg, res, parser);
-            case PackageParser.TAG_USES_SDK:
+            case TAG_USES_SDK:
                 return parseUsesSdk(input, pkg, res, parser);
-            case PackageParser.TAG_SUPPORT_SCREENS:
+            case TAG_SUPPORT_SCREENS:
                 return parseSupportScreens(input, pkg, res, parser);
-            case PackageParser.TAG_PROTECTED_BROADCAST:
+            case TAG_PROTECTED_BROADCAST:
                 return parseProtectedBroadcast(input, pkg, res, parser);
-            case PackageParser.TAG_INSTRUMENTATION:
+            case TAG_INSTRUMENTATION:
                 return parseInstrumentation(input, pkg, res, parser);
-            case PackageParser.TAG_ORIGINAL_PACKAGE:
+            case TAG_ORIGINAL_PACKAGE:
                 return parseOriginalPackage(input, pkg, res, parser);
-            case PackageParser.TAG_ADOPT_PERMISSIONS:
+            case TAG_ADOPT_PERMISSIONS:
                 return parseAdoptPermissions(input, pkg, res, parser);
-            case PackageParser.TAG_USES_GL_TEXTURE:
-            case PackageParser.TAG_COMPATIBLE_SCREENS:
-            case PackageParser.TAG_SUPPORTS_INPUT:
-            case PackageParser.TAG_EAT_COMMENT:
+            case TAG_USES_GL_TEXTURE:
+            case TAG_COMPATIBLE_SCREENS:
+            case TAG_SUPPORTS_INPUT:
+            case TAG_EAT_COMMENT:
                 // Just skip this tag
                 XmlUtils.skipCurrentTag(parser);
                 return input.success(pkg);
-            case PackageParser.TAG_RESTRICT_UPDATE:
+            case TAG_RESTRICT_UPDATE:
                 return parseRestrictUpdateHash(flags, input, pkg, res, parser);
-            case PackageParser.TAG_QUERIES:
+            case TAG_QUERIES:
                 return parseQueries(input, pkg, res, parser);
             default:
                 return ParsingUtils.unknownTag("<manifest>", pkg, parser, input);
@@ -1125,7 +1141,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         ParseResult<ParsedPermissionGroup> result = ParsedPermissionUtils.parsePermissionGroup(
-                pkg, res, parser, PackageParser.sUseRoundIcon, input);
+                pkg, res, parser, sUseRoundIcon, input);
         if (result.isError()) {
             return input.error(result);
         }
@@ -1136,7 +1152,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermission(
-                pkg, res, parser, PackageParser.sUseRoundIcon, input);
+                pkg, res, parser, sUseRoundIcon, input);
         if (result.isError()) {
             return input.error(result);
         }
@@ -1147,7 +1163,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermissionTree(
-                pkg, res, parser, PackageParser.sUseRoundIcon, input);
+                pkg, res, parser, sUseRoundIcon, input);
         if (result.isError()) {
             return input.error(result);
         }
@@ -1405,7 +1421,7 @@
     private static ParseResult<ParsingPackage> parseUsesSdk(ParseInput input,
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws IOException, XmlPullParserException {
-        if (PackageParser.SDK_VERSION > 0) {
+        if (SDK_VERSION > 0) {
             TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk);
             try {
                 int minVers = 1;
@@ -1440,7 +1456,7 @@
                 }
 
                 ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion(
-                        targetVers, targetCode, PackageParser.SDK_CODENAMES, input);
+                        targetVers, targetCode, SDK_CODENAMES, input);
                 if (targetSdkVersionResult.isError()) {
                     return input.error(targetSdkVersionResult);
                 }
@@ -1454,7 +1470,7 @@
                 }
 
                 ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode,
-                        PackageParser.SDK_VERSION, PackageParser.SDK_CODENAMES, input);
+                        SDK_VERSION, SDK_CODENAMES, input);
                 if (minSdkVersionResult.isError()) {
                     return input.error(minSdkVersionResult);
                 }
@@ -1637,7 +1653,7 @@
 
     private static ParseResult<ParsingPackage> parseRestrictUpdateHash(int flags, ParseInput input,
             ParsingPackage pkg, Resources res, XmlResourceParser parser) {
-        if ((flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+        if ((flags & PARSE_IS_SYSTEM_DIR) != 0) {
             TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestRestrictUpdate);
             try {
                 final String hash = sa.getNonConfigurationString(
@@ -1846,7 +1862,7 @@
                         return input.error("Empty class name in package " + pkgName);
                     }
 
-                    if (PackageParser.DEBUG_BACKUP) {
+                    if (DEBUG_BACKUP) {
                         Slog.v(TAG, "android:backupAgent = " + backupAgentName
                                 + " from " + pkgName + "+" + backupAgent);
                     }
@@ -1870,7 +1886,7 @@
                     fullBackupContent = v.resourceId;
 
                     if (v.resourceId == 0) {
-                        if (PackageParser.DEBUG_BACKUP) {
+                        if (DEBUG_BACKUP) {
                             Slog.v(TAG, "fullBackupContent specified as boolean=" +
                                     (v.data == 0 ? "false" : "true"));
                         }
@@ -1880,7 +1896,7 @@
 
                     pkg.setFullBackupContent(fullBackupContent);
                 }
-                if (PackageParser.DEBUG_BACKUP) {
+                if (DEBUG_BACKUP) {
                     Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
                 }
             }
@@ -1968,6 +1984,11 @@
             }
 
             pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
+            pkg.setMemtagMode(sa.getInt(R.styleable.AndroidManifestApplication_memtagMode, -1));
+            if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInit)) {
+                pkg.setNativeHeapZeroInit(sa.getBoolean(
+                        R.styleable.AndroidManifestApplication_nativeHeapZeroInit, false));
+            }
         } finally {
             sa.recycle();
         }
@@ -1994,7 +2015,7 @@
                 case "receiver":
                     ParseResult<ParsedActivity> activityResult =
                             ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
-                                    res, parser, flags, PackageParser.sUseRoundIcon, input);
+                                    res, parser, flags, sUseRoundIcon, input);
 
                     if (activityResult.isSuccess()) {
                         ParsedActivity activity = activityResult.getResult();
@@ -2012,7 +2033,7 @@
                 case "service":
                     ParseResult<ParsedService> serviceResult =
                             ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
-                                    flags, PackageParser.sUseRoundIcon, input);
+                                    flags, sUseRoundIcon, input);
                     if (serviceResult.isSuccess()) {
                         ParsedService service = serviceResult.getResult();
                         hasServiceOrder |= (service.getOrder() != 0);
@@ -2024,7 +2045,7 @@
                 case "provider":
                     ParseResult<ParsedProvider> providerResult =
                             ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
-                                    flags, PackageParser.sUseRoundIcon, input);
+                                    flags, sUseRoundIcon, input);
                     if (providerResult.isSuccess()) {
                         pkg.addProvider(providerResult.getResult());
                     }
@@ -2033,7 +2054,7 @@
                     break;
                 case "activity-alias":
                     activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
-                            parser, PackageParser.sUseRoundIcon, input);
+                            parser, sUseRoundIcon, input);
                     if (activityResult.isSuccess()) {
                         ParsedActivity activity = activityResult.getResult();
                         hasActivityOrder |= (activity.getOrder() != 0);
@@ -2477,6 +2498,10 @@
 
     /**
      * Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
+     *
+     * This is distinct from any of the functionality of app links domain verification, and cannot
+     * be converted to remain backwards compatible. It's possible the presence of this flag does
+     * not indicate a valid package for domain verification.
      */
     private static boolean hasDomainURLs(ParsingPackage pkg) {
         final List<ParsedActivity> activities = pkg.getActivities();
@@ -2505,8 +2530,7 @@
     private static void setMaxAspectRatio(ParsingPackage pkg) {
         // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater.
         // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD.
-        float maxAspectRatio = pkg.getTargetSdkVersion() < O
-                ? PackageParser.DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
+        float maxAspectRatio = pkg.getTargetSdkVersion() < O ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0;
 
         float packageMaxAspectRatio = pkg.getMaxAspectRatio();
         if (packageMaxAspectRatio != 0) {
@@ -2514,10 +2538,8 @@
             maxAspectRatio = packageMaxAspectRatio;
         } else {
             Bundle appMetaData = pkg.getMetaData();
-            if (appMetaData != null && appMetaData.containsKey(
-                    PackageParser.METADATA_MAX_ASPECT_RATIO)) {
-                maxAspectRatio = appMetaData.getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO,
-                        maxAspectRatio);
+            if (appMetaData != null && appMetaData.containsKey(METADATA_MAX_ASPECT_RATIO)) {
+                maxAspectRatio = appMetaData.getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio);
             }
         }
 
@@ -2536,8 +2558,7 @@
             // process the meta data here since this method is called at the end of processing
             // the application and all meta data is guaranteed.
             final float activityAspectRatio = activity.getMetaData() != null
-                    ? activity.getMetaData().getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO,
-                    maxAspectRatio)
+                    ? activity.getMetaData().getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio)
                     : maxAspectRatio;
 
             activity.setMaxAspectRatio(activity.getResizeMode(), activityAspectRatio);
@@ -2565,7 +2586,7 @@
     private void setSupportsSizeChanges(ParsingPackage pkg) {
         final Bundle appMetaData = pkg.getMetaData();
         final boolean supportsSizeChanges = appMetaData != null
-                && appMetaData.getBoolean(PackageParser.METADATA_SUPPORTS_SIZE_CHANGES, false);
+                && appMetaData.getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false);
 
         List<ParsedActivity> activities = pkg.getActivities();
         int activitiesSize = activities.size();
@@ -2573,7 +2594,7 @@
             ParsedActivity activity = activities.get(index);
             if (supportsSizeChanges || (activity.getMetaData() != null
                     && activity.getMetaData().getBoolean(
-                            PackageParser.METADATA_SUPPORTS_SIZE_CHANGES, false))) {
+                            METADATA_SUPPORTS_SIZE_CHANGES, false))) {
                 activity.setSupportsSizeChanges(true);
             }
         }
@@ -2674,7 +2695,7 @@
             ParsingPackage pkg, Resources res, XmlResourceParser parser)
             throws XmlPullParserException, IOException {
         ParseResult<ParsedInstrumentation> result = ParsedInstrumentationUtils.parseInstrumentation(
-                pkg, res, parser, PackageParser.sUseRoundIcon, input);
+                pkg, res, parser, sUseRoundIcon, input);
         if (result.isError()) {
             return input.error(result);
         }
@@ -2741,14 +2762,10 @@
         }
     }
 
-    private static void convertSplitPermissions(ParsingPackage pkg) {
-        final List<PermissionManager.SplitPermissionInfo> splitPermissions =
-                ActivityThread.currentApplication().getSystemService(PermissionManager.class)
-                        .getSplitPermissions();
-
-        final int listSize = splitPermissions.size();
+    private void convertSplitPermissions(ParsingPackage pkg) {
+        final int listSize = mSplitPermissionInfos.size();
         for (int is = 0; is < listSize; is++) {
-            final PermissionManager.SplitPermissionInfo spi = splitPermissions.get(is);
+            final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
             List<String> requestedPermissions = pkg.getRequestedPermissions();
             if (pkg.getTargetSdkVersion() >= spi.getTargetSdk()
                     || !requestedPermissions.contains(spi.getSplitPermission())) {
@@ -2860,7 +2877,7 @@
                     } else if (v.type == TypedValue.TYPE_FLOAT) {
                         property = new Property(name, v.getFloat(), packageName, className);
                     } else {
-                        if (!PackageParser.RIGID_PARSER) {
+                        if (!RIGID_PARSER) {
                             Slog.w(TAG,
                                     tagName + " only supports string, integer, float, color, "
                                             + "boolean, and resource reference types: "
diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
index d65f8ff..0403a25 100644
--- a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java
@@ -22,9 +22,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
@@ -77,7 +77,7 @@
     @NonNull
     public static ParseResult<String> buildProcessName(@NonNull String pkg, String defProc,
             CharSequence procSeq, int flags, String[] separateProcesses, ParseInput input) {
-        if ((flags & PackageParser.PARSE_IGNORE_PROCESSES) != 0 && !"system".contentEquals(
+        if ((flags & ParsingPackageUtils.PARSE_IGNORE_PROCESSES) != 0 && !"system".contentEquals(
                 procSeq)) {
             return input.success(defProc != null ? defProc : pkg);
         }
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java
index 1915028..2ea24f7 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivity.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java
@@ -28,7 +28,6 @@
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -127,7 +126,7 @@
         activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
         activity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE;
         activity.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic();
-        activity.configChanges = PackageParser.getActivityConfigChanges(0, 0);
+        activity.configChanges = ParsedActivityUtils.getActivityConfigChanges(0, 0);
         activity.softInputMode = 0;
         activity.persistableMode = ActivityInfo.PERSIST_NEVER;
         activity.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
index f96bd54..f821e08 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -23,8 +23,8 @@
 import android.app.ActivityTaskManager;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.PackageParser;
 import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseInput.DeferredError;
@@ -67,6 +67,12 @@
         SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED);
     }
 
+    /**
+     * Bit mask of all the valid bits that can be set in recreateOnConfigChanges.
+     */
+    private static final int RECREATE_ON_CONFIG_CHANGES_MASK =
+            ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
+
     @NonNull
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public static ParseResult<ParsedActivity> parseActivityOrReceiver(String[] separateProcesses,
@@ -153,7 +159,7 @@
                 activity.rotationAnimation = sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED);
                 activity.softInputMode = sa.getInt(R.styleable.AndroidManifestActivity_windowSoftInputMode, 0);
 
-                activity.configChanges = PackageParser.getActivityConfigChanges(
+                activity.configChanges = getActivityConfigChanges(
                         sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0),
                         sa.getInt(R.styleable.AndroidManifestActivity_recreateOnConfigChanges, 0));
 
@@ -345,7 +351,7 @@
                     if (intent != null) {
                         activity.order = Math.max(intent.getOrder(), activity.order);
                         activity.addIntent(intent);
-                        if (PackageParser.LOG_UNSAFE_BROADCASTS && isReceiver
+                        if (LOG_UNSAFE_BROADCASTS && isReceiver
                                 && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
                             int actionCount = intent.countActions();
                             for (int i = 0; i < actionCount; i++) {
@@ -354,7 +360,7 @@
                                     continue;
                                 }
 
-                                if (!PackageParser.SAFE_BROADCASTS.contains(action)) {
+                                if (!SAFE_BROADCASTS.contains(action)) {
                                     Slog.w(TAG,
                                             "Broadcast " + action + " may never be delivered to "
                                                     + pkg.getPackageName() + " as requested at: "
@@ -532,7 +538,7 @@
             ParsedActivity activity, ParseInput input) {
         // There isn't a metadata for us to fall back. Whatever is in layout is correct.
         if (activity.metaData == null || !activity.metaData.containsKey(
-                PackageParser.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
+                ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) {
             return input.success(activity.windowLayout);
         }
 
@@ -542,7 +548,7 @@
         }
 
         String windowLayoutAffinity = activity.metaData.getString(
-                PackageParser.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
+                ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY);
         ActivityInfo.WindowLayout layout = activity.windowLayout;
         if (layout == null) {
             layout = new ActivityInfo.WindowLayout(-1 /* width */, -1 /* widthFraction */,
@@ -553,4 +559,14 @@
         }
         return input.success(layout);
     }
+
+    /**
+     * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml.
+     * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from
+     *                                AndroidManifest.xml.
+     * @hide
+     */
+    static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) {
+        return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK);
+    }
 }
diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
index 368dcfd..939e77f 100644
--- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java
@@ -21,6 +21,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageParser;
 import android.content.pm.parsing.ParsingPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.ParsingUtils;
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseResult;
@@ -65,7 +66,7 @@
                 }
             }
 
-            if (PackageParser.sUseRoundIcon) {
+            if (ParsingPackageUtils.sUseRoundIcon) {
                 intentInfo.icon = sa.getResourceId(
                         R.styleable.AndroidManifestIntentFilter_roundIcon, 0);
             }
@@ -141,7 +142,7 @@
 
         intentInfo.hasDefault = intentInfo.hasCategory(Intent.CATEGORY_DEFAULT);
 
-        if (PackageParser.DEBUG_PARSER) {
+        if (DEBUG) {
             final StringBuilder cats = new StringBuilder("Intent d=");
             cats.append(intentInfo.isHasDefault());
             cats.append(", cat=");
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
index cbd2c55..9012b5ce 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java
@@ -108,17 +108,14 @@
 
         permission.protectionLevel = PermissionInfo.fixProtectionLevel(permission.protectionLevel);
 
-        if (permission.getProtectionFlags() != 0) {
-            if ((permission.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) == 0
-                    && (permission.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
-                    == 0
-                    && (permission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
-                    != PermissionInfo.PROTECTION_SIGNATURE
-                    && (permission.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
-                    != PermissionInfo.PROTECTION_INTERNAL) {
-                return input.error("<permission>  protectionLevel specifies a non-instant flag "
-                        + "but is not based on signature or internal type");
-            }
+        final int otherProtectionFlags = permission.getProtectionFlags()
+                & ~(PermissionInfo.PROTECTION_FLAG_APPOP | PermissionInfo.PROTECTION_FLAG_INSTANT
+                | PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY);
+        if (otherProtectionFlags != 0
+                && permission.getProtection() != PermissionInfo.PROTECTION_SIGNATURE
+                && permission.getProtection() != PermissionInfo.PROTECTION_INTERNAL) {
+            return input.error("<permission> protectionLevel specifies a non-instant, non-appop,"
+                    + " non-runtimeOnly flag but is not based on signature or internal type");
         }
 
         return ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index e0ae81b..89fef9d 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
@@ -41,7 +42,10 @@
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
     protected Set<String> deniedPermissions = emptySet();
 
-    protected int gwpAsanMode = -1;
+    protected int gwpAsanMode = ApplicationInfo.GWP_ASAN_DEFAULT;
+    protected int memtagMode = ApplicationInfo.MEMTAG_DEFAULT;
+    @Nullable
+    protected Boolean nativeHeapZeroInit = null;
 
     public ParsedProcess() {
     }
@@ -57,7 +61,7 @@
 
 
 
-    // Code below generated by codegen v1.0.15.
+    // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -74,7 +78,9 @@
     public ParsedProcess(
             @NonNull String name,
             @NonNull Set<String> deniedPermissions,
-            int gwpAsanMode) {
+            int gwpAsanMode,
+            int memtagMode,
+            @Nullable Boolean nativeHeapZeroInit) {
         this.name = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
@@ -82,6 +88,8 @@
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, deniedPermissions);
         this.gwpAsanMode = gwpAsanMode;
+        this.memtagMode = memtagMode;
+        this.nativeHeapZeroInit = nativeHeapZeroInit;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -102,6 +110,16 @@
     }
 
     @DataClass.Generated.Member
+    public int getMemtagMode() {
+        return memtagMode;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable Boolean getNativeHeapZeroInit() {
+        return nativeHeapZeroInit;
+    }
+
+    @DataClass.Generated.Member
     static Parcelling<Set<String>> sParcellingForDeniedPermissions =
             Parcelling.Cache.get(
                     Parcelling.BuiltIn.ForInternedStringSet.class);
@@ -118,9 +136,14 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
+        byte flg = 0;
+        if (nativeHeapZeroInit != null) flg |= 0x10;
+        dest.writeByte(flg);
         dest.writeString(name);
         sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
         dest.writeInt(gwpAsanMode);
+        dest.writeInt(memtagMode);
+        if (nativeHeapZeroInit != null) dest.writeBoolean(nativeHeapZeroInit);
     }
 
     @Override
@@ -134,9 +157,12 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
+        byte flg = in.readByte();
         String _name = in.readString();
         Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
         int _gwpAsanMode = in.readInt();
+        int _memtagMode = in.readInt();
+        Boolean _nativeHeapZeroInit = (flg & 0x10) == 0 ? null : (Boolean) in.readBoolean();
 
         this.name = _name;
         com.android.internal.util.AnnotationValidations.validate(
@@ -145,6 +171,8 @@
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, deniedPermissions);
         this.gwpAsanMode = _gwpAsanMode;
+        this.memtagMode = _memtagMode;
+        this.nativeHeapZeroInit = _nativeHeapZeroInit;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -164,10 +192,10 @@
     };
 
     @DataClass.Generated(
-            time = 1584557524776L,
-            codegenVersion = "1.0.15",
+            time = 1611615591258L,
+            codegenVersion = "1.0.22",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcess.java",
-            inputSignatures = "protected @android.annotation.NonNull java.lang.String name\nprotected @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprotected  int gwpAsanMode\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
+            inputSignatures = "protected @android.annotation.NonNull java.lang.String name\nprotected @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprotected  int gwpAsanMode\nprotected  int memtagMode\nprotected @android.annotation.Nullable java.lang.Boolean nativeHeapZeroInit\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcess extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=false, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
index 9bff719..2579774 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -103,6 +103,11 @@
             }
 
             proc.gwpAsanMode = sa.getInt(R.styleable.AndroidManifestProcess_gwpAsanMode, -1);
+            proc.memtagMode = sa.getInt(R.styleable.AndroidManifestProcess_memtagMode, -1);
+            if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInit)) {
+                proc.nativeHeapZeroInit =
+                        sa.getBoolean(R.styleable.AndroidManifestProcess_nativeHeapZeroInit, false);
+            }
         } finally {
             sa.recycle();
         }
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index 9e3a8f4..f3caf60 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -18,9 +18,11 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.ParseFlags;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
@@ -36,20 +38,21 @@
  * @hide
  */
 public class DefaultSplitAssetLoader implements SplitAssetLoader {
-    private final String mBaseCodePath;
-    private final String[] mSplitCodePaths;
+    private final String mBaseApkPath;
+    private final String[] mSplitApkPaths;
     private final @ParseFlags int mFlags;
     private AssetManager mCachedAssetManager;
 
-    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) {
-        mBaseCodePath = pkg.baseCodePath;
-        mSplitCodePaths = pkg.splitCodePaths;
+    public DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
+        mBaseApkPath = pkg.getBaseApkPath();
+        mSplitApkPaths = pkg.getSplitApkPaths();
         mFlags = flags;
     }
 
     private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
             throws PackageParserException {
-        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+        if ((flags & ParsingPackageUtils.PARSE_MUST_BE_APK) != 0
+                && !ApkLiteParseUtils.isApkPath(path)) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                     "Invalid package file: " + path);
         }
@@ -68,16 +71,16 @@
             return mCachedAssetManager;
         }
 
-        ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
-                ? mSplitCodePaths.length : 0) + 1];
+        ApkAssets[] apkAssets = new ApkAssets[(mSplitApkPaths != null
+                ? mSplitApkPaths.length : 0) + 1];
 
         // Load the base.
         int splitIdx = 0;
-        apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+        apkAssets[splitIdx++] = loadApkAssets(mBaseApkPath, mFlags);
 
         // Load any splits.
-        if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
-            for (String apkPath : mSplitCodePaths) {
+        if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
+            for (String apkPath : mSplitApkPaths) {
                 apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
             }
         }
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 58eaabf..523ca40 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -19,9 +19,11 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.ParseFlags;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
@@ -45,14 +47,14 @@
     private final ApkAssets[][] mCachedSplitApks;
     private final AssetManager[] mCachedAssetManagers;
 
-    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
+    public SplitAssetDependencyLoader(PackageLite pkg,
             SparseArray<int[]> dependencies, @ParseFlags int flags) {
         super(dependencies);
 
         // The base is inserted into index 0, so we need to shift all the splits by 1.
-        mSplitPaths = new String[pkg.splitCodePaths.length + 1];
-        mSplitPaths[0] = pkg.baseCodePath;
-        System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
+        mSplitPaths = new String[pkg.getSplitApkPaths().length + 1];
+        mSplitPaths[0] = pkg.getBaseApkPath();
+        System.arraycopy(pkg.getSplitApkPaths(), 0, mSplitPaths, 1, pkg.getSplitApkPaths().length);
 
         mFlags = flags;
         mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
@@ -66,7 +68,8 @@
 
     private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
             throws PackageParserException {
-        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+        if ((flags & ParsingPackageUtils.PARSE_MUST_BE_APK) != 0
+                && !ApkLiteParseUtils.isApkPath(path)) {
             throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                     "Invalid package file: " + path);
         }
diff --git a/core/java/android/content/pm/split/SplitDependencyLoader.java b/core/java/android/content/pm/split/SplitDependencyLoader.java
index 3586546..3e68132 100644
--- a/core/java/android/content/pm/split/SplitDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitDependencyLoader.java
@@ -17,7 +17,7 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.content.pm.PackageParser;
+import android.content.pm.parsing.PackageLite;
 import android.util.IntArray;
 import android.util.SparseArray;
 
@@ -149,10 +149,19 @@
         return dst;
     }
 
-    public static @NonNull SparseArray<int[]> createDependenciesFromPackage(
-            PackageParser.PackageLite pkg) throws IllegalDependencyException {
-        // The data structure that holds the dependencies. In PackageParser, splits are stored
-        // in their own array, separate from the base. We treat all paths as equals, so
+    /**
+     * Build the split dependency tree by the given package
+     *
+     * @param pkg The package to retrieve the dependency tree
+     * @return The dependency tree of splits
+     * @throws IllegalDependencyException if the requires split is missing, targets split is
+     *         missing, it declares itself as configuration split for a non-feature split, or
+     *         cycle detected in split dependencies.
+     */
+    public static @NonNull SparseArray<int[]> createDependenciesFromPackage(PackageLite pkg)
+            throws IllegalDependencyException {
+        // The data structure that holds the dependencies. In ParsingPackageUtils, splits are
+        // stored in their own array, separate from the base. We treat all paths as equals, so
         // we need to insert the base as index 0, and shift all other splits.
         final SparseArray<int[]> splitDependencies = new SparseArray<>();
 
@@ -161,19 +170,19 @@
 
         // First write out the <uses-split> dependencies. These must appear first in the
         // array of ints, as is convention in this class.
-        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
-            if (!pkg.isFeatureSplits[splitIdx]) {
+        for (int splitIdx = 0; splitIdx < pkg.getSplitNames().length; splitIdx++) {
+            if (!pkg.getIsFeatureSplits()[splitIdx]) {
                 // Non-feature splits don't have dependencies.
                 continue;
             }
 
             // Implicit dependency on the base.
             final int targetIdx;
-            final String splitDependency = pkg.usesSplitNames[splitIdx];
+            final String splitDependency = pkg.getUsesSplitNames()[splitIdx];
             if (splitDependency != null) {
-                final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency);
+                final int depIdx = Arrays.binarySearch(pkg.getSplitNames(), splitDependency);
                 if (depIdx < 0) {
-                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                    throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
                             + "' requires split '" + splitDependency + "', which is missing.");
                 }
                 targetIdx = depIdx + 1;
@@ -188,26 +197,26 @@
         // dependencies and are considered leaves.
         //
         // At this point, all splits in splitDependencies have the first element in their array set.
-        for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) {
-            if (pkg.isFeatureSplits[splitIdx]) {
+        for (int splitIdx = 0, size = pkg.getSplitNames().length; splitIdx < size; splitIdx++) {
+            if (pkg.getIsFeatureSplits()[splitIdx]) {
                 // Feature splits are not configForSplits.
                 continue;
             }
 
             // Implicit feature for the base.
             final int targetSplitIdx;
-            final String configForSplit = pkg.configForSplit[splitIdx];
+            final String configForSplit = pkg.getConfigForSplit()[splitIdx];
             if (configForSplit != null) {
-                final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit);
+                final int depIdx = Arrays.binarySearch(pkg.getSplitNames(), configForSplit);
                 if (depIdx < 0) {
-                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                    throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
                             + "' targets split '" + configForSplit + "', which is missing.");
                 }
 
-                if (!pkg.isFeatureSplits[depIdx]) {
-                    throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx]
+                if (!pkg.getIsFeatureSplits()[depIdx]) {
+                    throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx]
                             + "' declares itself as configuration split for a non-feature split '"
-                            + pkg.splitNames[depIdx] + "'");
+                            + pkg.getSplitNames()[depIdx] + "'");
                 }
                 targetSplitIdx = depIdx + 1;
             } else {
diff --git a/apex/permission/framework/java/android/permission/PermissionState.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl
similarity index 88%
rename from apex/permission/framework/java/android/permission/PermissionState.java
rename to core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl
index e810db8..c143cc5 100644
--- a/apex/permission/framework/java/android/permission/PermissionState.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.aidl
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package android.permission;
+package android.content.pm.verify.domain;
 
-/**
- * @hide
- */
-public class PermissionState {}
+parcelable DomainVerificationInfo;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
new file mode 100644
index 0000000..7afbe1f
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Contains the state of all domains for a given package on device. Used by the domain verification
+ * agent to determine the domains declared by a package that need to be verified by comparing
+ * against the digital asset links response from the server hosting that domain.
+ * <p>
+ * These values for each domain can be modified through
+ * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
+        genEqualsHashCode = true)
+public final class DomainVerificationInfo implements Parcelable {
+
+    /**
+     * A domain verification ID for use in later API calls. This represents the snapshot of the
+     * domains for a package on device, and will be invalidated whenever the package changes.
+     * <p>
+     * An exception will be thrown at the next API call that receives the ID if it is no longer
+     * valid.
+     * <p>
+     * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+     * which point it can use the package name to evict existing requests with an invalid set ID. If
+     * the caller wants to manually check if any IDs have been invalidate, the {@link
+     * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+     * the last query of this method, prompting the caller to re-query.
+     * <p>
+     * This allows the caller to arbitrarily grant or revoke domain verification status, through
+     * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+     */
+    @NonNull
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForUUID.class)
+    private final UUID mIdentifier;
+
+    /**
+     * The package name that this data corresponds to.
+     */
+    @NonNull
+    private final String mPackageName;
+
+    /**
+     * Map of host names to their current state. State is an integer, which defaults to
+     * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+     * domain verification agent (the intended consumer of this API), which can be equal
+     * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+     * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+     * any unsuccessful response.
+     * <p>
+     * Any value non-inclusive between those 2 values are reserved for use by the system.
+     * The domain verification agent may be able to act on these reserved values, and this
+     * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+     * It is expected that the agent attempt to verify all domains that it can modify the
+     * state of, even if it does not understand the meaning of those values.
+     */
+    @NonNull
+    private final Map<String, Integer> mHostToStateMap;
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new DomainVerificationInfo.
+     *
+     * @param identifier
+     *   A domain verification ID for use in later API calls. This represents the snapshot of the
+     *   domains for a package on device, and will be invalidated whenever the package changes.
+     *   <p>
+     *   An exception will be thrown at the next API call that receives the ID if it is no longer
+     *   valid.
+     *   <p>
+     *   The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+     *   which point it can use the package name to evict existing requests with an invalid set ID. If
+     *   the caller wants to manually check if any IDs have been invalidate, the {@link
+     *   PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+     *   the last query of this method, prompting the caller to re-query.
+     *   <p>
+     *   This allows the caller to arbitrarily grant or revoke domain verification status, through
+     *   {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+     * @param packageName
+     *   The package name that this data corresponds to.
+     * @param hostToStateMap
+     *   Map of host names to their current state. State is an integer, which defaults to
+     *   {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+     *   domain verification agent (the intended consumer of this API), which can be equal
+     *   to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+     *   greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+     *   any unsuccessful response.
+     *   <p>
+     *   Any value non-inclusive between those 2 values are reserved for use by the system.
+     *   The domain verification agent may be able to act on these reserved values, and this
+     *   ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+     *   It is expected that the agent attempt to verify all domains that it can modify the
+     *   state of, even if it does not understand the meaning of those values.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public DomainVerificationInfo(
+            @NonNull UUID identifier,
+            @NonNull String packageName,
+            @NonNull Map<String,Integer> hostToStateMap) {
+        this.mIdentifier = identifier;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mIdentifier);
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mHostToStateMap = hostToStateMap;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHostToStateMap);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * A domain verification ID for use in later API calls. This represents the snapshot of the
+     * domains for a package on device, and will be invalidated whenever the package changes.
+     * <p>
+     * An exception will be thrown at the next API call that receives the ID if it is no longer
+     * valid.
+     * <p>
+     * The caller may also be notified with a broadcast whenever a package and ID is invalidated, at
+     * which point it can use the package name to evict existing requests with an invalid set ID. If
+     * the caller wants to manually check if any IDs have been invalidate, the {@link
+     * PackageManager#getChangedPackages(int)} API will allow tracking the packages changed since
+     * the last query of this method, prompting the caller to re-query.
+     * <p>
+     * This allows the caller to arbitrarily grant or revoke domain verification status, through
+     * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}.
+     */
+    @DataClass.Generated.Member
+    public @NonNull UUID getIdentifier() {
+        return mIdentifier;
+    }
+
+    /**
+     * The package name that this data corresponds to.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Map of host names to their current state. State is an integer, which defaults to
+     * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the
+     * domain verification agent (the intended consumer of this API), which can be equal
+     * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or
+     * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for
+     * any unsuccessful response.
+     * <p>
+     * Any value non-inclusive between those 2 values are reserved for use by the system.
+     * The domain verification agent may be able to act on these reserved values, and this
+     * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}.
+     * It is expected that the agent attempt to verify all domains that it can modify the
+     * state of, even if it does not understand the meaning of those values.
+     */
+    @DataClass.Generated.Member
+    public @NonNull Map<String,Integer> getHostToStateMap() {
+        return mHostToStateMap;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "DomainVerificationInfo { " +
+                "identifier = " + mIdentifier + ", " +
+                "packageName = " + mPackageName + ", " +
+                "hostToStateMap = " + mHostToStateMap +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(DomainVerificationInfo other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        DomainVerificationInfo that = (DomainVerificationInfo) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mIdentifier, that.mIdentifier)
+                && java.util.Objects.equals(mPackageName, that.mPackageName)
+                && java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mIdentifier);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap);
+        return _hash;
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<UUID> sParcellingForIdentifier =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForUUID.class);
+    static {
+        if (sParcellingForIdentifier == null) {
+            sParcellingForIdentifier = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForUUID());
+        }
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
+        dest.writeString(mPackageName);
+        dest.writeMap(mHostToStateMap);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ DomainVerificationInfo(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        UUID identifier = sParcellingForIdentifier.unparcel(in);
+        String packageName = in.readString();
+        Map<String,Integer> hostToStateMap = new java.util.LinkedHashMap<>();
+        in.readMap(hostToStateMap, Integer.class.getClassLoader());
+
+        this.mIdentifier = identifier;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mIdentifier);
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mHostToStateMap = hostToStateMap;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHostToStateMap);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<DomainVerificationInfo> CREATOR
+            = new Parcelable.Creator<DomainVerificationInfo>() {
+        @Override
+        public DomainVerificationInfo[] newArray(int size) {
+            return new DomainVerificationInfo[size];
+        }
+
+        @Override
+        public DomainVerificationInfo createFromParcel(@NonNull android.os.Parcel in) {
+            return new DomainVerificationInfo(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1611862790369L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java",
+            inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
new file mode 100644
index 0000000..af12536
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.UserHandle;
+
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * System service to access the domain verification APIs.
+ *
+ * Allows the approved domain verification
+ * agent on the device (the sole holder of
+ * {@link android.Manifest.permission#DOMAIN_VERIFICATION_AGENT}) to update the approval status
+ * of domains declared by applications in their AndroidManifest.xml, to allow them to open those
+ * links inside the app when selected by the user. This is done through querying
+ * {@link #getDomainVerificationInfo(String)} and calling
+ * {@link #setDomainVerificationStatus(UUID, Set, int)}.
+ *
+ * Also allows the domain preference settings (holder of
+ * {@link android.Manifest.permission#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) to update the
+ * preferences of the user, when they have chosen to explicitly allow an application to open links.
+ * This is done through querying {@link #getDomainVerificationUserSelection(String)} and calling
+ * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)} and
+ * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.DOMAIN_VERIFICATION_SERVICE)
+public interface DomainVerificationManager {
+
+    /**
+     * Extra field name for a {@link DomainVerificationRequest} for the requested packages.
+     * Passed to an the domain verification agent that handles
+     * {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}.
+     */
+    String EXTRA_VERIFICATION_REQUEST =
+            "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
+
+    /**
+     * No response has been recorded by either the system or any verification agent.
+     */
+    int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
+
+    /** The verification agent has explicitly verified the domain at some point. */
+    int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
+
+    /**
+     * The first available custom response code. This and any greater integer, along with
+     * {@link #STATE_SUCCESS} are the only values settable by the verification agent. All values
+     * will be treated as if the domain is unverified.
+     */
+    int STATE_FIRST_VERIFIER_DEFINED = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+
+    /** @hide */
+    @NonNull
+    static String stateToDebugString(@DomainVerificationState.State int state) {
+        switch (state) {
+            case DomainVerificationState.STATE_NO_RESPONSE:
+                return "none";
+            case DomainVerificationState.STATE_SUCCESS:
+                return "verified";
+            case DomainVerificationState.STATE_APPROVED:
+                return "approved";
+            case DomainVerificationState.STATE_DENIED:
+                return "denied";
+            case DomainVerificationState.STATE_MIGRATED:
+                return "migrated";
+            case DomainVerificationState.STATE_RESTORED:
+                return "restored";
+            case DomainVerificationState.STATE_LEGACY_FAILURE:
+                return "legacy_failure";
+            case DomainVerificationState.STATE_SYS_CONFIG:
+                return "system_configured";
+            default:
+                return String.valueOf(state);
+        }
+    }
+
+    /**
+     * Checks if a state considers the corresponding domain to be successfully verified. The
+     * domain verification agent may use this to determine whether or not to re-verify a domain.
+     */
+    static boolean isStateVerified(@DomainVerificationState.State int state) {
+        switch (state) {
+            case DomainVerificationState.STATE_SUCCESS:
+            case DomainVerificationState.STATE_APPROVED:
+            case DomainVerificationState.STATE_MIGRATED:
+            case DomainVerificationState.STATE_RESTORED:
+            case DomainVerificationState.STATE_SYS_CONFIG:
+                return true;
+            case DomainVerificationState.STATE_NO_RESPONSE:
+            case DomainVerificationState.STATE_DENIED:
+            case DomainVerificationState.STATE_LEGACY_FAILURE:
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Checks if a state is modifiable by the domain verification agent. This is useful as the
+     * platform may add new state codes in newer versions, and older verification agents can use
+     * this method to determine if a state can be changed without having to be aware of what the
+     * new state means.
+     */
+    static boolean isStateModifiable(@DomainVerificationState.State int state) {
+        switch (state) {
+            case DomainVerificationState.STATE_NO_RESPONSE:
+            case DomainVerificationState.STATE_SUCCESS:
+            case DomainVerificationState.STATE_MIGRATED:
+            case DomainVerificationState.STATE_RESTORED:
+            case DomainVerificationState.STATE_LEGACY_FAILURE:
+                return true;
+            case DomainVerificationState.STATE_APPROVED:
+            case DomainVerificationState.STATE_DENIED:
+            case DomainVerificationState.STATE_SYS_CONFIG:
+                return false;
+            default:
+                return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+        }
+    }
+
+    /**
+     * For determine re-verify policy. This is hidden from the domain verification agent so that
+     * no behavior is made based on the result.
+     * @hide
+     */
+    static boolean isStateDefault(@DomainVerificationState.State int state) {
+        switch (state) {
+            case DomainVerificationState.STATE_NO_RESPONSE:
+            case DomainVerificationState.STATE_MIGRATED:
+            case DomainVerificationState.STATE_RESTORED:
+                return true;
+            case DomainVerificationState.STATE_SUCCESS:
+            case DomainVerificationState.STATE_APPROVED:
+            case DomainVerificationState.STATE_DENIED:
+            case DomainVerificationState.STATE_LEGACY_FAILURE:
+            case DomainVerificationState.STATE_SYS_CONFIG:
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Used to iterate all {@link DomainVerificationInfo} values to do cleanup or retries. This is
+     * usually a heavy workload and should be done infrequently.
+     *
+     * @return the current snapshot of package names with valid autoVerify URLs.
+     */
+    @NonNull
+    @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+    List<String> getValidVerificationPackageNames();
+
+    /**
+     * Retrieves the domain verification state for a given package.
+     *
+     * @return the data for the package, or null if it does not declare any autoVerify domains
+     * @throws NameNotFoundException If the package is unavailable. This is an unrecoverable error
+     *                               and should not be re-tried except on a time scheduled basis.
+     */
+    @Nullable
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+            android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
+    })
+    DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+            throws NameNotFoundException;
+
+    /**
+     * Change the verification status of the {@param domains} of the package associated with
+     * {@param domainSetId}.
+     *
+     * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
+     * @param domains     List of host names to change the state of.
+     * @param state       See {@link DomainVerificationInfo#getHostToStateMap()}.
+     * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
+     *                                  invalid. This usually means the work being processed by the
+     *                                  verification agent is outdated and a new request should
+     *                                  be scheduled, if one has not already been done as part of
+     *                                  the {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION}
+     *                                  broadcast.
+     * @throws NameNotFoundException    If the ID is known to be good, but the package is
+     *                                  unavailable. This may be because the package is
+     *                                  installed on a volume that is no longer mounted. This
+     *                                  error is unrecoverable until the package is available
+     *                                  again, and should not be re-tried except on a time
+     *                                  scheduled basis.
+     */
+    @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+    void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+            @DomainVerificationState.State int state) throws NameNotFoundException;
+
+    /**
+     * TODO(b/178525735): This documentation is incorrect in the context of UX changes.
+     * Change whether the given {@param packageName} is allowed to automatically open verified
+     * HTTP/HTTPS domains. The final state is determined along with the verification status for the
+     * specific domain being opened and other system state. An app with this enabled is not
+     * guaranteed to be the sole link handler for its domains.
+     *
+     * By default, all apps are allowed to open verified links. Users must disable them explicitly.
+     */
+    @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+    void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed)
+            throws NameNotFoundException;
+
+    /**
+     * Update the recorded user selection for the given {@param domains} for the given {@param
+     * domainSetId}. This state is recorded for the lifetime of a domain for a package on device,
+     * and will never be reset by the system short of an app data clear.
+     *
+     * This state is stored per device user. If another user needs to be changed, the appropriate
+     * permissions must be acquired and
+     * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
+     *
+     * This will be combined with the verification status and other system state to determine which
+     * application is launched to handle an app link.
+     *
+     * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
+     * @param domains     The domains to toggle the state of.
+     * @param enabled     Whether or not the app should automatically open the domains specified.
+     * @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
+     *                                  invalid.
+     * @throws NameNotFoundException    If the ID is known to be good, but the package is
+     *                                  unavailable. This may be because the package is
+     *                                  installed on a volume that is no longer mounted. This
+     *                                  error is unrecoverable until the package is available
+     *                                  again, and should not be re-tried except on a time
+     *                                  scheduled basis.
+     */
+    @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+    void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+            @NonNull Set<String> domains, boolean enabled) throws NameNotFoundException;
+
+    /**
+     * Retrieve the user selection data for the given {@param packageName} and the current user.
+     * It is the responsibility of the caller to ensure that the
+     * {@link DomainVerificationUserSelection#getIdentifier()} matches any prior API calls.
+     *
+     * This state is stored per device user. If another user needs to be accessed, the appropriate
+     * permissions must be acquired and
+     * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
+     *
+     * @param packageName The app to query state for.
+     * @return the user selection verification data for the given package for the current user,
+     * or null if the package does not declare any HTTP/HTTPS domains.
+     */
+    @Nullable
+    @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
+    DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String packageName)
+            throws NameNotFoundException;
+
+    /**
+     * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains
+     * provided by the caller is no longer valid. This may be recoverable, and the caller should
+     * re-query the package name associated with the ID using
+     * {@link #getDomainVerificationInfo(String)} in order to check. If that also fails, then the
+     * package is no longer known to the device and thus all pending work for it should be dropped.
+     *
+     * @hide
+     */
+    class InvalidDomainSetException extends IllegalArgumentException {
+
+        public static final int REASON_ID_NULL = 1;
+        public static final int REASON_ID_INVALID = 2;
+        public static final int REASON_SET_NULL_OR_EMPTY = 3;
+        public static final int REASON_UNKNOWN_DOMAIN = 4;
+
+        /** @hide */
+        @IntDef({
+                REASON_ID_NULL,
+                REASON_ID_INVALID,
+                REASON_SET_NULL_OR_EMPTY,
+                REASON_UNKNOWN_DOMAIN
+        })
+        public @interface Reason {
+        }
+
+        public static String buildMessage(@Nullable UUID domainSetId, @Nullable String packageName,
+                @Reason int reason) {
+            switch (reason) {
+                case REASON_ID_NULL:
+                    return "Domain set ID cannot be null";
+                case REASON_ID_INVALID:
+                    return "Domain set ID " + domainSetId + " has been invalidated";
+                case REASON_SET_NULL_OR_EMPTY:
+                    return "Domain set cannot be null or empty";
+                case REASON_UNKNOWN_DOMAIN:
+                    return "Domain set contains value that was not declared by the target package "
+                            + packageName;
+                default:
+                    return "Unknown failure";
+            }
+        }
+
+        @Reason
+        private final int mReason;
+
+        @Nullable
+        private final UUID mDomainSetId;
+
+        @Nullable
+        private final String mPackageName;
+
+        /** @hide */
+        public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName,
+                @Reason int reason) {
+            super(buildMessage(domainSetId, packageName, reason));
+            mDomainSetId = domainSetId;
+            mPackageName = packageName;
+            mReason = reason;
+        }
+
+        @Nullable
+        public UUID getDomainSetId() {
+            return mDomainSetId;
+        }
+
+        @Nullable
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        @Reason
+        public int getReason() {
+            return mReason;
+        }
+    }
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
new file mode 100644
index 0000000..5938def
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @hide
+ */
+@SuppressWarnings("RedundantThrows")
+public class DomainVerificationManagerImpl implements DomainVerificationManager {
+
+    public static final int ERROR_INVALID_DOMAIN_SET = 1;
+    public static final int ERROR_NAME_NOT_FOUND = 2;
+
+    @IntDef(prefix = { "ERROR_" }, value = {
+            ERROR_INVALID_DOMAIN_SET,
+            ERROR_NAME_NOT_FOUND,
+    })
+    private @interface Error {
+    }
+
+    private final Context mContext;
+
+    private final IDomainVerificationManager mDomainVerificationManager;
+
+    public DomainVerificationManagerImpl(Context context,
+            IDomainVerificationManager domainVerificationManager) {
+        mContext = context;
+        mDomainVerificationManager = domainVerificationManager;
+    }
+
+    @NonNull
+    @Override
+    public List<String> getValidVerificationPackageNames() {
+        try {
+            return mDomainVerificationManager.getValidVerificationPackageNames();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Nullable
+    @Override
+    public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+            throws NameNotFoundException {
+        try {
+            return mDomainVerificationManager.getDomainVerificationInfo(packageName);
+        } catch (Exception e) {
+            Exception converted = rethrow(e, packageName);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
+
+    @Override
+    public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+            int state) throws IllegalArgumentException, NameNotFoundException {
+        try {
+            mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
+                    new ArrayList<>(domains), state);
+        } catch (Exception e) {
+            Exception converted = rethrow(e, domainSetId);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
+
+    @Override
+    public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+            boolean allowed) throws NameNotFoundException {
+        try {
+            mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
+                    allowed, mContext.getUserId());
+        } catch (Exception e) {
+            Exception converted = rethrow(e, packageName);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
+
+    @Override
+    public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+            @NonNull Set<String> domains, boolean enabled)
+            throws IllegalArgumentException, NameNotFoundException {
+        try {
+            mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
+                    new ArrayList<>(domains), enabled, mContext.getUserId());
+        } catch (Exception e) {
+            Exception converted = rethrow(e, domainSetId);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public DomainVerificationUserSelection getDomainVerificationUserSelection(
+            @NonNull String packageName) throws NameNotFoundException {
+        try {
+            return mDomainVerificationManager.getDomainVerificationUserSelection(packageName,
+                    mContext.getUserId());
+        } catch (Exception e) {
+            Exception converted = rethrow(e, packageName);
+            if (converted instanceof NameNotFoundException) {
+                throw (NameNotFoundException) converted;
+            } else if (converted instanceof RuntimeException) {
+                throw (RuntimeException) converted;
+            } else {
+                throw new RuntimeException(converted);
+            }
+        }
+    }
+
+    private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
+        return rethrow(exception, domainSetId, null);
+    }
+
+    private Exception rethrow(Exception exception, @Nullable String packageName) {
+        return rethrow(exception, null, packageName);
+    }
+
+    private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
+            @Nullable String packageName) {
+        if (exception instanceof ServiceSpecificException) {
+            int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
+            if (packageName == null) {
+                packageName = exception.getMessage();
+            }
+
+            @Error int managerErrorCode = packedErrorCode & 0xFFFF;
+            switch (managerErrorCode) {
+                case ERROR_INVALID_DOMAIN_SET:
+                    int errorSpecificCode = packedErrorCode >> 16;
+                    return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
+                            domainSetId, packageName, errorSpecificCode));
+                case ERROR_NAME_NOT_FOUND:
+                    return new NameNotFoundException(packageName);
+                default:
+                    return exception;
+            }
+        } else if (exception instanceof RemoteException) {
+            return ((RemoteException) exception).rethrowFromSystemServer();
+        } else {
+            return exception;
+        }
+    }
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
new file mode 100644
index 0000000..473abce
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Intent;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Set;
+
+/**
+ * Request object sent in the {@link Intent} that's broadcast to the domain verification
+ * agent, retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}.
+ * <p>
+ * This contains the set of packages which have been invalidated and will require
+ * re-verification. The exact domains can be retrieved with
+ * {@link DomainVerificationManager#getDomainVerificationInfo(String)}
+ *
+ * @hide
+ */
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genHiddenConstructor = true, genAidl = false, genEqualsHashCode = true)
+@SystemApi
+public final class DomainVerificationRequest implements Parcelable {
+
+    /**
+     * The package names of the apps that need to be verified. The receiver should call
+     * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+     * these values to get the actual set of domains that need to be acted on.
+     */
+    @NonNull
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForStringSet.class)
+    private final Set<String> mPackageNames;
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new DomainVerificationRequest.
+     *
+     * @param packageNames
+     *   The package names of the apps that need to be verified. The receiver should call
+     *   {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+     *   these values to get the actual set of domains that need to be acted on.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public DomainVerificationRequest(
+            @NonNull Set<String> packageNames) {
+        this.mPackageNames = packageNames;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageNames);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The package names of the apps that need to be verified. The receiver should call
+     * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of
+     * these values to get the actual set of domains that need to be acted on.
+     */
+    @DataClass.Generated.Member
+    public @NonNull Set<String> getPackageNames() {
+        return mPackageNames;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(DomainVerificationRequest other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        DomainVerificationRequest that = (DomainVerificationRequest) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mPackageNames, that.mPackageNames);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mPackageNames);
+        return _hash;
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<Set<String>> sParcellingForPackageNames =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForStringSet.class);
+    static {
+        if (sParcellingForPackageNames == null) {
+            sParcellingForPackageNames = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForStringSet());
+        }
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        sParcellingForPackageNames.parcel(mPackageNames, dest, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ DomainVerificationRequest(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        Set<String> packageNames = sParcellingForPackageNames.unparcel(in);
+
+        this.mPackageNames = packageNames;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageNames);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<DomainVerificationRequest> CREATOR
+            = new Parcelable.Creator<DomainVerificationRequest>() {
+        @Override
+        public DomainVerificationRequest[] newArray(int size) {
+            return new DomainVerificationRequest[size];
+        }
+
+        @Override
+        public DomainVerificationRequest createFromParcel(@NonNull android.os.Parcel in) {
+            return new DomainVerificationRequest(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1611862814990L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java",
+            inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationState.java b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
new file mode 100644
index 0000000..17593ef
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationState.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 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 android.content.pm.verify.domain;
+
+import android.annotation.IntDef;
+
+/**
+ * @hide
+ */
+public interface DomainVerificationState {
+
+    /**
+     * @hide
+     */
+    @IntDef({
+            STATE_NO_RESPONSE,
+            STATE_SUCCESS,
+            STATE_MIGRATED,
+            STATE_RESTORED,
+            STATE_APPROVED,
+            STATE_DENIED,
+            STATE_LEGACY_FAILURE,
+            STATE_SYS_CONFIG,
+            STATE_FIRST_VERIFIER_DEFINED
+    })
+    @interface State {
+    }
+
+    // TODO(b/159952358): Document all the places that states need to be updated when one is added
+    /**
+     * @see DomainVerificationManager#STATE_NO_RESPONSE
+     */
+    int STATE_NO_RESPONSE = 0;
+
+    /**
+     * @see DomainVerificationManager#STATE_SUCCESS
+     */
+    int STATE_SUCCESS = 1;
+
+    /**
+     * The system has chosen to ignore the verification agent's opinion on whether the domain should
+     * be verified. This will treat the domain as verified.
+     */
+    int STATE_APPROVED = 2;
+
+    /**
+     * The system has chosen to ignore the verification agent's opinion on whether the domain should
+     * be verified. This will treat the domain as unverified.
+     */
+    int STATE_DENIED = 3;
+
+    /**
+     * The state was migrated from the previous intent filter verification API. This will treat the
+     * domain as verified, but it should be updated by the verification agent. The older API's
+     * collection and handling of verifying domains may lead to improperly migrated state.
+     */
+    int STATE_MIGRATED = 4;
+
+    /**
+     * The state was restored from a user backup or by the system. This is treated as if the domain
+     * was verified, but the verification agent may choose to re-verify this domain to be certain
+     * nothing has changed since the snapshot.
+     */
+    int STATE_RESTORED = 5;
+
+    /**
+     * The domain was failed by a legacy intent filter verification agent from v1 of the API. This
+     * is made distinct from {@link #STATE_FIRST_VERIFIER_DEFINED} to prevent any v2 verification
+     * agent from misinterpreting the result, since {@link #STATE_FIRST_VERIFIER_DEFINED} is agent
+     * specific and can be defined as a special error code.
+     */
+    int STATE_LEGACY_FAILURE = 6;
+
+    /**
+     * The application has been granted auto verification for all domains by configuration on the
+     * system image.
+     *
+     * TODO: Can be stored per-package rather than for all domains for a package to save memory.
+     */
+    int STATE_SYS_CONFIG = 7;
+
+    /**
+     * @see DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED
+     */
+    int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000;
+}
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
similarity index 87%
copy from core/java/android/service/attestation/ImpressionToken.aidl
copy to core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
index 284a4ba..ddb5ef8 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.attestation;
+package android.content.pm.verify.domain;
 
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable DomainVerificationUserSelection;
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
new file mode 100644
index 0000000..8d16f75
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2020 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 android.content.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Contains the user selection state for a package. This means all web HTTP(S) domains
+ * declared by a package in its manifest, whether or not they were marked for auto
+ * verification.
+ * <p>
+ * By default, all apps are allowed to automatically open links with domains that they've
+ * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
+ * The user can decide to disable this, disallowing the application from opening these
+ * links.
+ * <p>
+ * Separately, independent of this toggle, the user can choose specific domains to allow
+ * an app to open, which is reflected as part of {@link #getHostToUserSelectionMap()},
+ * which maps the domain name to the true/false state of whether it was enabled by the user.
+ * <p>
+ * These values can be changed through the
+ * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
+ * boolean)} and
+ * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+ * boolean)} APIs.
+ * <p>
+ * Note that because state is per user, if a different user needs to be changed, one will
+ * need to use {@link Context#createContextAsUser(UserHandle, int)} and hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ *
+ * @hide
+ */
+@SystemApi
+@SuppressWarnings("DefaultAnnotationParam")
+@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
+        genEqualsHashCode = true)
+public final class DomainVerificationUserSelection implements Parcelable {
+
+    /**
+     * @see DomainVerificationInfo#getIdentifier
+     */
+    @NonNull
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForUUID.class)
+    private final UUID mIdentifier;
+
+    /**
+     * The package name that this data corresponds to.
+     */
+    @NonNull
+    private final String mPackageName;
+
+    /**
+     * The user that this data corresponds to.
+     */
+    @NonNull
+    private final UserHandle mUser;
+
+    /**
+     * Whether or not this package is allowed to open links.
+     */
+    @NonNull
+    private final boolean mLinkHandlingAllowed;
+
+    /**
+     * Retrieve the existing user selection state for the matching
+     * {@link #getPackageName()}, as was previously set by
+     * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+     * boolean)}.
+     *
+     * @return Map of hosts to enabled state for the given package and user.
+     */
+    @NonNull
+    private final Map<String, Boolean> mHostToUserSelectionMap;
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new DomainVerificationUserSelection.
+     *
+     * @param packageName
+     *   The package name that this data corresponds to.
+     * @param user
+     *   The user that this data corresponds to.
+     * @param linkHandlingAllowed
+     *   Whether or not this package is allowed to open links.
+     * @param hostToUserSelectionMap
+     *   Retrieve the existing user selection state for the matching
+     *   {@link #getPackageName()}, as was previously set by
+     *   {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+     *   boolean)}.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public DomainVerificationUserSelection(
+            @NonNull UUID identifier,
+            @NonNull String packageName,
+            @NonNull UserHandle user,
+            @NonNull boolean linkHandlingAllowed,
+            @NonNull Map<String,Boolean> hostToUserSelectionMap) {
+        this.mIdentifier = identifier;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mIdentifier);
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mUser = user;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mUser);
+        this.mLinkHandlingAllowed = linkHandlingAllowed;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLinkHandlingAllowed);
+        this.mHostToUserSelectionMap = hostToUserSelectionMap;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHostToUserSelectionMap);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * @see DomainVerificationInfo#getIdentifier
+     */
+    @DataClass.Generated.Member
+    public @NonNull UUID getIdentifier() {
+        return mIdentifier;
+    }
+
+    /**
+     * The package name that this data corresponds to.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * The user that this data corresponds to.
+     */
+    @DataClass.Generated.Member
+    public @NonNull UserHandle getUser() {
+        return mUser;
+    }
+
+    /**
+     * Whether or not this package is allowed to open links.
+     */
+    @DataClass.Generated.Member
+    public @NonNull boolean isLinkHandlingAllowed() {
+        return mLinkHandlingAllowed;
+    }
+
+    /**
+     * Retrieve the existing user selection state for the matching
+     * {@link #getPackageName()}, as was previously set by
+     * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set,
+     * boolean)}.
+     *
+     * @return Map of hosts to enabled state for the given package and user.
+     */
+    @DataClass.Generated.Member
+    public @NonNull Map<String,Boolean> getHostToUserSelectionMap() {
+        return mHostToUserSelectionMap;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "DomainVerificationUserSelection { " +
+                "identifier = " + mIdentifier + ", " +
+                "packageName = " + mPackageName + ", " +
+                "user = " + mUser + ", " +
+                "linkHandlingAllowed = " + mLinkHandlingAllowed + ", " +
+                "hostToUserSelectionMap = " + mHostToUserSelectionMap +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        DomainVerificationUserSelection that = (DomainVerificationUserSelection) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mIdentifier, that.mIdentifier)
+                && java.util.Objects.equals(mPackageName, that.mPackageName)
+                && java.util.Objects.equals(mUser, that.mUser)
+                && mLinkHandlingAllowed == that.mLinkHandlingAllowed
+                && java.util.Objects.equals(mHostToUserSelectionMap, that.mHostToUserSelectionMap);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mIdentifier);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mUser);
+        _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mHostToUserSelectionMap);
+        return _hash;
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<UUID> sParcellingForIdentifier =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForUUID.class);
+    static {
+        if (sParcellingForIdentifier == null) {
+            sParcellingForIdentifier = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForUUID());
+        }
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mLinkHandlingAllowed) flg |= 0x8;
+        dest.writeByte(flg);
+        sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
+        dest.writeString(mPackageName);
+        dest.writeTypedObject(mUser, flags);
+        dest.writeMap(mHostToUserSelectionMap);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ DomainVerificationUserSelection(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean linkHandlingAllowed = (flg & 0x8) != 0;
+        UUID identifier = sParcellingForIdentifier.unparcel(in);
+        String packageName = in.readString();
+        UserHandle user = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
+        Map<String,Boolean> hostToUserSelectionMap = new java.util.LinkedHashMap<>();
+        in.readMap(hostToUserSelectionMap, Boolean.class.getClassLoader());
+
+        this.mIdentifier = identifier;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mIdentifier);
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mUser = user;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mUser);
+        this.mLinkHandlingAllowed = linkHandlingAllowed;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mLinkHandlingAllowed);
+        this.mHostToUserSelectionMap = hostToUserSelectionMap;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mHostToUserSelectionMap);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<DomainVerificationUserSelection> CREATOR
+            = new Parcelable.Creator<DomainVerificationUserSelection>() {
+        @Override
+        public DomainVerificationUserSelection[] newArray(int size) {
+            return new DomainVerificationUserSelection[size];
+        }
+
+        @Override
+        public DomainVerificationUserSelection createFromParcel(@NonNull android.os.Parcel in) {
+            return new DomainVerificationUserSelection(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1611799495498L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java",
+            inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
new file mode 100644
index 0000000..21dd623b
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020, 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 android.content.pm.verify.domain;
+
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import java.util.List;
+
+/**
+ * @see DomainVerificationManager
+ * @hide
+ */
+interface IDomainVerificationManager {
+
+    List<String> getValidVerificationPackageNames();
+
+    @nullable
+    DomainVerificationInfo getDomainVerificationInfo(String packageName);
+
+    @nullable
+    DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName,
+            int userId);
+
+    void setDomainVerificationStatus(String domainSetId, in List<String> domains, int state);
+
+    void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId);
+
+    void setDomainVerificationUserSelection(String domainSetId, in List<String> domains,
+            boolean enabled, int userId);
+}
diff --git a/core/java/android/content/pm/verify/domain/TEST_MAPPING b/core/java/android/content/pm/verify/domain/TEST_MAPPING
new file mode 100644
index 0000000..c6c9791
--- /dev/null
+++ b/core/java/android/content/pm/verify/domain/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "PackageManagerServiceUnitTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.test.verify.domain"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index f6edb2e..abf694f 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -89,6 +89,11 @@
     private static final int NEEDS_COMPAT_RES = 16;
 
     /**
+     * Set if the application needs to be forcibly downscaled
+     */
+    private static final int HAS_OVERRIDE_SCALING = 32;
+
+    /**
      * The effective screen density we have selected for this application.
      */
     public final int applicationDensity;
@@ -107,6 +112,11 @@
     @UnsupportedAppUsage
     public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
             boolean forceCompat) {
+        this(appInfo, screenLayout, sw, forceCompat, 1f);
+    }
+
+    public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+            boolean forceCompat, float overrideScale) {
         int compatFlags = 0;
 
         if (appInfo.targetSdkVersion < VERSION_CODES.O) {
@@ -241,7 +251,12 @@
                 compatFlags |= NEVER_NEEDS_COMPAT;
             }
 
-            if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+            if (overrideScale != 1.0f) {
+                applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
+                applicationScale = overrideScale;
+                applicationInvertedScale = 1.0f / overrideScale;
+                compatFlags |= HAS_OVERRIDE_SCALING;
+            } else if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
                 applicationDensity = DisplayMetrics.DENSITY_DEVICE;
                 applicationScale = 1.0f;
                 applicationInvertedScale = 1.0f;
@@ -277,7 +292,7 @@
      */
     @UnsupportedAppUsage
     public boolean isScalingRequired() {
-        return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
+        return (mCompatibilityFlags & (SCALING_REQUIRED | HAS_OVERRIDE_SCALING)) != 0;
     }
     
     @UnsupportedAppUsage
@@ -303,7 +318,7 @@
      */
     @UnsupportedAppUsage
     public Translator getTranslator() {
-        return isScalingRequired() ? new Translator() : null;
+        return (mCompatibilityFlags & SCALING_REQUIRED) != 0 ? new Translator() : null;
     }
 
     /**
@@ -504,6 +519,16 @@
         if (isScalingRequired()) {
             float invertedRatio = applicationInvertedScale;
             inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
+            inoutConfig.screenWidthDp = (int) ((inoutConfig.screenWidthDp * invertedRatio) + .5f);
+            inoutConfig.screenHeightDp = (int) ((inoutConfig.screenHeightDp * invertedRatio) + .5f);
+            inoutConfig.smallestScreenWidthDp =
+                    (int) ((inoutConfig.smallestScreenWidthDp * invertedRatio) + .5f);
+            inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
+            inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
+            final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
+            if (appBounds != null) {
+                appBounds.scale(invertedRatio);
+            }
         }
     }
 
diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java
index 14eb11a..ecd240d 100644
--- a/core/java/android/content/res/FontResourcesParser.java
+++ b/core/java/android/content/res/FontResourcesParser.java
@@ -47,14 +47,17 @@
         private final @NonNull String mProviderAuthority;
         private final @NonNull String mProviderPackage;
         private final @NonNull String mQuery;
+        private final @Nullable String mSystemFontFamilyName;
         private final @Nullable List<List<String>> mCerts;
 
         public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg,
-                @NonNull String query, @Nullable List<List<String>> certs) {
+                @NonNull String query, @Nullable List<List<String>> certs,
+                @Nullable String systemFontFamilyName) {
             mProviderAuthority = authority;
             mProviderPackage = pkg;
             mQuery = query;
             mCerts = certs;
+            mSystemFontFamilyName = systemFontFamilyName;
         }
 
         public @NonNull String getAuthority() {
@@ -69,6 +72,10 @@
             return mQuery;
         }
 
+        public @NonNull String getSystemFontFamilyName() {
+            return mSystemFontFamilyName;
+        }
+
         public @Nullable List<List<String>> getCerts() {
             return mCerts;
         }
@@ -166,6 +173,8 @@
         String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
         String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
         int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0);
+        String systemFontFamilyName = array.getString(
+                R.styleable.FontFamily_fontProviderSystemFontFamily);
         array.recycle();
         if (authority != null && providerPackage != null && query != null) {
             while (parser.next() != XmlPullParser.END_TAG) {
@@ -191,7 +200,13 @@
                     }
                 }
             }
-            return new ProviderResourceEntry(authority, providerPackage, query, certs);
+            return new ProviderResourceEntry(
+                    authority,
+                    providerPackage,
+                    query,
+                    certs,
+                    systemFontFamilyName
+            );
         }
         List<FontFileResourceEntry> fonts = new ArrayList<>();
         while (parser.next() != XmlPullParser.END_TAG) {
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index ea6cf2f..abb4f9f 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -16,22 +16,33 @@
 
 package android.graphics.fonts;
 
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.content.Context;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.text.FontConfig;
 import android.util.Log;
 
 import com.android.internal.graphics.fonts.IFontManager;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
  * This class gives you control of system installed font files.
+ *
+ * <p>
+ * This class gives you the information of system font configuration and ability of changing them.
+ *
  * @hide
  */
 @SystemApi
@@ -41,6 +52,135 @@
     private static final String TAG = "FontManager";
     private final @NonNull IFontManager mIFontManager;
 
+    /** @hide */
+    @IntDef(prefix = "RESULT_",
+            value = { RESULT_SUCCESS, RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                    RESULT_ERROR_VERIFICATION_FAILURE, RESULT_ERROR_VERSION_MISMATCH,
+                    RESULT_ERROR_INVALID_FONT_FILE, RESULT_ERROR_INVALID_FONT_NAME,
+                    RESULT_ERROR_DOWNGRADING, RESULT_ERROR_FAILED_UPDATE_CONFIG,
+                    RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_REMOTE_EXCEPTION })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {}
+
+    /**
+     * Indicates that the request has been processed successfully.
+     */
+    public static final int RESULT_SUCCESS = 0;
+
+    /**
+     * Indicates that a failure occurred while writing the font file to disk.
+     *
+     * This is an internal error that the system cannot place the font file for being used by
+     * application.
+     */
+    public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1;
+
+    /**
+     * Indicates that a failure occurred during the verification of the font file.
+     *
+     * The system failed to verify given font file contents and signature with system installed
+     * certificate.
+     */
+    public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2;
+
+    /**
+     * Indicates that a failure occurred as a result of invalid font format or content.
+     *
+     * Android only accepts OpenType compliant font files.
+     */
+    public static final int RESULT_ERROR_INVALID_FONT_FILE = -3;
+
+    /**
+     * Indicates a failure due to missing PostScript name in font's name table.
+     *
+     * Indicates that a failure occurred since PostScript name in the name table(ID=6) was missing.
+     * The font is expected to have a PostScript name.
+     */
+    public static final int RESULT_ERROR_INVALID_FONT_NAME = -4;
+
+    /**
+     * Indicates that a failure occurred due to downgrading the font version.
+     *
+     * The font must have equal or newer revision in its head table.
+     */
+    public static final int RESULT_ERROR_DOWNGRADING = -5;
+
+    /**
+     * Indicates that a failure occurred while updating system font configuration.
+     *
+     * This is an internal error that the system couldn't update the {@link FontConfig}.
+     */
+    public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6;
+
+    /**
+     * Indicates a failure due to disabled font updater.
+     *
+     * This is typically returned due to missing Linux kernel feature.
+     * The font updater only works with the Linux kernel that has fs-verity feature. The fs-verity
+     * is required after the device shipped with Android 11. Thus the updated device may not have
+     * fs-verity feature and font updater is disabled.
+     */
+    public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7;
+
+    /**
+     * Indicates that a failure occurred because provided {@code baseVersion} did not match.
+     *
+     * The {@code baseVersion} provided does not match to the current {@link FontConfig} version.
+     * Please get the latest configuration and update {@code baseVersion} accordingly.
+     */
+    public static final int RESULT_ERROR_VERSION_MISMATCH = -8;
+
+    /**
+     * Indicates a failure due to IPC communication.
+     */
+    public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9;
+
+    /**
+     * Indicates a failure of opening font file.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_FAILED_TO_OPEN_FONT_FILE = -10001;
+
+    /**
+     * Indicates a failure of opening signature file.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_FAILED_TO_OPEN_SIGNATURE_FILE = -10002;
+
+    /**
+     * Indicates a failure of invalid shell command arguments.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_INVALID_SHELL_ARGUMENT = -10003;
+
+    /**
+     * Indicates a failure of reading signature file.
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_INVALID_SIGNATURE_FILE = -10004;
+
+    /**
+     * Indicates a failure due to exceeding allowed signature file size (8kb).
+     *
+     * This error code is only used with the shell command interaction.
+     *
+     * @hide
+     */
+    public static final int RESULT_ERROR_SIGNATURE_TOO_LARGE = -10005;
+
+
     private FontManager(@NonNull IFontManager iFontManager) {
         mIFontManager = iFontManager;
     }
@@ -65,6 +205,70 @@
     }
 
     /**
+     * Update system installed font file.
+     *
+     * <p>
+     * To protect devices, system font updater relies on the Linux Kernel feature called fs-verity.
+     * If the device is not ready for fs-verity, {@link #RESULT_ERROR_FONT_UPDATER_DISABLED} will be
+     * returned.
+     *
+     * Android only accepts OpenType compliant font files. If other font files are provided,
+     * {@link #RESULT_ERROR_INVALID_FONT_FILE} will be returned.
+     *
+     * The font file to be updated is identified by PostScript name stored in name table. If the
+     * font file doesn't have PostScript name entry, {@link #RESULT_ERROR_INVALID_FONT_NAME} will be
+     * returned.
+     *
+     * The entire font file is verified with the given signature for the system installed
+     * certificate. If the system cannot verify the font contents,
+     * {@link #RESULT_ERROR_VERIFICATION_FAILURE} will be returned.
+     *
+     * The font file must have newer or equal revision number in the head table. In other words, the
+     * downgrading font file is not allowed. If the older font file is provided,
+     * {@link #RESULT_ERROR_DOWNGRADING} will be returned.
+     *
+     * The caller must specify the base config version for keeping consist system configuration. If
+     * the system configuration is updated for some reason between you get config with
+     * {@link #getFontConfig()} and calling this method, {@link #RESULT_ERROR_VERSION_MISMATCH} will
+     * be returned. Get the latest font configuration by calling {@link #getFontConfig()} again and
+     * try with the latest config version again.
+     *
+     * @param pfd A file descriptor of the font file.
+     * @param signature A PKCS#7 detached signature for verifying entire font files.
+     * @param baseVersion A base config version to be updated. You can get latest config version by
+     *                    {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If the
+     *                    system has newer config version, the update will fail with
+     *                    {@link #RESULT_ERROR_VERSION_MISMATCH}. Try to get the latest config and
+     *                    try update again.
+     * @return result code.
+     *
+     * @see FontConfig#getConfigVersion()
+     * @see #getFontConfig()
+     * @see #RESULT_SUCCESS
+     * @see #RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE
+     * @see #RESULT_ERROR_VERIFICATION_FAILURE
+     * @see #RESULT_ERROR_VERSION_MISMATCH
+     * @see #RESULT_ERROR_INVALID_FONT_FILE
+     * @see #RESULT_ERROR_INVALID_FONT_NAME
+     * @see #RESULT_ERROR_DOWNGRADING
+     * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
+     * @see #RESULT_ERROR_FONT_UPDATER_DISABLED
+     * @see #RESULT_ERROR_REMOTE_EXCEPTION
+     */
+    @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile(
+            @NonNull ParcelFileDescriptor pfd,
+            @NonNull byte[] signature,
+            @IntRange(from = 0) int baseVersion
+    ) {
+        try {
+            return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to call updateFont API", e);
+            return RESULT_ERROR_REMOTE_EXCEPTION;
+        }
+    }
+
+    /**
      * Factory method of the FontManager.
      *
      * Do not use this method directly. Use getSystemService(Context.FONT_SERVICE) instead.
diff --git a/core/java/android/graphics/fonts/SystemFontState.aidl b/core/java/android/graphics/fonts/FontUpdateRequest.aidl
similarity index 95%
rename from core/java/android/graphics/fonts/SystemFontState.aidl
rename to core/java/android/graphics/fonts/FontUpdateRequest.aidl
index 19b20f2..f6cb373 100644
--- a/core/java/android/graphics/fonts/SystemFontState.aidl
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.aidl
@@ -17,4 +17,4 @@
 package android.graphics.fonts;
 
 /** @hide */
-parcelable SystemFontState;
\ No newline at end of file
+parcelable FontUpdateRequest;
\ No newline at end of file
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
new file mode 100644
index 0000000..db047f8
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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 android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+/**
+ * Represents a font update request. Currently only font install request is supported.
+ * @hide
+ */
+// TODO: Support font config update.
+public final class FontUpdateRequest implements Parcelable {
+
+    public static final Creator<FontUpdateRequest> CREATOR = new Creator<FontUpdateRequest>() {
+        @Override
+        public FontUpdateRequest createFromParcel(Parcel in) {
+            return new FontUpdateRequest(in);
+        }
+
+        @Override
+        public FontUpdateRequest[] newArray(int size) {
+            return new FontUpdateRequest[size];
+        }
+    };
+
+    @NonNull
+    private final ParcelFileDescriptor mFd;
+    @NonNull
+    private final byte[] mSignature;
+
+    public FontUpdateRequest(@NonNull ParcelFileDescriptor fd, @NonNull byte[] signature) {
+        mFd = fd;
+        mSignature = signature;
+    }
+
+    private FontUpdateRequest(Parcel in) {
+        mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        mSignature = in.readBlob();
+    }
+
+    @NonNull
+    public ParcelFileDescriptor getFd() {
+        return mFd;
+    }
+
+    @NonNull
+    public byte[] getSignature() {
+        return mSignature;
+    }
+
+    @Override
+    public int describeContents() {
+        return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mFd, flags);
+        dest.writeBlob(mSignature);
+    }
+}
diff --git a/core/java/android/hardware/Battery.java b/core/java/android/hardware/Battery.java
new file mode 100644
index 0000000..24c8d76
--- /dev/null
+++ b/core/java/android/hardware/Battery.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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 android.hardware;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.os.BatteryManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The Battery class is a representation of a single battery on a device.
+ */
+public abstract class Battery {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_UNKNOWN,
+            STATUS_CHARGING,
+            STATUS_DISCHARGING,
+            STATUS_NOT_CHARGING,
+            STATUS_FULL
+    })
+    public @interface BatteryStatus {
+    }
+
+    /** Battery status is unknown. */
+    public static final int STATUS_UNKNOWN = BatteryManager.BATTERY_STATUS_UNKNOWN;
+    /** Battery is charging. */
+    public static final int STATUS_CHARGING = BatteryManager.BATTERY_STATUS_CHARGING;
+    /** Battery is discharging. */
+    public static final int STATUS_DISCHARGING = BatteryManager.BATTERY_STATUS_DISCHARGING;
+    /** Battery is connected to power but not charging. */
+    public static final int STATUS_NOT_CHARGING = BatteryManager.BATTERY_STATUS_NOT_CHARGING;
+    /** Battery is full. */
+    public static final int STATUS_FULL = BatteryManager.BATTERY_STATUS_FULL;
+
+    /**
+     * Check whether the hardware has a battery.
+     *
+     * @return True if the hardware has a battery, else false.
+     */
+    public abstract boolean hasBattery();
+
+    /**
+     * Get the battery status.
+     *
+     * @return the battery status.
+     */
+    public abstract @BatteryStatus int getStatus();
+
+    /**
+     * Get remaining battery capacity as float percentage [0.0f, 1.0f] of total capacity
+     * Returns -1 when battery capacity can't be read.
+     *
+     * @return the battery capacity.
+     */
+    public abstract @FloatRange(from = -1.0f, to = 1.0f) float getCapacity();
+}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 4145a72..08b1e24 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -29,7 +29,6 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.os.RemoteException;
-import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.util.Slog;
 
@@ -47,6 +46,13 @@
     private static final String TAG = "BiometricManager";
 
     /**
+     * An ID that should match any biometric sensor on the device.
+     *
+     * @hide
+     */
+    public static final int SENSOR_ID_ANY = -1;
+
+    /**
      * No error detected.
      */
     public static final int BIOMETRIC_SUCCESS =
@@ -139,7 +145,7 @@
          *
          * <p>This corresponds to {@link KeyProperties#AUTH_BIOMETRIC_STRONG} during key generation.
          *
-         * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int)
+         * @see android.security.keystore.KeyGenParameterSpec.Builder
          */
         int BIOMETRIC_STRONG = 0x000F;
 
@@ -182,7 +188,7 @@
          * <p>This corresponds to {@link KeyProperties#AUTH_DEVICE_CREDENTIAL} during key
          * generation.
          *
-         * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int)
+         * @see android.security.keystore.KeyGenParameterSpec.Builder
          */
         int DEVICE_CREDENTIAL = 1 << 15;
     }
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 76cf9b9..4f6a7c7 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -36,7 +36,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.security.identity.IdentityCredential;
-import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.text.TextUtils;
 import android.util.Log;
@@ -325,7 +324,7 @@
          * request authentication with the proper set of authenticators (e.g. match the
          * authenticators specified during key generation).
          *
-         * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int)
+         * @see android.security.keystore.KeyGenParameterSpec.Builder
          * @see KeyProperties#AUTH_BIOMETRIC_STRONG
          * @see KeyProperties#AUTH_DEVICE_CREDENTIAL
          *
@@ -365,6 +364,21 @@
         }
 
         /**
+         * If set, authenticate using the biometric sensor with the given ID.
+         *
+         * @param sensorId The ID of a biometric sensor, or -1 to allow any sensor (default).
+         * @return This builder.
+         *
+         * @hide
+         */
+        @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+        @NonNull
+        public Builder setSensorId(int sensorId) {
+            mPromptInfo.setSensorId(sensorId);
+            return this;
+        }
+
+        /**
          * Creates a {@link BiometricPrompt}.
          *
          * @return An instance of {@link BiometricPrompt}.
@@ -589,7 +603,8 @@
      *
      * <p>Cryptographic operations in Android can be split into two categories: auth-per-use and
      * time-based. This is specified during key creation via the timeout parameter of the
-     * {@link KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int)} API.
+     * {@code setUserAuthenticationParameters(int, int)} method of {@link
+     * android.security.keystore.KeyGenParameterSpec.Builder}.
      *
      * <p>CryptoObjects are used to unlock auth-per-use keys via
      * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor,
@@ -778,6 +793,27 @@
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
             int userId) {
+        authenticateUserForOperation(cancel, executor, callback, userId, 0 /* operationId */);
+    }
+
+    /**
+     * Authenticates for the given user and keystore operation.
+     *
+     * @param cancel An object that can be used to cancel authentication
+     * @param executor An executor to handle callback events
+     * @param callback An object to receive authentication events
+     * @param userId The user to authenticate
+     * @param operationId The keystore operation associated with authentication
+     *
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void authenticateUserForOperation(
+            @NonNull CancellationSignal cancel,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AuthenticationCallback callback,
+            int userId,
+            long operationId) {
         if (cancel == null) {
             throw new IllegalArgumentException("Must supply a cancellation signal");
         }
@@ -787,7 +823,7 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        authenticateInternal(null /* crypto */, cancel, executor, callback, userId);
+        authenticateInternal(operationId, cancel, executor, callback, userId);
     }
 
     /**
@@ -912,11 +948,31 @@
         }
     }
 
-    private void authenticateInternal(@Nullable CryptoObject crypto,
+    private void authenticateInternal(
+            @Nullable CryptoObject crypto,
             @NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
             int userId) {
+
+        mCryptoObject = crypto;
+        final long operationId = crypto != null ? crypto.getOpId() : 0L;
+        authenticateInternal(operationId, cancel, executor, callback, userId);
+    }
+
+    private void authenticateInternal(
+            long operationId,
+            @NonNull CancellationSignal cancel,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AuthenticationCallback callback,
+            int userId) {
+
+        // Ensure we don't return the wrong crypto object as an auth result.
+        if (mCryptoObject != null && mCryptoObject.getOpId() != operationId) {
+            Log.w(TAG, "CryptoObject operation ID does not match argument; setting field to null");
+            mCryptoObject = null;
+        }
+
         try {
             if (cancel.isCanceled()) {
                 Log.w(TAG, "Authentication already canceled");
@@ -925,13 +981,11 @@
                 cancel.setOnCancelListener(new OnAuthenticationCancelListener());
             }
 
-            mCryptoObject = crypto;
             mExecutor = executor;
             mAuthenticationCallback = callback;
-            final long operationId = crypto != null ? crypto.getOpId() : 0;
 
             final PromptInfo promptInfo;
-            if (crypto != null) {
+            if (operationId != 0L) {
                 // Allowed authenticators should default to BIOMETRIC_STRONG for crypto auth.
                 // Note that we use a new PromptInfo here so as to not overwrite the application's
                 // preference, since it is possible that the same prompt configuration be used
@@ -952,10 +1006,9 @@
 
         } catch (RemoteException e) {
             Log.e(TAG, "Remote exception while authenticating", e);
-            mExecutor.execute(() -> {
-                callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
-                        mContext.getString(R.string.biometric_error_hw_unavailable));
-            });
+            mExecutor.execute(() -> callback.onAuthenticationError(
+                    BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+                    mContext.getString(R.string.biometric_error_hw_unavailable)));
         }
     }
 }
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index c2eff7d..0e99f31 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -40,6 +40,7 @@
     private @BiometricManager.Authenticators.Types int mAuthenticators;
     private boolean mDisallowBiometricsIfPolicyExists;
     private boolean mReceiveSystemEvents;
+    private int mSensorId = -1;
 
     public PromptInfo() {
 
@@ -59,6 +60,7 @@
         mAuthenticators = in.readInt();
         mDisallowBiometricsIfPolicyExists = in.readBoolean();
         mReceiveSystemEvents = in.readBoolean();
+        mSensorId = in.readInt();
     }
 
     public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -93,6 +95,7 @@
         dest.writeInt(mAuthenticators);
         dest.writeBoolean(mDisallowBiometricsIfPolicyExists);
         dest.writeBoolean(mReceiveSystemEvents);
+        dest.writeInt(mSensorId);
     }
 
     public boolean containsPrivateApiConfigurations() {
@@ -166,6 +169,10 @@
         mReceiveSystemEvents = receiveSystemEvents;
     }
 
+    public void setSensorId(int sensorId) {
+        mSensorId = sensorId;
+    }
+
     // Getters
 
     public CharSequence getTitle() {
@@ -226,4 +233,8 @@
     public boolean isReceiveSystemEvents() {
         return mReceiveSystemEvents;
     }
+
+    public int getSensorId() {
+        return mSensorId;
+    }
 }
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 29a6ee2..f175e7b 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -16,8 +16,12 @@
 
 package android.hardware.devicestate;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
 
 import java.util.concurrent.Executor;
@@ -28,13 +32,19 @@
  *
  * @hide
  */
+@TestApi
 @SystemService(Context.DEVICE_STATE_SERVICE)
 public final class DeviceStateManager {
-    /** Invalid device state. */
+    /**
+     * Invalid device state.
+     *
+     * @hide
+     */
     public static final int INVALID_DEVICE_STATE = -1;
 
-    private DeviceStateManagerGlobal mGlobal;
+    private final DeviceStateManagerGlobal mGlobal;
 
+    /** @hide */
     public DeviceStateManager() {
         DeviceStateManagerGlobal global = DeviceStateManagerGlobal.getInstance();
         if (global == null) {
@@ -45,23 +55,73 @@
     }
 
     /**
+     * Returns the list of device states that are supported and can be requested with
+     * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+     */
+    @NonNull
+    public int[] getSupportedStates() {
+        return mGlobal.getSupportedStates();
+    }
+
+    /**
+     * Submits a {@link DeviceStateRequest request} to modify the device state.
+     * <p>
+     * By default, the request is kept active until a call to
+     * {@link #cancelRequest(DeviceStateRequest)} or until one of the following occurs:
+     * <ul>
+     *     <li>Another processes submits a request succeeding this request in which case the request
+     *     will be suspended until the interrupting request is canceled.
+     *     <li>The requested state has become unsupported.
+     *     <li>The process submitting the request dies.
+     * </ul>
+     * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}.
+     *
+     * @throws IllegalArgumentException if the requested state is unsupported.
+     * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE}
+     * permission is not held.
+     *
+     * @see DeviceStateRequest
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    public void requestState(@NonNull DeviceStateRequest request,
+            @Nullable @CallbackExecutor Executor executor,
+            @Nullable DeviceStateRequest.Callback callback) {
+        mGlobal.requestState(request, callback, executor);
+    }
+
+    /**
+     * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
+     * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+     * <p>
+     * This method is noop if the {@code request} has not been submitted with a call to
+     * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+     *
+     * @throws SecurityException if the {@link android.Manifest.permission#CONTROL_DEVICE_STATE}
+     * permission is not held.
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    public void cancelRequest(@NonNull DeviceStateRequest request) {
+        mGlobal.cancelRequest(request);
+    }
+
+    /**
      * Registers a listener to receive notifications about changes in device state.
      *
-     * @param listener the listener to register.
      * @param executor the executor to process notifications.
+     * @param listener the listener to register.
      *
      * @see DeviceStateListener
      */
-    public void registerDeviceStateListener(@NonNull DeviceStateListener listener,
-            @NonNull Executor executor) {
+    public void addDeviceStateListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull DeviceStateListener listener) {
         mGlobal.registerDeviceStateListener(listener, executor);
     }
 
     /**
      * Unregisters a listener previously registered with
-     * {@link #registerDeviceStateListener(DeviceStateListener, Executor)}.
+     * {@link #addDeviceStateListener(Executor, DeviceStateListener)}.
      */
-    public void unregisterDeviceStateListener(@NonNull DeviceStateListener listener) {
+    public void removeDeviceStateListener(@NonNull DeviceStateListener listener) {
         mGlobal.unregisterDeviceStateListener(listener);
     }
 
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index c8905038..b9ae88e 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -20,9 +20,11 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager.DeviceStateListener;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -67,6 +69,9 @@
 
     @GuardedBy("mLock")
     private final ArrayList<DeviceStateListenerWrapper> mListeners = new ArrayList<>();
+    @GuardedBy("mLock")
+    private final ArrayMap<IBinder, DeviceStateRequestWrapper> mRequests = new ArrayMap<>();
+
     @Nullable
     @GuardedBy("mLock")
     private Integer mLastReceivedState;
@@ -77,9 +82,84 @@
     }
 
     /**
+     * Returns the set of supported device states.
+     *
+     * @see DeviceStateManager#getSupportedStates()
+     */
+    public int[] getSupportedStates() {
+        try {
+            return mDeviceStateManager.getSupportedDeviceStates();
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Submits a {@link DeviceStateRequest request} to modify the device state.
+     *
+     * @see DeviceStateManager#requestState(DeviceStateRequest,
+     * Executor, DeviceStateRequest.Callback)
+     * @see DeviceStateRequest
+     */
+    public void requestState(@NonNull DeviceStateRequest request,
+            @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
+        if (callback == null && executor != null) {
+            throw new IllegalArgumentException("Callback must be supplied with executor.");
+        } else if (executor == null && callback != null) {
+            throw new IllegalArgumentException("Executor must be supplied with callback.");
+        }
+
+        synchronized (mLock) {
+            registerCallbackIfNeededLocked();
+
+            if (findRequestTokenLocked(request) != null) {
+                // This request has already been submitted.
+                return;
+            }
+
+            // Add the request wrapper to the mRequests array before requesting the state as the
+            // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
+            // same process as this instance.
+            IBinder token = new Binder();
+            mRequests.put(token, new DeviceStateRequestWrapper(request, callback, executor));
+
+            try {
+                mDeviceStateManager.requestState(token, request.getState(), request.getFlags());
+            } catch (RemoteException ex) {
+                mRequests.remove(token);
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
+     * {@link #requestState(DeviceStateRequest, DeviceStateRequest.Callback, Executor)}.
+     *
+     * @see DeviceStateManager#cancelRequest(DeviceStateRequest)
+     */
+    public void cancelRequest(@NonNull DeviceStateRequest request) {
+        synchronized (mLock) {
+            registerCallbackIfNeededLocked();
+
+            final IBinder token = findRequestTokenLocked(request);
+            if (token == null) {
+                // This request has not been submitted.
+                return;
+            }
+
+            try {
+                mDeviceStateManager.cancelRequest(token);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Registers a listener to receive notifications about changes in device state.
      *
-     * @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor)
+     * @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
      */
     @VisibleForTesting(visibility = Visibility.PACKAGE)
     public void registerDeviceStateListener(@NonNull DeviceStateListener listener,
@@ -112,7 +192,7 @@
      * Unregisters a listener previously registered with
      * {@link #registerDeviceStateListener(DeviceStateListener, Executor)}.
      *
-     * @see DeviceStateManager#registerDeviceStateListener(DeviceStateListener, Executor)
+     * @see DeviceStateManager#addDeviceStateListener(Executor, DeviceStateListener)
      */
     @VisibleForTesting(visibility = Visibility.PACKAGE)
     public void unregisterDeviceStateListener(DeviceStateListener listener) {
@@ -144,6 +224,17 @@
         return -1;
     }
 
+    @Nullable
+    private IBinder findRequestTokenLocked(@NonNull DeviceStateRequest request) {
+        for (int i = 0; i < mRequests.size(); i++) {
+            if (mRequests.valueAt(i).mRequest.equals(request)) {
+                return mRequests.keyAt(i);
+            }
+        }
+        return null;
+    }
+
+    /** Handles a call from the server that the device state has changed. */
     private void handleDeviceStateChanged(int newDeviceState) {
         ArrayList<DeviceStateListenerWrapper> listeners;
         synchronized (mLock) {
@@ -156,11 +247,68 @@
         }
     }
 
+    /**
+     * Handles a call from the server that a request for the supplied {@code token} has become
+     * active.
+     */
+    private void handleRequestActive(IBinder token) {
+        DeviceStateRequestWrapper request;
+        synchronized (mLock) {
+            request = mRequests.get(token);
+        }
+        if (request != null) {
+            request.notifyRequestActive();
+        }
+    }
+
+    /**
+     * Handles a call from the server that a request for the supplied {@code token} has become
+     * suspended.
+     */
+    private void handleRequestSuspended(IBinder token) {
+        DeviceStateRequestWrapper request;
+        synchronized (mLock) {
+            request = mRequests.get(token);
+        }
+        if (request != null) {
+            request.notifyRequestSuspended();
+        }
+    }
+
+    /**
+     * Handles a call from the server that a request for the supplied {@code token} has become
+     * canceled.
+     */
+    private void handleRequestCanceled(IBinder token) {
+        DeviceStateRequestWrapper request;
+        synchronized (mLock) {
+            request = mRequests.remove(token);
+        }
+        if (request != null) {
+            request.notifyRequestCanceled();
+        }
+    }
+
     private final class DeviceStateManagerCallback extends IDeviceStateManagerCallback.Stub {
         @Override
         public void onDeviceStateChanged(int deviceState) {
             handleDeviceStateChanged(deviceState);
         }
+
+        @Override
+        public void onRequestActive(IBinder token) {
+            handleRequestActive(token);
+        }
+
+        @Override
+        public void onRequestSuspended(IBinder token) {
+            handleRequestSuspended(token);
+        }
+
+        @Override
+        public void onRequestCanceled(IBinder token) {
+            handleRequestCanceled(token);
+        }
     }
 
     private static final class DeviceStateListenerWrapper {
@@ -176,4 +324,43 @@
             mExecutor.execute(() -> mDeviceStateListener.onDeviceStateChanged(newDeviceState));
         }
     }
+
+    private static final class DeviceStateRequestWrapper {
+        private final DeviceStateRequest mRequest;
+        @Nullable
+        private final DeviceStateRequest.Callback mCallback;
+        @Nullable
+        private final Executor mExecutor;
+
+        DeviceStateRequestWrapper(@NonNull DeviceStateRequest request,
+                @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
+            mRequest = request;
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        void notifyRequestActive() {
+            if (mCallback == null) {
+                return;
+            }
+
+            mExecutor.execute(() -> mCallback.onRequestActivated(mRequest));
+        }
+
+        void notifyRequestSuspended() {
+            if (mCallback == null) {
+                return;
+            }
+
+            mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
+        }
+
+        void notifyRequestCanceled() {
+            if (mCallback == null) {
+                return;
+            }
+
+            mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest));
+        }
+    }
 }
diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java
new file mode 100644
index 0000000..70f7002
--- /dev/null
+++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2021 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 android.hardware.devicestate;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * A request to alter the state of the device managed by {@link DeviceStateManager}.
+ * <p>
+ * Once constructed, a {@link DeviceStateRequest request} can be submitted with a call to
+ * {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
+ * DeviceStateRequest.Callback)}.
+ * <p>
+ * By default, the request is kept active until a call to
+ * {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following
+ * occurs:
+ * <ul>
+ *     <li>Another processes submits a request succeeding this request in which case the request
+ *     will be suspended until the interrupting request is canceled.
+ *     <li>The requested state has become unsupported.
+ *     <li>The process submitting the request dies.
+ * </ul>
+ * However, this behavior can be changed by setting flags on the request. For example, the
+ * {@link #FLAG_CANCEL_WHEN_BASE_CHANGES} flag will extend this behavior to also cancel the
+ * request whenever the base (non-override) device state changes.
+ *
+ * @see DeviceStateManager
+ *
+ * @hide
+ */
+@TestApi
+public final class DeviceStateRequest {
+    /**
+     * Flag that indicates the request should be canceled automatically when the base
+     * (non-override) device state changes. Useful when the requestor only wants the request to
+     * remain active while the base state remains constant and automatically cancel when the user
+     * manipulates the device into a different state.
+     */
+    public static final int FLAG_CANCEL_WHEN_BASE_CHANGES = 1 << 0;
+
+    /** @hide */
+    @IntDef(prefix = {"FLAG_"}, flag = true, value = {
+            FLAG_CANCEL_WHEN_BASE_CHANGES,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RequestFlags {}
+
+    /**
+     * Creates a new {@link Builder} for a {@link DeviceStateRequest}. Must be one of the supported
+     * states for the device which can be queried with a call to
+     * {@link DeviceStateManager#getSupportedStates()}.
+     *
+     * @param requestedState the device state being requested.
+     */
+    @NonNull
+    public static Builder newBuilder(int requestedState) {
+        return new Builder(requestedState);
+    }
+
+    /**
+     * Builder for {@link DeviceStateRequest}. An instance can be obtained through
+     * {@link #newBuilder(int)}.
+     */
+    public static final class Builder {
+        private final int mRequestedState;
+        private int mFlags;
+
+        private Builder(int requestedState) {
+            mRequestedState = requestedState;
+        }
+
+        /**
+         * Sets the flag bits provided within {@code flags} with all other bits remaining
+         * unchanged.
+         */
+        @NonNull
+        public Builder setFlags(@RequestFlags int flags) {
+            mFlags |= flags;
+            return this;
+        }
+
+        /**
+         * Returns a new {@link DeviceStateRequest} object whose state matches the state set on the
+         * builder.
+         */
+        @NonNull
+        public DeviceStateRequest build() {
+            return new DeviceStateRequest(mRequestedState, mFlags);
+        }
+    }
+
+    /** Callback to track the status of a request. */
+    public interface Callback {
+        /**
+         * Called to indicate the request has become active and the device state will match the
+         * requested state.
+         * <p>
+         * Guaranteed to be called after a call to
+         * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)} with a state
+         * matching the requested state.
+         */
+        default void onRequestActivated(@NonNull DeviceStateRequest request) {}
+
+        /**
+         * Called to indicate the request has been temporarily suspended.
+         * <p>
+         * Guaranteed to be called before a call to
+         * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
+         */
+        default void onRequestSuspended(@NonNull DeviceStateRequest request) {}
+
+        /**
+         * Called to indicate the request has been canceled. The request can be resubmitted with
+         * another call to {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
+         * DeviceStateRequest.Callback)}.
+         * <p>
+         * Guaranteed to be called before a call to
+         * {@link DeviceStateManager.DeviceStateListener#onDeviceStateChanged(int)}.
+         * <p>
+         * Note: A call to {@link #onRequestSuspended(DeviceStateRequest)} is not guaranteed to
+         * occur before this method.
+         */
+        default void onRequestCanceled(@NonNull DeviceStateRequest request) {}
+    }
+
+    private final int mRequestedState;
+    @RequestFlags
+    private final int mFlags;
+
+    private DeviceStateRequest(int requestedState, @RequestFlags int flags) {
+        mRequestedState = requestedState;
+        mFlags = flags;
+    }
+
+    public int getState() {
+        return mRequestedState;
+    }
+
+    @RequestFlags
+    public int getFlags() {
+        return mFlags;
+    }
+}
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index a157b33..323ad21 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -20,5 +20,45 @@
 
 /** @hide */
 interface IDeviceStateManager {
+    /**
+     * Registers a callback to receive notifications from the device state manager. Only one
+     * callback can be registered per-process.
+     * <p>
+     * As the callback mechanism is used to alert the caller of changes to request status a callback
+     * <b>MUST</b> be registered before calling {@link #requestState(IBinder, int, int)} or
+     * {@link #cancelRequest(IBinder)}. Otherwise an exception will be thrown.
+     *
+     * @throws SecurityException if a callback is already registered for the calling process.
+     */
     void registerCallback(in IDeviceStateManagerCallback callback);
+
+    /** Returns the array of supported device state identifiers. */
+    int[] getSupportedDeviceStates();
+
+    /**
+     * Requests that the device enter the supplied {@code state}. A callback <b>MUST</b> have been
+     * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
+     * call to this method.
+     *
+     * @param token the request token previously registered with
+     *        {@link #requestState(IBinder, int, int)}
+     *
+     * @throws IllegalStateException if a callback has not yet been registered for the calling
+     *         process.
+     * @throws IllegalStateException if the supplied {@code token} has already been registered.
+     * @throws IllegalArgumentException if the supplied {@code state} is not supported.
+     */
+    void requestState(IBinder token, int state, int flags);
+
+    /**
+     * Cancels a request previously submitted with a call to
+     * {@link #requestState(IBinder, int, int)}.
+     *
+     * @param token the request token previously registered with
+     *        {@link #requestState(IBinder, int, int)}
+     *
+     * @throws IllegalStateException if the supplied {@code token} has not been previously
+     *         requested.
+     */
+    void cancelRequest(IBinder token);
 }
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
index d1c5813..ee2a071 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl
@@ -18,5 +18,42 @@
 
 /** @hide */
 interface IDeviceStateManagerCallback {
+    /**
+     * Called in response to a change in device state. Guaranteed to be called once with the initial
+     * value on registration of the callback.
+     *
+     * @param deviceState the new state of the device.
+     */
     oneway void onDeviceStateChanged(int deviceState);
+
+    /**
+     * Called to notify the callback that a request has become active. Guaranteed to be called
+     * after a subsequent call to {@link #onDeviceStateChanged(int)} if the request becoming active
+     * resulted in a device state change.
+     *
+     * @param token the request token previously registered with
+     *        {@link IDeviceStateManager#requestState(IBinder, int, int)}
+     */
+    oneway void onRequestActive(IBinder token);
+
+    /**
+     * Called to notify the callback that a request has become suspended. Guaranteed to be called
+     * before a subsequent call to {@link #onDeviceStateChanged(int)} if the request becoming
+     * suspended resulted in a device state change.
+     *
+     * @param token the request token previously registered with
+     *        {@link IDeviceStateManager#requestState(IBinder, int, int)}
+     */
+    oneway void onRequestSuspended(IBinder token);
+
+    /**
+     * Called to notify the callback that a request has become canceled. No further callbacks will
+     * be triggered for this request. Guaranteed to be called before a subsequent call to
+     * {@link #onDeviceStateChanged(int)} if the request becoming canceled resulted in a device
+     * state change.
+     *
+     * @param token the request token previously registered with
+     *        {@link IDeviceStateManager#requestState(IBinder, int, int)}
+     */
+    oneway void onRequestCanceled(IBinder token);
 }
diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java
index e2d836c..a6c6b46 100644
--- a/core/java/android/hardware/display/BrightnessChangeEvent.java
+++ b/core/java/android/hardware/display/BrightnessChangeEvent.java
@@ -65,6 +65,23 @@
     /** If night mode color filter is active this will be the temperature in kelvin */
     public final int colorTemperature;
 
+    /** Whether the bright color reduction color transform is active */
+    public final boolean reduceBrightColors;
+
+    /** How strong the bright color reduction color transform is set (only applicable if active),
+     *  specified as an integer from 0 - 100, inclusive. This value (scaled to 0-1, inclusive) is
+     *  then used in Ynew = (a * scaledStrength^2 + b * scaledStrength + c) * Ycurrent, where a, b,
+     *  and c are coefficients provided in the bright color reduction coefficient matrix, and
+     *  Ycurrent is the current hardware brightness in nits.
+     */
+    public final int reduceBrightColorsStrength;
+
+    /** Applied offset for the bright color reduction color transform (only applicable if active).
+     *  The offset is computed by summing the coefficients a, b, and c, from the coefficient matrix
+     *  and multiplying by the current brightness.
+     */
+    public final float reduceBrightColorsOffset;
+
     /** Brightness level before slider adjustment */
     public final float lastBrightness;
 
@@ -105,8 +122,9 @@
     private BrightnessChangeEvent(float brightness, long timeStamp, String packageName,
             int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel,
             float powerBrightnessFactor, boolean nightMode, int colorTemperature,
-            float lastBrightness, boolean isDefaultBrightnessConfig, boolean isUserSetBrightness,
-            long[] colorValueBuckets, long colorSampleDuration) {
+            boolean reduceBrightColors, int reduceBrightColorsStrength,
+            float reduceBrightColorsOffset, float lastBrightness, boolean isDefaultBrightnessConfig,
+            boolean isUserSetBrightness, long[] colorValueBuckets, long colorSampleDuration) {
         this.brightness = brightness;
         this.timeStamp = timeStamp;
         this.packageName = packageName;
@@ -117,6 +135,9 @@
         this.powerBrightnessFactor = powerBrightnessFactor;
         this.nightMode = nightMode;
         this.colorTemperature = colorTemperature;
+        this.reduceBrightColors = reduceBrightColors;
+        this.reduceBrightColorsStrength = reduceBrightColorsStrength;
+        this.reduceBrightColorsOffset = reduceBrightColorsOffset;
         this.lastBrightness = lastBrightness;
         this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
         this.isUserSetBrightness = isUserSetBrightness;
@@ -136,6 +157,9 @@
         this.powerBrightnessFactor = other.powerBrightnessFactor;
         this.nightMode = other.nightMode;
         this.colorTemperature = other.colorTemperature;
+        this.reduceBrightColors = other.reduceBrightColors;
+        this.reduceBrightColorsStrength = other.reduceBrightColorsStrength;
+        this.reduceBrightColorsOffset = other.reduceBrightColorsOffset;
         this.lastBrightness = other.lastBrightness;
         this.isDefaultBrightnessConfig = other.isDefaultBrightnessConfig;
         this.isUserSetBrightness = other.isUserSetBrightness;
@@ -154,6 +178,9 @@
         powerBrightnessFactor = source.readFloat();
         nightMode = source.readBoolean();
         colorTemperature = source.readInt();
+        reduceBrightColors = source.readBoolean();
+        reduceBrightColorsStrength = source.readInt();
+        reduceBrightColorsOffset = source.readFloat();
         lastBrightness = source.readFloat();
         isDefaultBrightnessConfig = source.readBoolean();
         isUserSetBrightness = source.readBoolean();
@@ -188,6 +215,9 @@
         dest.writeFloat(powerBrightnessFactor);
         dest.writeBoolean(nightMode);
         dest.writeInt(colorTemperature);
+        dest.writeBoolean(reduceBrightColors);
+        dest.writeInt(reduceBrightColorsStrength);
+        dest.writeFloat(reduceBrightColorsOffset);
         dest.writeFloat(lastBrightness);
         dest.writeBoolean(isDefaultBrightnessConfig);
         dest.writeBoolean(isUserSetBrightness);
@@ -207,6 +237,9 @@
         private float mPowerBrightnessFactor;
         private boolean mNightMode;
         private int mColorTemperature;
+        private boolean mReduceBrightColors;
+        private int mReduceBrightColorsStrength;
+        private float mReduceBrightColorsOffset;
         private float mLastBrightness;
         private boolean mIsDefaultBrightnessConfig;
         private boolean mIsUserSetBrightness;
@@ -273,6 +306,24 @@
             return this;
         }
 
+        /** {@see BrightnessChangeEvent#reduceBrightColors} */
+        public Builder setReduceBrightColors(boolean reduceBrightColors) {
+            mReduceBrightColors = reduceBrightColors;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#reduceBrightColorsStrength} */
+        public Builder setReduceBrightColorsStrength(int strength) {
+            mReduceBrightColorsStrength = strength;
+            return this;
+        }
+
+        /** {@see BrightnessChangeEvent#reduceBrightColorsOffset} */
+        public Builder setReduceBrightColorsOffset(float offset) {
+            mReduceBrightColorsOffset = offset;
+            return this;
+        }
+
         /** {@see BrightnessChangeEvent#lastBrightness} */
         public Builder setLastBrightness(float lastBrightness) {
             mLastBrightness = lastBrightness;
@@ -304,7 +355,8 @@
         public BrightnessChangeEvent build() {
             return new BrightnessChangeEvent(mBrightness, mTimeStamp,
                     mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel,
-                    mPowerBrightnessFactor, mNightMode, mColorTemperature, mLastBrightness,
+                    mPowerBrightnessFactor, mNightMode, mColorTemperature, mReduceBrightColors,
+                    mReduceBrightColorsStrength, mReduceBrightColorsOffset, mLastBrightness,
                     mIsDefaultBrightnessConfig, mIsUserSetBrightness, mColorValueBuckets,
                     mColorSampleDuration);
         }
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index efeb89e..e247df3 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -438,6 +438,56 @@
     }
 
     /**
+     * Enables or disables reduce bright colors.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public boolean setReduceBrightColorsActivated(boolean activated) {
+        return mManager.setReduceBrightColorsActivated(activated);
+    }
+
+    /**
+     * Returns whether reduce bright colors is currently enabled.
+     *
+     * @hide
+     */
+    public boolean isReduceBrightColorsActivated() {
+        return mManager.isReduceBrightColorsActivated();
+    }
+
+    /**
+     * Set the strength level of bright color reduction to apply to the display.
+     *
+     * @param strength 0-100 (inclusive), where 100 is full strength
+     * @return whether the change was applied successfully
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+    public boolean setReduceBrightColorsStrength(@IntRange(from = 0, to = 100) int strength) {
+        return mManager.setReduceBrightColorsStrength(strength);
+    }
+
+    /**
+     * Gets the strength of the bright color reduction transform.
+     *
+     * @hide
+     */
+    public int getReduceBrightColorsStrength() {
+        return mManager.getReduceBrightColorsStrength();
+    }
+
+    /**
+     * Gets the brightness impact of the bright color reduction transform, as in the factor by which
+     * the current brightness (in nits) should be multiplied to obtain the brightness offset 'b'.
+     *
+     * @hide
+     */
+    public float getReduceBrightColorsOffsetFactor() {
+        return mManager.getReduceBrightColorsOffsetFactor();
+    }
+
+    /**
      * Returns {@code true} if Night Display is supported by the device.
      *
      * @hide
@@ -478,6 +528,15 @@
     }
 
     /**
+     * Returns {@code true} if reduce bright colors is supported by the device.
+     *
+     * @hide
+     */
+    public static boolean isReduceBrightColorsAvailable(Context context) {
+        return context.getResources().getBoolean(R.bool.config_reduceBrightColorsAvailable);
+    }
+
+    /**
      * Check if the color transforms are color accelerated. Some transforms are experimental only
      * on non-accelerated platforms due to the performance implications.
      *
@@ -678,6 +737,46 @@
             }
         }
 
+        boolean isReduceBrightColorsActivated() {
+            try {
+                return mCdm.isReduceBrightColorsActivated();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        boolean setReduceBrightColorsActivated(boolean activated) {
+            try {
+                return mCdm.setReduceBrightColorsActivated(activated);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        int getReduceBrightColorsStrength() {
+            try {
+                return mCdm.getReduceBrightColorsStrength();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        boolean setReduceBrightColorsStrength(int strength) {
+            try {
+                return mCdm.setReduceBrightColorsStrength(strength);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        float getReduceBrightColorsOffsetFactor() {
+            try {
+                return mCdm.getReduceBrightColorsOffsetFactor();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         int getColorMode() {
             try {
                 return mCdm.getColorMode();
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 5a03ade..48b05b7 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -47,20 +47,22 @@
      * begins adjusting the power state to match what was requested.
      * </p>
      *
+     * @param groupId The identifier for the display group being requested to change power state
      * @param request The requested power state.
-     * @param waitForNegativeProximity If true, issues a request to wait for
+     * @param waitForNegativeProximity If {@code true}, issues a request to wait for
      * negative proximity before turning the screen back on, assuming the screen
      * was turned off by the proximity sensor.
-     * @return True if display is ready, false if there are important changes that must
-     * be made asynchronously (such as turning the screen on), in which case the caller
-     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
-     * then try the request again later until the state converges.
+     * @return {@code true} if display group is ready, {@code false} if there are important
+     * changes that must be made asynchronously (such as turning the screen on), in which case
+     * the caller should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged}
+     * then try the request again later until the state converges. If the provided {@code groupId}
+     * cannot be found then {@code true} will be returned.
      */
-    public abstract boolean requestPowerState(DisplayPowerRequest request,
+    public abstract boolean requestPowerState(int groupId, DisplayPowerRequest request,
             boolean waitForNegativeProximity);
 
     /**
-     * Returns true if the proximity sensor screen-off function is available.
+     * Returns {@code true} if the proximity sensor screen-off function is available.
      */
     public abstract boolean isProximitySensorAvailable();
 
@@ -71,6 +73,22 @@
     public abstract int getDisplayGroupId(int displayId);
 
     /**
+     * Registers a display group listener which will be informed of the addition, removal, or change
+     * of display groups.
+     *
+     * @param listener The listener to register.
+     */
+    public abstract void registerDisplayGroupListener(DisplayGroupListener listener);
+
+    /**
+     * Unregisters a display group listener which will be informed of the addition, removal, or
+     * change of display groups.
+     *
+     * @param listener The listener to unregister.
+     */
+    public abstract void unregisterDisplayGroupListener(DisplayGroupListener listener);
+
+    /**
      * Screenshot for internal system-only use such as rotation, etc.  This method includes
      * secure layers and the result should never be exposed to non-system applications.
      * This method does not apply any rotation and provides the output in natural orientation.
@@ -465,4 +483,46 @@
     public interface DisplayTransactionListener {
         void onDisplayTransaction(Transaction t);
     }
+
+    /**
+     * Called when there are changes to {@link com.android.server.display.DisplayGroup
+     * DisplayGroups}.
+     */
+    public interface DisplayGroupListener {
+        /**
+         * A new display group with the provided {@code groupId} was added.
+         *
+         * <ol>
+         *     <li>The {@code groupId} is applied to all appropriate {@link Display displays}.
+         *     <li>This method is called.
+         *     <li>{@link android.hardware.display.DisplayManager.DisplayListener DisplayListeners}
+         *     are informed of any corresponding changes.
+         * </ol>
+         */
+        void onDisplayGroupAdded(int groupId);
+
+        /**
+         * The display group with the provided {@code groupId} was removed.
+         *
+         * <ol>
+         *     <li>All affected {@link Display displays} have their group IDs updated appropriately.
+         *     <li>{@link android.hardware.display.DisplayManager.DisplayListener DisplayListeners}
+         *     are informed of any corresponding changes.
+         *     <li>This method is called.
+         * </ol>
+         */
+        void onDisplayGroupRemoved(int groupId);
+
+        /**
+         * The display group with the provided {@code groupId} has changed.
+         *
+         * <ol>
+         *     <li>All affected {@link Display displays} have their group IDs updated appropriately.
+         *     <li>{@link android.hardware.display.DisplayManager.DisplayListener DisplayListeners}
+         *     are informed of any corresponding changes.
+         *     <li>This method is called.
+         * </ol>
+         */
+        void onDisplayGroupChanged(int groupId);
+    }
 }
diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl
index 7b1033d..ef4f5f9 100644
--- a/core/java/android/hardware/display/IColorDisplayManager.aidl
+++ b/core/java/android/hardware/display/IColorDisplayManager.aidl
@@ -45,4 +45,10 @@
 
     boolean isDisplayWhiteBalanceEnabled();
     boolean setDisplayWhiteBalanceEnabled(boolean enabled);
+
+    boolean isReduceBrightColorsActivated();
+    boolean setReduceBrightColorsActivated(boolean activated);
+    int getReduceBrightColorsStrength();
+    boolean setReduceBrightColorsStrength(int strength);
+    float getReduceBrightColorsOffsetFactor();
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/face/FaceAuthenticationFrame.java b/core/java/android/hardware/face/FaceAuthenticationFrame.java
new file mode 100644
index 0000000..f39d634
--- /dev/null
+++ b/core/java/android/hardware/face/FaceAuthenticationFrame.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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 android.hardware.face;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data model for a frame captured during face authentication.
+ *
+ * @hide
+ */
+public final class FaceAuthenticationFrame implements Parcelable {
+    @NonNull private final FaceDataFrame mData;
+
+    /**
+     * Data model for a frame captured during face authentication.
+     *
+     * @param data Information about the current frame.
+     */
+    public FaceAuthenticationFrame(@NonNull FaceDataFrame data) {
+        mData = data;
+    }
+
+    /**
+     * @return Information about the current frame.
+     */
+    @NonNull
+    public FaceDataFrame getData() {
+        return mData;
+    }
+
+    private FaceAuthenticationFrame(@NonNull Parcel source) {
+        mData = source.readParcelable(FaceDataFrame.class.getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mData, flags);
+    }
+
+    public static final Creator<FaceAuthenticationFrame> CREATOR =
+            new Creator<FaceAuthenticationFrame>() {
+
+        @Override
+        public FaceAuthenticationFrame createFromParcel(Parcel source) {
+            return new FaceAuthenticationFrame(source);
+        }
+
+        @Override
+        public FaceAuthenticationFrame[] newArray(int size) {
+            return new FaceAuthenticationFrame[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/face/FaceDataFrame.java b/core/java/android/hardware/face/FaceDataFrame.java
new file mode 100644
index 0000000..3a0e09b
--- /dev/null
+++ b/core/java/android/hardware/face/FaceDataFrame.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2021 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 android.hardware.face;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A container for data common to {@link FaceAuthenticationFrame} and {@link FaceEnrollFrame}.
+ *
+ * @hide
+ */
+public final class FaceDataFrame implements Parcelable {
+    private final int mAcquiredInfo;
+    private final int mVendorCode;
+    private final float mPan;
+    private final float mTilt;
+    private final float mDistance;
+    private final boolean mIsCancellable;
+
+    /**
+     * A container for data common to {@link FaceAuthenticationFrame} and {@link FaceEnrollFrame}.
+     *
+     * @param acquiredInfo An integer corresponding to a known acquired message.
+     * @param vendorCode An integer representing a custom vendor-specific message. Ignored unless
+     *  {@code acquiredInfo} is {@code FACE_ACQUIRED_VENDOR}.
+     * @param pan The horizontal pan of the detected face. Values in the range [-1, 1] indicate a
+     *  good capture.
+     * @param tilt The vertical tilt of the detected face. Values in the range [-1, 1] indicate a
+     *  good capture.
+     * @param distance The distance of the detected face from the device. Values in the range
+     *  [-1, 1] indicate a good capture.
+     * @param isCancellable Whether the ongoing face operation should be canceled.
+     */
+    public FaceDataFrame(
+            int acquiredInfo,
+            int vendorCode,
+            float pan,
+            float tilt,
+            float distance,
+            boolean isCancellable) {
+        mAcquiredInfo = acquiredInfo;
+        mVendorCode = vendorCode;
+        mPan = pan;
+        mTilt = tilt;
+        mDistance = distance;
+        mIsCancellable = isCancellable;
+    }
+
+    /**
+     * @return An integer corresponding to a known acquired message.
+     *
+     * @see android.hardware.biometrics.BiometricFaceConstants
+     */
+    public int getAcquiredInfo() {
+        return mAcquiredInfo;
+    }
+
+    /**
+     * @return An integer representing a custom vendor-specific message. Ignored unless
+     * {@code acquiredInfo} is {@link
+     * android.hardware.biometrics.BiometricFaceConstants#FACE_ACQUIRED_VENDOR}.
+     *
+     * @see android.hardware.biometrics.BiometricFaceConstants
+     */
+    public int getVendorCode() {
+        return mVendorCode;
+    }
+
+    /**
+     * @return The horizontal pan of the detected face. Values in the range [-1, 1] indicate a good
+     * capture.
+     */
+    public float getPan() {
+        return mPan;
+    }
+
+    /**
+     * @return The vertical tilt of the detected face. Values in the range [-1, 1] indicate a good
+     * capture.
+     */
+    public float getTilt() {
+        return mTilt;
+    }
+
+    /**
+     * @return The distance of the detected face from the device. Values in the range [-1, 1]
+     * indicate a good capture.
+     */
+    public float getDistance() {
+        return mDistance;
+    }
+
+    /**
+     * @return Whether the ongoing face operation should be canceled.
+     */
+    public boolean isCancellable() {
+        return mIsCancellable;
+    }
+
+    private FaceDataFrame(@NonNull Parcel source) {
+        mAcquiredInfo = source.readInt();
+        mVendorCode = source.readInt();
+        mPan = source.readFloat();
+        mTilt = source.readFloat();
+        mDistance = source.readFloat();
+        mIsCancellable = source.readBoolean();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAcquiredInfo);
+        dest.writeInt(mVendorCode);
+        dest.writeFloat(mPan);
+        dest.writeFloat(mTilt);
+        dest.writeFloat(mDistance);
+        dest.writeBoolean(mIsCancellable);
+    }
+
+    public static final Creator<FaceDataFrame> CREATOR = new Creator<FaceDataFrame>() {
+        @Override
+        public FaceDataFrame createFromParcel(Parcel source) {
+            return new FaceDataFrame(source);
+        }
+
+        @Override
+        public FaceDataFrame[] newArray(int size) {
+            return new FaceDataFrame[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/face/FaceEnrollCell.java b/core/java/android/hardware/face/FaceEnrollCell.java
new file mode 100644
index 0000000..8415419
--- /dev/null
+++ b/core/java/android/hardware/face/FaceEnrollCell.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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 android.hardware.face;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A matrix cell, corresponding to a desired face image, that may be captured during enrollment.
+ *
+ * @hide
+ */
+public final class FaceEnrollCell implements Parcelable {
+    private final int mX;
+    private final int mY;
+    private final int mZ;
+
+    /**
+     * A matrix cell, corresponding to a desired face image, that may be captured during enrollment.
+     *
+     * @param x The horizontal coordinate of this cell.
+     * @param y The vertical coordinate of this cell.
+     * @param z The depth coordinate of this cell.
+     */
+    public FaceEnrollCell(int x, int y, int z) {
+        mX = x;
+        mY = y;
+        mZ = z;
+    }
+
+    /**
+     * @return The horizontal coordinate of this cell.
+     */
+    public int getX() {
+        return mX;
+    }
+
+    /**
+     * @return The vertical coordinate of this cell.
+     */
+    public int getY() {
+        return mY;
+    }
+
+    /**
+     * @return The depth coordinate of this cell.
+     */
+    public int getZ() {
+        return mZ;
+    }
+
+    private FaceEnrollCell(@NonNull Parcel source) {
+        mX = source.readInt();
+        mY = source.readInt();
+        mZ = source.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mX);
+        dest.writeInt(mY);
+        dest.writeInt(mZ);
+    }
+
+    public static final Creator<FaceEnrollCell> CREATOR = new Creator<FaceEnrollCell>() {
+        @Override
+        public FaceEnrollCell createFromParcel(Parcel source) {
+            return new FaceEnrollCell(source);
+        }
+
+        @Override
+        public FaceEnrollCell[] newArray(int size) {
+            return new FaceEnrollCell[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/face/FaceEnrollFrame.java b/core/java/android/hardware/face/FaceEnrollFrame.java
new file mode 100644
index 0000000..551139d
--- /dev/null
+++ b/core/java/android/hardware/face/FaceEnrollFrame.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 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 android.hardware.face;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data model for a frame captured during face enrollment.
+ *
+ * @hide
+ */
+public final class FaceEnrollFrame implements Parcelable {
+    @Nullable private final FaceEnrollCell mCell;
+    @FaceEnrollStage private final int mStage;
+    @NonNull private final FaceDataFrame mData;
+
+    /**
+     * Data model for a frame captured during face enrollment.
+     *
+     * @param cell The cell captured during this frame of enrollment, if any.
+     * @param stage An integer representing the current stage of enrollment.
+     * @param data Information about the current frame.
+     */
+    public FaceEnrollFrame(
+            @Nullable FaceEnrollCell cell,
+            @FaceEnrollStage int stage,
+            @NonNull FaceDataFrame data) {
+        mCell = cell;
+        mStage = stage;
+        mData = data;
+    }
+
+    /**
+     * @return The cell captured during this frame of enrollment, if any.
+     */
+    @Nullable
+    public FaceEnrollCell getCell() {
+        return mCell;
+    }
+
+    /**
+     * @return An integer representing the current stage of enrollment.
+     */
+    @FaceEnrollStage
+    public int getStage() {
+        return mStage;
+    }
+
+    /**
+     * @return Information about the current frame.
+     */
+    @NonNull
+    public FaceDataFrame getData() {
+        return mData;
+    }
+
+    private FaceEnrollFrame(@NonNull Parcel source) {
+        mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader());
+        mStage = source.readInt();
+        mData = source.readParcelable(FaceDataFrame.class.getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mCell, flags);
+        dest.writeInt(mStage);
+        dest.writeParcelable(mData, flags);
+    }
+
+    public static final Creator<FaceEnrollFrame> CREATOR = new Creator<FaceEnrollFrame>() {
+        @Override
+        public FaceEnrollFrame createFromParcel(Parcel source) {
+            return new FaceEnrollFrame(source);
+        }
+
+        @Override
+        public FaceEnrollFrame[] newArray(int size) {
+            return new FaceEnrollFrame[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/face/FaceEnrollStage.java b/core/java/android/hardware/face/FaceEnrollStage.java
new file mode 100644
index 0000000..03dba55
--- /dev/null
+++ b/core/java/android/hardware/face/FaceEnrollStage.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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 android.hardware.face;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A stage that may occur during face enrollment.
+ *
+ * @hide
+ */
+@Retention(RetentionPolicy.SOURCE)
+@IntDef({
+    FaceEnrollStage.FIRST_FRAME_RECEIVED,
+    FaceEnrollStage.WAITING_FOR_CENTERING,
+    FaceEnrollStage.HOLD_STILL_IN_CENTER,
+    FaceEnrollStage.ENROLLING_MOVEMENT_1,
+    FaceEnrollStage.ENROLLING_MOVEMENT_2,
+    FaceEnrollStage.ENROLLMENT_FINISHED
+})
+public @interface FaceEnrollStage {
+    /**
+     * Enrollment has just begun. No action is needed from the user yet.
+     */
+    int FIRST_FRAME_RECEIVED = 0;
+
+    /**
+     * The user must center their face in the frame.
+     */
+    int WAITING_FOR_CENTERING = 1;
+
+    /**
+     * The user must keep their face centered in the frame.
+     */
+    int HOLD_STILL_IN_CENTER = 2;
+
+    /**
+     * The user must follow a first set of movement instructions.
+     */
+    int ENROLLING_MOVEMENT_1 = 3;
+
+    /**
+     * The user must follow a second set of movement instructions.
+     */
+    int ENROLLING_MOVEMENT_2 = 4;
+
+    /**
+     * Enrollment has completed. No more action is needed from the user.
+     */
+    int ENROLLMENT_FINISHED = 5;
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 75893d9..588bc01 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -301,7 +301,8 @@
     @RequiresPermission(MANAGE_BIOMETRIC)
     public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
             EnrollmentCallback callback, int[] disabledFeatures) {
-        enroll(userId, hardwareAuthToken, cancel, callback, disabledFeatures, null /* surface */);
+        enroll(userId, hardwareAuthToken, cancel, callback, disabledFeatures, null /* surface */,
+                false /* debugConsent */);
     }
 
     /**
@@ -313,18 +314,20 @@
      * which point the object is no longer valid. The operation can be canceled by using the
      * provided cancel object.
      *
-     * @param token    a unique token provided by a recent creation or verification of device
-     *                 credentials (e.g. pin, pattern or password).
-     * @param cancel   an object that can be used to cancel enrollment
-     * @param userId   the user to whom this face will belong to
-     * @param callback an object to receive enrollment events
-     * @param surface  optional camera preview surface for a single-camera device. Must be null if
-     *                 not used.
+     * @param hardwareAuthToken a unique token provided by a recent creation or
+     *                          verification of device credentials (e.g. pin, pattern or password).
+     * @param cancel            an object that can be used to cancel enrollment
+     * @param userId            the user to whom this face will belong to
+     * @param callback          an object to receive enrollment events
+     * @param surface           optional camera preview surface for a single-camera device.
+     *                          Must be null if not used.
+     * @param debugConsent      a feature flag that the user has consented to debug.
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
     public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
-            EnrollmentCallback callback, int[] disabledFeatures, @Nullable Surface surface) {
+            EnrollmentCallback callback, int[] disabledFeatures, @Nullable Surface surface,
+            boolean debugConsent) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an enrollment callback");
         }
@@ -343,7 +346,7 @@
                 mEnrollmentCallback = callback;
                 Trace.beginSection("FaceManager#enroll");
                 mService.enroll(userId, mToken, hardwareAuthToken, mServiceReceiver,
-                        mContext.getOpPackageName(), disabledFeatures, surface);
+                        mContext.getOpPackageName(), disabledFeatures, surface, debugConsent);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 1b188e8..a3e7e2d 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -74,7 +74,7 @@
 
     // Start face enrollment
     void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
-            String opPackageName, in int [] disabledFeatures, in Surface surface);
+            String opPackageName, in int [] disabledFeatures, in Surface surface, boolean debugConsent);
 
     // Start remote face enrollment
     void enrollRemotely(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index d932865..188a2a4 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -93,17 +93,26 @@
     private static final int MSG_UDFPS_POINTER_UP = 109;
 
     /**
-     * Request authentication with any single sensor.
      * @hide
      */
-    public static final int SENSOR_ID_ANY = -1;
+    public static final int ENROLL_FIND_SENSOR = 1;
+    /**
+     * @hide
+     */
+    public static final int ENROLL_ENROLL = 2;
 
     /**
      * @hide
      */
-    @IntDef({SENSOR_ID_ANY})
+    @IntDef({ENROLL_FIND_SENSOR, ENROLL_ENROLL})
     @Retention(RetentionPolicy.SOURCE)
-    public @interface SensorId {}
+    public @interface EnrollReason {}
+
+    /**
+     * Request authentication with any single sensor.
+     * @hide
+     */
+    public static final int SENSOR_ID_ANY = -1;
 
     private IFingerprintService mService;
     private Context mContext;
@@ -508,8 +517,8 @@
      */
     @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
-            @NonNull AuthenticationCallback callback, Handler handler, @SensorId int sensorId,
-            int userId) {
+            @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId) {
+
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
@@ -590,7 +599,7 @@
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId,
-            EnrollmentCallback callback, boolean shouldLogMetrics) {
+            EnrollmentCallback callback, @EnrollReason int enrollReason) {
         if (userId == UserHandle.USER_CURRENT) {
             userId = getCurrentUserId();
         }
@@ -611,7 +620,7 @@
             try {
                 mEnrollmentCallback = callback;
                 mService.enroll(mToken, hardwareAuthToken, userId, mServiceReceiver,
-                        mContext.getOpPackageName(), shouldLogMetrics);
+                        mContext.getOpPackageName(), enrollReason);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception in enroll: ", e);
                 // Though this may not be a hardware issue, it will cause apps to give up or try
@@ -653,15 +662,12 @@
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void generateChallenge(int userId, GenerateChallengeCallback callback) {
-        final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties =
-                getSensorPropertiesInternal();
-        if (fingerprintSensorProperties.isEmpty()) {
+        final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor();
+        if (sensorProps == null) {
             Slog.e(TAG, "No sensors");
             return;
         }
-
-        final int sensorId = fingerprintSensorProperties.get(0).sensorId;
-        generateChallenge(sensorId, userId, callback);
+        generateChallenge(sensorProps.sensorId, userId, callback);
     }
 
     /**
@@ -681,18 +687,18 @@
      */
     @RequiresPermission(MANAGE_FINGERPRINT)
     public void revokeChallenge(int userId, long challenge) {
-        if (mService != null) try {
-            final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties =
-                    getSensorPropertiesInternal();
-            if (fingerprintSensorProperties.isEmpty()) {
-                Slog.e(TAG, "No sensors");
-                return;
+        if (mService != null) {
+            try {
+                final FingerprintSensorPropertiesInternal sensorProps = getFirstFingerprintSensor();
+                if (sensorProps == null) {
+                    Slog.e(TAG, "No sensors");
+                    return;
+                }
+                mService.revokeChallenge(mToken, sensorProps.sensorId, userId,
+                        mContext.getOpPackageName(), challenge);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
             }
-            final int sensorId = fingerprintSensorProperties.get(0).sensorId;
-            mService.revokeChallenge(mToken, sensorId, userId, mContext.getOpPackageName(),
-                    challenge);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1161,6 +1167,12 @@
         }
     }
 
+    @Nullable
+    private FingerprintSensorPropertiesInternal getFirstFingerprintSensor() {
+        final List<FingerprintSensorPropertiesInternal> allSensors = getSensorPropertiesInternal();
+        return allSensors.isEmpty() ? null : allSensors.get(0);
+    }
+
     private void cancelEnrollment() {
         if (mService != null) try {
             mService.cancelEnrollment(mToken);
diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
index 2650dc5..663a704 100644
--- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
+++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java
@@ -19,6 +19,8 @@
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
 
+import android.annotation.NonNull;
+import android.content.Context;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.os.Parcel;
@@ -81,10 +83,37 @@
             @SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
             @FingerprintSensorProperties.SensorType int sensorType,
             boolean resetLockoutRequiresHardwareAuthToken) {
-        // TODO: Value should be provided from the HAL
+        // TODO(b/179175438): Value should be provided from the HAL
         this(sensorId, strength, maxEnrollmentsPerUser, sensorType,
-                resetLockoutRequiresHardwareAuthToken, 540 /* sensorLocationX */,
-                1769 /* sensorLocationY */, 130 /* sensorRadius */);
+                resetLockoutRequiresHardwareAuthToken, 0 /* sensorLocationX */,
+                0 /* sensorLocationY */, 0 /* sensorRadius */);
+    }
+
+    /**
+     * Initializes SensorProperties with specified values and values obtained from resources using
+     * context.
+     */
+    // TODO(b/179175438): Remove this constructor once all HALs move to AIDL.
+    public FingerprintSensorPropertiesInternal(@NonNull Context context, int sensorId,
+            @SensorProperties.Strength int strength, int maxEnrollmentsPerUser,
+            @FingerprintSensorProperties.SensorType int sensorType,
+            boolean resetLockoutRequiresHardwareAuthToken) {
+        super(sensorId, strength, maxEnrollmentsPerUser);
+        this.sensorType = sensorType;
+        this.resetLockoutRequiresHardwareAuthToken = resetLockoutRequiresHardwareAuthToken;
+
+        int[] props = context.getResources().getIntArray(
+                com.android.internal.R.array.config_udfps_sensor_props);
+        if (props != null && props.length == 3) {
+            this.sensorLocationX = props[0];
+            this.sensorLocationY = props[1];
+            this.sensorRadius = props[2];
+        } else {
+            // Fake coordinates that could be used for the fake UDFPS mode.
+            this.sensorLocationX = 540;
+            this.sensorLocationY = 1636;
+            this.sensorRadius = 130;
+        }
     }
 
     protected FingerprintSensorPropertiesInternal(Parcel in) {
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 3657a83..8888247 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -78,7 +78,7 @@
 
     // Start fingerprint enrollment
     void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
-            String opPackageName, boolean shouldLogMetrics);
+            String opPackageName, int enrollReason);
 
     // Cancel enrollment in progress
     void cancelEnrollment(IBinder token);
diff --git a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
index c3b6c05..81c7d89 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl
@@ -21,10 +21,11 @@
  */
 oneway interface IUdfpsOverlayController {
     const int REASON_UNKNOWN = 0;
-    const int REASON_ENROLL = 1;
-    const int REASON_AUTH_BP = 2; // BiometricPrompt
-    const int REASON_AUTH_FPM_KEYGUARD = 3; // FingerprintManager usage from Keyguard
-    const int REASON_AUTH_FPM_OTHER = 4; // Other FingerprintManager usage
+    const int REASON_ENROLL_FIND_SENSOR = 1;
+    const int REASON_ENROLL_ENROLLING = 2;
+    const int REASON_AUTH_BP = 3; // BiometricPrompt
+    const int REASON_AUTH_FPM_KEYGUARD = 4; // FingerprintManager usage from Keyguard
+    const int REASON_AUTH_FPM_OTHER = 5; // Other FingerprintManager usage
 
     // Shows the overlay.
     void showUdfpsOverlay(int sensorId, int reason);
@@ -32,6 +33,12 @@
     // Hides the overlay.
     void hideUdfpsOverlay(int sensorId);
 
+    // Notifies of enrollment progress changes.
+    void onEnrollmentProgress(int sensorId, int remaining);
+
+    // Notifies when a non-terminal error occurs (e.g. user moved their finger too fast).
+    void onEnrollmentHelp(int sensorId);
+
     // Shows debug messages on the UDFPS overlay.
     void setDebugMessage(int sensorId, String message);
 }
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 4ec3f92..b90c728 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -503,6 +503,29 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface TvWakeOnOneTouchPlay {}
 
+    // -- Whether TV should send &lt;Standby&gt; on sleep.
+    /**
+     * Sending &lt;Standby&gt; on sleep.
+     *
+     * @hide
+     */
+    public static final int TV_SEND_STANDBY_ON_SLEEP_ENABLED = 1;
+    /**
+     * Not sending &lt;Standby&gt; on sleep.
+     *
+     * @hide
+     */
+    public static final int TV_SEND_STANDBY_ON_SLEEP_DISABLED = 0;
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "TV_SEND_STANDBY_ON_SLEEP_" }, value = {
+            TV_SEND_STANDBY_ON_SLEEP_ENABLED,
+            TV_SEND_STANDBY_ON_SLEEP_DISABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TvSendStandbyOnSleep {}
+
     // -- The RC profile of a TV panel.
     /**
      * RC profile none.
@@ -747,6 +770,14 @@
     public static final String CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY =
             "tv_wake_on_one_touch_play";
     /**
+     * Name of a setting deciding whether the device will also turn off other CEC devices
+     * when it goes to standby mode.
+     *
+     * @hide
+     */
+    public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP =
+            "tv_send_standby_on_sleep";
+    /**
      * Name of a setting representing the RC profile of a TV panel.
      *
      * @hide
@@ -805,12 +836,13 @@
         CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
         CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
         CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
+        CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
         CEC_SETTING_NAME_RC_PROFILE_TV,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
-        CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU
+        CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
     })
     public @interface CecSettingName {}
 
@@ -2158,4 +2190,48 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set the current status of TV send &lt;Standby&gt; on Sleep.
+     *
+     * <p>Sets whether the device will also turn off other CEC devices
+     * when it goes to standby mode.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void setTvSendStandbyOnSleep(@NonNull @TvSendStandbyOnSleep int value) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            mService.setCecSettingIntValue(CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP, value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the current status of TV send &lt;Standby&gt; on Sleep.
+     *
+     * <p>Reflects whether the device will also turn off other CEC devices
+     * when it goes to standby mode.
+     *
+     * @hide
+     */
+    @NonNull
+    @TvSendStandbyOnSleep
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public int getTvSendStandbyOnSleep() {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            return mService.getCecSettingIntValue(CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index b39df4d..c69c47f 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -93,6 +93,10 @@
     int[] getVibratorIds(int deviceId);
     boolean isVibrating(int deviceId);
 
+    // Input device battery query.
+    int getBatteryStatus(int deviceId);
+    int getBatteryCapacity(int deviceId);
+
     void setPointerIconType(int typeId);
     void setCustomPointerIcon(in PointerIcon icon);
 
diff --git a/core/java/android/hardware/input/InputDeviceBattery.java b/core/java/android/hardware/input/InputDeviceBattery.java
new file mode 100644
index 0000000..0fe124e
--- /dev/null
+++ b/core/java/android/hardware/input/InputDeviceBattery.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 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 android.hardware.input;
+
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.IInputConstants.INVALID_BATTERY_CAPACITY;
+
+import android.hardware.Battery;
+
+/**
+ * Battery implementation for input devices.
+ *
+ * @hide
+ */
+public final class InputDeviceBattery extends Battery {
+    private static final float NULL_BATTERY_CAPACITY = -1.0f;
+
+    private final InputManager mInputManager;
+    private final int mDeviceId;
+    private final boolean mHasBattery;
+
+    InputDeviceBattery(InputManager inputManager, int deviceId, boolean hasBattery) {
+        mInputManager = inputManager;
+        mDeviceId = deviceId;
+        mHasBattery = hasBattery;
+    }
+
+    @Override
+    public boolean hasBattery() {
+        return mHasBattery;
+    }
+
+    @Override
+    public int getStatus() {
+        if (!mHasBattery) {
+            return BATTERY_STATUS_UNKNOWN;
+        }
+        return mInputManager.getBatteryStatus(mDeviceId);
+    }
+
+    @Override
+    public float getCapacity() {
+        if (mHasBattery) {
+            int capacity = mInputManager.getBatteryCapacity(mDeviceId);
+            if (capacity != INVALID_BATTERY_CAPACITY) {
+                return (float) capacity / 100.0f;
+            }
+        }
+        return NULL_BATTERY_CAPACITY;
+    }
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6ab1106..185c59d 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -25,6 +25,7 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -909,18 +910,17 @@
     }
 
     /**
-     * Returns the maximum allowed obscuring opacity by UID to propagate touches.
+     * Returns the maximum allowed obscuring opacity per UID to propagate touches.
      *
-     * For certain window types (eg. SAWs), the decision of honoring {@link LayoutParams
-     * #FLAG_NOT_TOUCHABLE} or not depends on the combined obscuring opacity of the windows
-     * above the touch-consuming window.
+     * <p>For certain window types (eg. {@link LayoutParams#TYPE_APPLICATION_OVERLAY}), the decision
+     * of honoring {@link LayoutParams#FLAG_NOT_TOUCHABLE} or not depends on the combined obscuring
+     * opacity of the windows above the touch-consuming window, per UID. Check documentation of
+     * {@link LayoutParams#FLAG_NOT_TOUCHABLE} for more details.
      *
-     * @see #setMaximumObscuringOpacityForTouch(Context, float)
-     *
-     * @hide
+     * @see LayoutParams#FLAG_NOT_TOUCHABLE
      */
-    @TestApi
-    public float getMaximumObscuringOpacityForTouch(@NonNull Context context) {
+    public float getMaximumObscuringOpacityForTouch() {
+        Context context = ActivityThread.currentApplication();
         return Settings.Global.getFloat(context.getContentResolver(),
                 Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH,
                 DEFAULT_MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH);
@@ -946,17 +946,18 @@
      *
      * This value should be between 0 (inclusive) and 1 (inclusive).
      *
-     * @see #getMaximumObscuringOpacityForTouch(Context)
+     * @see #getMaximumObscuringOpacityForTouch()
      *
      * @hide
      */
     @TestApi
     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
-    public void setMaximumObscuringOpacityForTouch(@NonNull Context context, float opacity) {
+    public void setMaximumObscuringOpacityForTouch(float opacity) {
         if (opacity < 0 || opacity > 1) {
             throw new IllegalArgumentException(
                     "Maximum obscuring opacity for touch should be >= 0 and <= 1");
         }
+        Context context = ActivityThread.currentApplication();
         Settings.Global.putFloat(context.getContentResolver(),
                 Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH, opacity);
     }
@@ -1244,6 +1245,32 @@
     }
 
     /**
+     * Get the battery status of the input device
+     * @param deviceId The input device ID
+     * @hide
+     */
+    public int getBatteryStatus(int deviceId) {
+        try {
+            return mIm.getBatteryStatus(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the remaining battery capacity of the input device
+     * @param deviceId The input device ID
+     * @hide
+     */
+    public int getBatteryCapacity(int deviceId) {
+        try {
+            return mIm.getBatteryCapacity(deviceId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Add a runtime association between the input port and the display port. This overrides any
      * static associations.
      * @param inputPort The port of the input device.
@@ -1470,6 +1497,15 @@
     }
 
     /**
+     * Gets a battery object associated with an input device, assuming it has one.
+     * @return The battery, never null.
+     * @hide
+     */
+    public InputDeviceBattery getInputDeviceBattery(int deviceId, boolean hasBattery) {
+        return new InputDeviceBattery(this, deviceId, hasBattery);
+    }
+
+    /**
      * Listens for changes in input devices.
      */
     public interface InputDeviceListener {
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
index 20fa753..b31b85f 100644
--- a/core/java/android/hardware/location/ContextHubClientCallback.java
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
 import java.util.concurrent.Executor;
@@ -101,4 +102,34 @@
      * @param nanoAppId the ID of the nanoapp that had been disabled
      */
     public void onNanoAppDisabled(ContextHubClient client, long nanoAppId) {}
+
+    /**
+     * Callback invoked when a {@link ContextHubClient}'s authorization to communicate with a
+     * nanoapp changes. This typically happens as a result of the app that created the
+     * {@link ContextHubClient} gaining or losing the permissions required to communicate with a
+     * nanoapp.
+     *
+     * An example of the connection callbacks looks like:
+     * 1) {@link ContextHubClient} sends message to nanoapp and holds required permissions
+     * 2) {@link ContextHubClient} loses required permissions
+     * 3) Callback invoked with the nanoapp ID and
+     *    {@link ContextHubManager#AUTHORIZATION_DENIED_GRACE_PERIOD}
+     * 4) {@link ContextHubClient} performs any cleanup required with the nanoapp
+     * 5) Callback invoked with the nanoapp ID and {@link ContextHubManager#AUTHORIZATION_DENIED}.
+     *    At this point, any further attempts of communication between the nanoapp and the
+     *    {@link ContextHubClient} will be dropped by the contexthub along with
+     *    {@link ContextHubManager#AUTHORIZATION_DENIED} being sent. The {@link ContextHubClient}
+     *    should assume no communciation can happen again until
+     *    {@link ContextHubManager#AUTHORIZATION_GRANTED} is received.
+     *
+     * @param client the client that is associated with this callback
+     * @param nanoAppId the ID of the nanoapp associated with the new
+     * authorization state
+     * @param authorization the authorization state denoting the ability of the
+     * client to communicate with the nanoapp
+     */
+    public void onClientAuthorizationChanged(
+            @NonNull ContextHubClient client,
+            long nanoAppId,
+            @ContextHubManager.AuthorizationState int authorization) {}
 }
diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java
index 917f62b..3e8f421 100644
--- a/core/java/android/hardware/location/ContextHubIntentEvent.java
+++ b/core/java/android/hardware/location/ContextHubIntentEvent.java
@@ -43,39 +43,45 @@
 
     private final int mNanoAppAbortCode;
 
+    private final int mClientAuthorizationState;
+
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
-            long nanoAppId, NanoAppMessage nanoAppMessage, int nanoAppAbortCode) {
+            long nanoAppId, NanoAppMessage nanoAppMessage, int nanoAppAbortCode,
+            @ContextHubManager.AuthorizationState int clientAuthorizationState) {
         mContextHubInfo = contextHubInfo;
         mEventType = eventType;
         mNanoAppId = nanoAppId;
         mNanoAppMessage = nanoAppMessage;
         mNanoAppAbortCode = nanoAppAbortCode;
+        mClientAuthorizationState = clientAuthorizationState;
     }
 
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType) {
         this(contextHubInfo, eventType, -1 /* nanoAppId */, null /* nanoAppMessage */,
-                -1 /* nanoAppAbortCode */);
+                -1 /* nanoAppAbortCode */, 0 /* clientAuthorizationState */);
     }
 
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
             long nanoAppId) {
         this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */,
-                -1 /* nanoAppAbortCode */);
+                -1 /* nanoAppAbortCode */, 0 /* clientAuthorizationState */);
     }
 
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
             long nanoAppId, @NonNull NanoAppMessage nanoAppMessage) {
-        this(contextHubInfo, eventType, nanoAppId, nanoAppMessage, -1 /* nanoAppAbortCode */);
+        this(contextHubInfo, eventType, nanoAppId, nanoAppMessage, -1 /* nanoAppAbortCode */,
+                0 /* clientAuthorizationState */);
     }
 
     private ContextHubIntentEvent(
             @NonNull ContextHubInfo contextHubInfo, @ContextHubManager.Event int eventType,
             long nanoAppId, int nanoAppAbortCode) {
-        this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, nanoAppAbortCode);
+        this(contextHubInfo, eventType, nanoAppId, null /* nanoAppMessage */, nanoAppAbortCode,
+                0 /* clientAuthorizationState */);
     }
 
     /**
@@ -105,7 +111,8 @@
             case ContextHubManager.EVENT_NANOAPP_ENABLED:
             case ContextHubManager.EVENT_NANOAPP_DISABLED:
             case ContextHubManager.EVENT_NANOAPP_ABORTED:
-            case ContextHubManager.EVENT_NANOAPP_MESSAGE: // fall through
+            case ContextHubManager.EVENT_NANOAPP_MESSAGE:
+            case ContextHubManager.EVENT_CLIENT_AUTHORIZATION: // fall through
                 long nanoAppId = getLongExtraOrThrow(intent, ContextHubManager.EXTRA_NANOAPP_ID);
                 if (eventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
                     hasExtraOrThrow(intent, ContextHubManager.EXTRA_MESSAGE);
@@ -120,6 +127,11 @@
                     int nanoAppAbortCode = getIntExtraOrThrow(
                             intent, ContextHubManager.EXTRA_NANOAPP_ABORT_CODE);
                     event = new ContextHubIntentEvent(info, eventType, nanoAppId, nanoAppAbortCode);
+                } else if (eventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+                    int authState = getIntExtraOrThrow(
+                            intent, ContextHubManager.EXTRA_CLIENT_AUTHORIZATION_STATE);
+                    event = new ContextHubIntentEvent(info, eventType, nanoAppId,
+                            null /* nanoAppMessage */, -1 /* nanoAppAbortCode */, authState);
                 } else {
                     event = new ContextHubIntentEvent(info, eventType, nanoAppId);
                 }
@@ -192,6 +204,21 @@
         return mNanoAppMessage;
     }
 
+    /**
+     * @return the client authorization state
+     *
+     * @throws UnsupportedOperationException if this was not a client authorization state event
+     */
+    @ContextHubManager.AuthorizationState
+    public int getClientAuthorizationState() {
+        if (mEventType != ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+            throw new UnsupportedOperationException(
+                    "Cannot invoke getClientAuthorizationState() on non-authorization event: "
+                    + mEventType);
+        }
+        return mClientAuthorizationState;
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -207,6 +234,9 @@
         if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
             out += ", nanoAppMessage = " + mNanoAppMessage;
         }
+        if (mEventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+            out += ", clientAuthState = " + mClientAuthorizationState;
+        }
 
         return out + "]";
     }
@@ -233,6 +263,9 @@
                     if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
                         isEqual &= other.getNanoAppMessage().equals(mNanoAppMessage);
                     }
+                    if (mEventType == ContextHubManager.EVENT_CLIENT_AUTHORIZATION) {
+                        isEqual &= other.getClientAuthorizationState() == mClientAuthorizationState;
+                    }
                 } catch (UnsupportedOperationException e) {
                     isEqual = false;
                 }
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index d444807..ebb3021 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -59,6 +59,12 @@
     private static final String TAG = "ContextHubManager";
 
     /**
+     * An extra of type int describing the client's authorization state.
+     */
+    public static final String EXTRA_CLIENT_AUTHORIZATION_STATE =
+            "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE";
+
+    /**
      * An extra of type {@link ContextHubInfo} describing the source of the event.
      */
     public static final String EXTRA_CONTEXT_HUB_INFO =
@@ -86,6 +92,42 @@
     public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE";
 
     /**
+     * Constants describing if a {@link ContextHubClient} and a {@link NanoApp} are authorized to
+     * communicate.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "AUTHORIZATION_" }, value = {
+        AUTHORIZATION_DENIED,
+        AUTHORIZATION_DENIED_GRACE_PERIOD,
+        AUTHORIZATION_GRANTED,
+    })
+    public @interface AuthorizationState { }
+
+    /**
+     * Indicates that the {@link ContextHubClient} can no longer communicate with a nanoapp. If the
+     * {@link ContextHubClient} attempts to send messages to the nanoapp, it will continue to
+     * receive this authorization state if the connection is still closed.
+     */
+    public static final int AUTHORIZATION_DENIED = 0;
+
+    /**
+     * Indicates the {@link ContextHubClient} will soon lose its authorization to communicate with a
+     * nanoapp. The {@link ContextHubClient} must perform any cleanup with the nanoapp as soon as
+     * possible.
+     *
+     * Note that the time between this state event and {@link AUTHORIZATION_DENIED} must be enough
+     * for the {@link ContextHubClient} to send at least one message to the nanoapp.
+     */
+    public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1;
+
+    /**
+     * The {@link ContextHubClient} is authorized to communicate with the nanoapp.
+     */
+    public static final int AUTHORIZATION_GRANTED = 2;
+
+    /**
      * Constants describing the type of events from a Context Hub.
      * {@hide}
      */
@@ -98,6 +140,7 @@
         EVENT_NANOAPP_ABORTED,
         EVENT_NANOAPP_MESSAGE,
         EVENT_HUB_RESET,
+        EVENT_CLIENT_AUTHORIZATION,
     })
     public @interface Event { }
 
@@ -138,6 +181,14 @@
      */
     public static final int EVENT_HUB_RESET = 6;
 
+    /**
+     * An event describing a client authorization state change. See
+     * {@link ContextHubClientCallback#onClientAuthorizationChanged} for more details on when this
+     * event will be sent. Contains the EXTRA_NANOAPP_ID and EXTRA_CLIENT_AUTHORIZATION_STATE
+     * extras.
+     */
+    public static final int EVENT_CLIENT_AUTHORIZATION = 7;
+
     private final Looper mMainLooper;
     private final IContextHubService mService;
     private Callback mCallback;
@@ -747,6 +798,14 @@
             public void onNanoAppDisabled(long nanoAppId) {
                 executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId));
             }
+
+            @Override
+            public void onClientAuthorizationChanged(
+                    long nanoAppId, @ContextHubManager.AuthorizationState int authorization) {
+                executor.execute(
+                        () -> callback.onClientAuthorizationChanged(
+                                client, nanoAppId, authorization));
+            }
         };
     }
 
@@ -757,9 +816,10 @@
      * registration succeeds, the client can send messages to nanoapps through the returned
      * {@link ContextHubClient} object, and receive notifications through the provided callback.
      *
+     * @param context  the context of the application
      * @param hubInfo  the hub to attach this client to
-     * @param callback the notification callback to register
      * @param executor the executor to invoke the callback
+     * @param callback the notification callback to register
      * @return the registered client object
      *
      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
@@ -773,8 +833,9 @@
             android.Manifest.permission.ACCESS_CONTEXT_HUB
     })
     @NonNull public ContextHubClient createClient(
-            @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
-            @NonNull @CallbackExecutor Executor executor) {
+            @Nullable Context context, @NonNull ContextHubInfo hubInfo,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull ContextHubClientCallback callback) {
         Objects.requireNonNull(callback, "Callback cannot be null");
         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
         Objects.requireNonNull(executor, "Executor cannot be null");
@@ -783,9 +844,14 @@
         IContextHubClientCallback clientInterface = createClientCallback(
                 client, callback, executor);
 
+        String attributionTag = null;
+        if (context != null) {
+            attributionTag = context.getAttributionTag();
+        }
+
         IContextHubClient clientProxy;
         try {
-            clientProxy = mService.createClient(hubInfo.getId(), clientInterface);
+            clientProxy = mService.createClient(hubInfo.getId(), clientInterface, attributionTag);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -794,19 +860,25 @@
         return client;
     }
 
+
+    /**
+     * Equivalent to
+     * {@link #createClient(ContextHubInfo, Executor, String, ContextHubClientCallback)}
+     * with the {@link Context} being set to null.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.LOCATION_HARDWARE,
+            android.Manifest.permission.ACCESS_CONTEXT_HUB
+    })
+    @NonNull public ContextHubClient createClient(
+            @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
+            @NonNull @CallbackExecutor Executor executor) {
+        return createClient(null /* context */, hubInfo, executor, callback);
+    }
+
     /**
      * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
      * with the executor using the main thread's Looper.
-     *
-     * @param hubInfo  the hub to attach this client to
-     * @param callback the notification callback to register
-     * @return the registered client object
-     *
-     * @throws IllegalArgumentException if hubInfo does not represent a valid hub
-     * @throws IllegalStateException    if there were too many registered clients at the service
-     * @throws NullPointerException     if callback or hubInfo is null
-     *
-     * @see ContextHubClientCallback
      */
     @RequiresPermission(anyOf = {
             android.Manifest.permission.LOCATION_HARDWARE,
@@ -814,7 +886,8 @@
     })
     @NonNull public ContextHubClient createClient(
             @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) {
-        return createClient(hubInfo, callback, new HandlerExecutor(Handler.getMain()));
+        return createClient(null /* context */, hubInfo, new HandlerExecutor(Handler.getMain()),
+                            callback);
     }
 
     /**
@@ -848,6 +921,8 @@
      * on the provided PendingIntent, then the client will be automatically unregistered by the
      * service.
      *
+     * @param context       the context of the application. If a PendingIntent client is recreated,
+     * the latest state in the context will be used and old state will be discarded
      * @param hubInfo       the hub to attach this client to
      * @param pendingIntent the PendingIntent to register to the client
      * @param nanoAppId     the ID of the nanoapp that Intent events will be generated for
@@ -862,16 +937,22 @@
             android.Manifest.permission.ACCESS_CONTEXT_HUB
     })
     @NonNull public ContextHubClient createClient(
-            @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+            @Nullable Context context, @NonNull ContextHubInfo hubInfo,
+            @NonNull PendingIntent pendingIntent, long nanoAppId) {
         Objects.requireNonNull(pendingIntent);
         Objects.requireNonNull(hubInfo);
 
         ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);
 
+        String attributionTag = null;
+        if (context != null) {
+            attributionTag = context.getAttributionTag();
+        }
+
         IContextHubClient clientProxy;
         try {
             clientProxy = mService.createPendingIntentClient(
-                    hubInfo.getId(), pendingIntent, nanoAppId);
+                    hubInfo.getId(), pendingIntent, nanoAppId, attributionTag);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -881,6 +962,19 @@
     }
 
     /**
+     * Equivalent to {@link #createClient(ContextHubInfo, PendingIntent, long, String)}
+     * with {@link Context} being set to null.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.LOCATION_HARDWARE,
+            android.Manifest.permission.ACCESS_CONTEXT_HUB
+    })
+    @NonNull public ContextHubClient createClient(
+            @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+        return createClient(null /* context */, hubInfo, pendingIntent, nanoAppId);
+    }
+
+    /**
      * Unregister a callback for receive messages from the context hub.
      *
      * @see Callback
diff --git a/core/java/android/hardware/location/IContextHubClientCallback.aidl b/core/java/android/hardware/location/IContextHubClientCallback.aidl
index 1c76bcb..bcd6b08 100644
--- a/core/java/android/hardware/location/IContextHubClientCallback.aidl
+++ b/core/java/android/hardware/location/IContextHubClientCallback.aidl
@@ -46,4 +46,7 @@
 
     // Callback invoked when a nanoapp is disabled at the attached Context Hub.
     void onNanoAppDisabled(long nanoAppId);
+
+    // Callback invoked when the authorization state of a client changes.
+    void onClientAuthorizationChanged(long nanoAppId, int authorization);
 }
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 04cc563..4961195 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -59,11 +59,13 @@
     int sendMessage(int contextHubHandle, int nanoAppHandle, in ContextHubMessage msg);
 
     // Creates a client to send and receive messages
-    IContextHubClient createClient(int contextHubId, in IContextHubClientCallback client);
+    IContextHubClient createClient(
+            int contextHubId, in IContextHubClientCallback client, in String attributionTag);
 
     // Creates a PendingIntent-based client to send and receive messages
     IContextHubClient createPendingIntentClient(
-            int contextHubId, in PendingIntent pendingIntent, long nanoAppId);
+            int contextHubId, in PendingIntent pendingIntent, long nanoAppId,
+            in String attributionTag);
 
     // Returns a list of ContextHub objects of available hubs
     List<ContextHubInfo> getContextHubs();
diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java
index 8de7ecd..96b1f19 100644
--- a/core/java/android/hardware/location/NanoAppState.java
+++ b/core/java/android/hardware/location/NanoAppState.java
@@ -15,10 +15,14 @@
  */
 package android.hardware.location;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A class describing the nanoapp state information resulting from a query to a Context Hub.
  *
@@ -29,11 +33,21 @@
     private long mNanoAppId;
     private int mNanoAppVersion;
     private boolean mIsEnabled;
+    private List<String> mNanoAppPermissions;
 
     public NanoAppState(long nanoAppId, int appVersion, boolean enabled) {
         mNanoAppId = nanoAppId;
         mNanoAppVersion = appVersion;
         mIsEnabled = enabled;
+        mNanoAppPermissions = new ArrayList<String>();
+    }
+
+    public NanoAppState(long nanoAppId, int appVersion, boolean enabled,
+                        @NonNull List<String> nanoAppPermissions) {
+        mNanoAppId = nanoAppId;
+        mNanoAppVersion = appVersion;
+        mIsEnabled = enabled;
+        mNanoAppPermissions = nanoAppPermissions;
     }
 
     /**
@@ -57,10 +71,19 @@
         return mIsEnabled;
     }
 
+    /**
+     * @return List of Android permissions that are required to communicate with this app.
+     */
+    public @NonNull List<String> getNanoAppPermissions() {
+        return mNanoAppPermissions;
+    }
+
     private NanoAppState(Parcel in) {
         mNanoAppId = in.readLong();
         mNanoAppVersion = in.readInt();
         mIsEnabled = (in.readInt() == 1);
+        mNanoAppPermissions = new ArrayList<String>();
+        in.readStringList(mNanoAppPermissions);
     }
 
     @Override
@@ -73,6 +96,7 @@
         out.writeLong(mNanoAppId);
         out.writeInt(mNanoAppVersion);
         out.writeInt(mIsEnabled ? 1 : 0);
+        out.writeStringList(mNanoAppPermissions);
     }
 
     public static final @android.annotation.NonNull Creator<NanoAppState> CREATOR =
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index c28bab7..5bac481 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -1198,7 +1198,10 @@
 
     /**
      * Returns whether the given functions are valid inputs to UsbManager.
-     * Currently the empty functions or any of MTP, PTP, RNDIS, MIDI are accepted.
+     * Currently the empty functions or any of MTP, PTP, RNDIS, MIDI, NCM are accepted.
+     *
+     * Only one function may be set at a time, except for RNDIS and NCM, which can be set together
+     * because from a user perspective they are the same function (tethering).
      *
      * @return Whether the mask is settable.
      *
@@ -1206,7 +1209,9 @@
      */
     public static boolean areSettableFunctions(long functions) {
         return functions == FUNCTION_NONE
-                || ((~SETTABLE_FUNCTIONS & functions) == 0 && Long.bitCount(functions) == 1);
+                || ((~SETTABLE_FUNCTIONS & functions) == 0
+                        && ((Long.bitCount(functions) == 1)
+                                || (functions == (FUNCTION_RNDIS | FUNCTION_NCM))));
     }
 
     /**
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 44a2e97..7e2be01 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1732,7 +1732,7 @@
                 // If app window has portrait orientation, regardless of what display orientation
                 // is, IME shouldn't use fullscreen-mode.
                 || (mInputEditorInfo.internalImeOptions
-                        & EditorInfo.IME_FLAG_APP_WINDOW_PORTRAIT) != 0) {
+                        & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0) {
             return false;
         }
         return true;
diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS
index e6a04da..d7db7c7 100644
--- a/core/java/android/inputmethodservice/OWNERS
+++ b/core/java/android/inputmethodservice/OWNERS
@@ -2,3 +2,5 @@
 set noparent
 
 include /services/core/java/com/android/server/inputmethod/OWNERS
+
+per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 84a2acc..b016ed6 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -82,4 +82,5 @@
 
     boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork);
     boolean isUidRestrictedOnMeteredNetworks(int uid);
+    boolean checkUidNetworkingBlocked(int uid, int uidRules, boolean isNetworkMetered, boolean isBackgroundRestricted);
 }
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 1a3dc97..0baf11e 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -23,11 +23,11 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.UnderlyingNetworkInfo;
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.IBinder;
 import android.os.Messenger;
-import com.android.internal.net.VpnInfo;
 
 /** {@hide} */
 interface INetworkStatsService {
@@ -70,7 +70,7 @@
          in Network[] defaultNetworks,
          in NetworkState[] networkStates,
          in String activeIface,
-         in VpnInfo[] vpnInfos);
+         in UnderlyingNetworkInfo[] underlyingNetworkInfos);
     /** Force update of statistics. */
     @UnsupportedAppUsage
     void forceUpdate();
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index d83715c..70bca30 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -15,6 +15,8 @@
  */
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.NonNull;
@@ -628,7 +630,7 @@
         }
 
         /** @hide */
-        @VisibleForTesting
+        @SystemApi(client = MODULE_LIBRARIES)
         public int getResourceId() {
             return mResourceId;
         }
diff --git a/core/java/android/net/MatchAllNetworkSpecifier.java b/core/java/android/net/MatchAllNetworkSpecifier.java
index 84985b6..9a11be0 100644
--- a/core/java/android/net/MatchAllNetworkSpecifier.java
+++ b/core/java/android/net/MatchAllNetworkSpecifier.java
@@ -32,17 +32,6 @@
  */
 @SystemApi
 public final class MatchAllNetworkSpecifier extends NetworkSpecifier implements Parcelable {
-    /**
-     * Utility method which verifies that the ns argument is not a MatchAllNetworkSpecifier and
-     * throws an IllegalArgumentException if it is.
-     * @hide
-     */
-    public static void checkNotMatchAllNetworkSpecifier(NetworkSpecifier ns) {
-        if (ns instanceof MatchAllNetworkSpecifier) {
-            throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted");
-        }
-    }
-
     /** @hide */
     @Override
     public boolean canBeSatisfiedBy(NetworkSpecifier other) {
diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java
index e65c27c..5d8122b 100644
--- a/core/java/android/net/NetworkIdentity.java
+++ b/core/java/android/net/NetworkIdentity.java
@@ -22,11 +22,12 @@
 import android.content.Context;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.os.Build;
 import android.service.NetworkIdentityProto;
 import android.telephony.Annotation.NetworkType;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.net.module.util.NetworkIdentityUtils;
+
 import java.util.Objects;
 
 /**
@@ -90,7 +91,8 @@
             builder.append(mSubType);
         }
         if (mSubscriberId != null) {
-            builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId));
+            builder.append(", subscriberId=")
+                    .append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
         }
         if (mNetworkId != null) {
             builder.append(", networkId=").append(mNetworkId);
@@ -111,7 +113,8 @@
         // Not dumping mSubType, subtypes are no longer supported.
 
         if (mSubscriberId != null) {
-            proto.write(NetworkIdentityProto.SUBSCRIBER_ID, scrubSubscriberId(mSubscriberId));
+            proto.write(NetworkIdentityProto.SUBSCRIBER_ID,
+                    NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
         }
         proto.write(NetworkIdentityProto.NETWORK_ID, mNetworkId);
         proto.write(NetworkIdentityProto.ROAMING, mRoaming);
@@ -150,32 +153,6 @@
     }
 
     /**
-     * Scrub given IMSI on production builds.
-     */
-    public static String scrubSubscriberId(String subscriberId) {
-        if (Build.IS_ENG) {
-            return subscriberId;
-        } else if (subscriberId != null) {
-            // TODO: parse this as MCC+MNC instead of hard-coding
-            return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "...";
-        } else {
-            return "null";
-        }
-    }
-
-    /**
-     * Scrub given IMSI on production builds.
-     */
-    public static String[] scrubSubscriberId(String[] subscriberId) {
-        if (subscriberId == null) return null;
-        final String[] res = new String[subscriberId.length];
-        for (int i = 0; i < res.length; i++) {
-            res[i] = NetworkIdentity.scrubSubscriberId(subscriberId[i]);
-        }
-        return res;
-    }
-
-    /**
      * Build a {@link NetworkIdentity} from the given {@link NetworkState} and {@code subType},
      * assuming that any mobile networks are using the current IMSI. The subType if applicable,
      * should be set as one of the TelephonyManager.NETWORK_TYPE_* constants, or
@@ -195,13 +172,15 @@
         subscriberId = state.subscriberId;
 
         if (type == TYPE_WIFI) {
-            if (state.networkId != null) {
-                networkId = state.networkId;
-            } else {
-                final WifiManager wifi = (WifiManager) context.getSystemService(
-                        Context.WIFI_SERVICE);
-                final WifiInfo info = wifi.getConnectionInfo();
-                networkId = info != null ? info.getSSID() : null;
+            if (state.networkCapabilities.getSsid() != null) {
+                networkId = state.networkCapabilities.getSsid();
+                if (networkId == null) {
+                    // TODO: Figure out if this code path never runs. If so, remove them.
+                    final WifiManager wifi = (WifiManager) context.getSystemService(
+                            Context.WIFI_SERVICE);
+                    final WifiInfo info = wifi.getConnectionInfo();
+                    networkId = info != null ? info.getSSID() : null;
+                }
             }
         }
 
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index ed169e7..3e6237d 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -464,6 +464,31 @@
     }
 
     /**
+     * Figure out if networking is blocked for a given set of conditions.
+     *
+     * This is used by ConnectivityService via passing stale copies of conditions, so it must not
+     * take any locks.
+     *
+     * @param uid The target uid.
+     * @param uidRules The uid rules which are obtained from NetworkPolicyManagerService.
+     * @param isNetworkMetered True if the network is metered.
+     * @param isBackgroundRestricted True if data saver is enabled.
+     *
+     * @return true if networking is blocked for the UID under the specified conditions.
+     *
+     * @hide
+     */
+    public boolean checkUidNetworkingBlocked(int uid, int uidRules,
+            boolean isNetworkMetered, boolean isBackgroundRestricted) {
+        try {
+            return mService.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered,
+                    isBackgroundRestricted);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Check that the given uid is restricted from doing networking on metered networks.
      *
      * @param uid The target uid.
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 713b688..e1ef8b5 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -30,7 +31,8 @@
 public class NetworkState implements Parcelable {
     private static final boolean VALIDATE_ROAMING_STATE = false;
 
-    public static final NetworkState EMPTY = new NetworkState(null, null, null, null, null, null);
+    // TODO: remove and make members @NonNull.
+    public static final NetworkState EMPTY = new NetworkState();
 
     public final NetworkInfo networkInfo;
     public final LinkProperties linkProperties;
@@ -40,9 +42,18 @@
     public final String subscriberId;
     public final String networkId;
 
-    public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
-            NetworkCapabilities networkCapabilities, Network network, String subscriberId,
-            String networkId) {
+    private NetworkState() {
+        networkInfo = null;
+        linkProperties = null;
+        networkCapabilities = null;
+        network = null;
+        subscriberId = null;
+        networkId = null;
+    }
+
+    public NetworkState(@NonNull NetworkInfo networkInfo, @NonNull LinkProperties linkProperties,
+            @NonNull NetworkCapabilities networkCapabilities, @NonNull Network network,
+            String subscriberId, String networkId) {
         this.networkInfo = networkInfo;
         this.linkProperties = linkProperties;
         this.networkCapabilities = networkCapabilities;
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index dc33cc7..aa61e03 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -48,6 +48,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.net.module.util.NetworkIdentityUtils;
 
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
@@ -296,11 +297,11 @@
         builder.append("matchRule=").append(getMatchRuleName(mMatchRule));
         if (mSubscriberId != null) {
             builder.append(", subscriberId=").append(
-                    NetworkIdentity.scrubSubscriberId(mSubscriberId));
+                    NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
         }
         if (mMatchSubscriberIds != null) {
             builder.append(", matchSubscriberIds=").append(
-                    Arrays.toString(NetworkIdentity.scrubSubscriberId(mMatchSubscriberIds)));
+                    Arrays.toString(NetworkIdentityUtils.scrubSubscriberIds(mMatchSubscriberIds)));
         }
         if (mNetworkId != null) {
             builder.append(", networkId=").append(mNetworkId);
diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java
index 6a8e3f9..5e56164 100644
--- a/core/java/android/net/OemNetworkPreferences.java
+++ b/core/java/android/net/OemNetworkPreferences.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -18,14 +18,14 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.Parcelable;
-import android.util.SparseArray;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 /** @hide */
@@ -60,16 +60,16 @@
     public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4;
 
     @NonNull
-    private final SparseArray<List<String>> mNetworkMappings;
+    private final Bundle mNetworkMappings;
 
     @NonNull
-    public SparseArray<List<String>> getNetworkPreferences() {
-        return mNetworkMappings.clone();
+    public Map<String, Integer> getNetworkPreferences() {
+        return convertToUnmodifiableMap(mNetworkMappings);
     }
 
-    private OemNetworkPreferences(@NonNull SparseArray<List<String>> networkMappings) {
+    private OemNetworkPreferences(@NonNull final Bundle networkMappings) {
         Objects.requireNonNull(networkMappings);
-        mNetworkMappings = networkMappings.clone();
+        mNetworkMappings = (Bundle) networkMappings.clone();
     }
 
     @Override
@@ -99,26 +99,45 @@
      * @hide
      */
     public static final class Builder {
-        private final SparseArray<List<String>> mNetworkMappings;
+        private final Bundle mNetworkMappings;
 
         public Builder() {
-            mNetworkMappings = new SparseArray<>();
+            mNetworkMappings = new Bundle();
+        }
+
+        public Builder(@NonNull final OemNetworkPreferences preferences) {
+            Objects.requireNonNull(preferences);
+            mNetworkMappings = (Bundle) preferences.mNetworkMappings.clone();
         }
 
         /**
-         * Add a network preference for a list of packages.
+         * Add a network preference for a given package. Previously stored values for the given
+         * package will be overwritten.
          *
-         * @param preference the desired network preference to use
-         * @param packages   full package names (e.g.: "com.google.apps.contacts") for apps to use
-         *                   the given preference
+         * @param packageName full package name (e.g.: "com.google.apps.contacts") of the app
+         *                    to use the given preference
+         * @param preference  the desired network preference to use
          * @return The builder to facilitate chaining.
          */
         @NonNull
-        public Builder addNetworkPreference(@OemNetworkPreference final int preference,
-                @NonNull List<String> packages) {
-            Objects.requireNonNull(packages);
-            mNetworkMappings.put(preference,
-                    Collections.unmodifiableList(new ArrayList<>(packages)));
+        public Builder addNetworkPreference(@NonNull final String packageName,
+                @OemNetworkPreference final int preference) {
+            Objects.requireNonNull(packageName);
+            mNetworkMappings.putInt(packageName, preference);
+            return this;
+        }
+
+        /**
+         * Remove a network preference for a given package.
+         *
+         * @param packageName full package name (e.g.: "com.google.apps.contacts") of the app to
+         *                    remove a preference for.
+         * @return The builder to facilitate chaining.
+         */
+        @NonNull
+        public Builder removeNetworkPreference(@NonNull final String packageName) {
+            Objects.requireNonNull(packageName);
+            mNetworkMappings.remove(packageName);
             return this;
         }
 
@@ -131,6 +150,14 @@
         }
     }
 
+    private static Map<String, Integer> convertToUnmodifiableMap(@NonNull final Bundle bundle) {
+        final Map<String, Integer> networkPreferences = new HashMap<>();
+        for (final String key : bundle.keySet()) {
+            networkPreferences.put(key, bundle.getInt(key));
+        }
+        return Collections.unmodifiableMap(networkPreferences);
+    }
+
     /** @hide */
     @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = {
             OEM_NETWORK_PREFERENCE_DEFAULT,
@@ -168,7 +195,7 @@
 
     @Override
     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        dest.writeSparseArray(mNetworkMappings);
+        dest.writeBundle(mNetworkMappings);
     }
 
     @Override
@@ -187,7 +214,7 @@
                 @Override
                 public OemNetworkPreferences createFromParcel(@NonNull android.os.Parcel in) {
                     return new OemNetworkPreferences(
-                            in.readSparseArray(getClass().getClassLoader()));
+                            in.readBundle(getClass().getClassLoader()));
                 }
             };
 }
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/net/UnderlyingNetworkInfo.aidl
similarity index 91%
rename from core/java/com/android/internal/net/VpnInfo.aidl
rename to core/java/android/net/UnderlyingNetworkInfo.aidl
index 6fc97be..a56f2f4 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/core/java/android/net/UnderlyingNetworkInfo.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.net;
 
-parcelable VpnInfo;
+parcelable UnderlyingNetworkInfo;
diff --git a/core/java/android/net/UnderlyingNetworkInfo.java b/core/java/android/net/UnderlyingNetworkInfo.java
new file mode 100644
index 0000000..7bf9231
--- /dev/null
+++ b/core/java/android/net/UnderlyingNetworkInfo.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 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 android.net;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A lightweight container used to carry information on the networks that underly a given
+ * virtual network.
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public final class UnderlyingNetworkInfo implements Parcelable {
+    /** The owner of this network. */
+    public final int ownerUid;
+    /** The interface name of this network. */
+    @NonNull
+    public final String iface;
+    /** The names of the interfaces underlying this network. */
+    @NonNull
+    public final List<String> underlyingIfaces;
+
+    public UnderlyingNetworkInfo(int ownerUid, @NonNull String iface,
+            @NonNull List<String> underlyingIfaces) {
+        Objects.requireNonNull(iface);
+        Objects.requireNonNull(underlyingIfaces);
+        this.ownerUid = ownerUid;
+        this.iface = iface;
+        this.underlyingIfaces = Collections.unmodifiableList(new ArrayList<>(underlyingIfaces));
+    }
+
+    private UnderlyingNetworkInfo(@NonNull Parcel in) {
+        this.ownerUid = in.readInt();
+        this.iface = in.readString();
+        this.underlyingIfaces = new ArrayList<>();
+        in.readList(this.underlyingIfaces, null /*classLoader*/);
+    }
+
+    @Override
+    public String toString() {
+        return "UnderlyingNetworkInfo{"
+                + "ownerUid=" + ownerUid
+                + ", iface='" + iface + '\''
+                + ", underlyingIfaces='" + underlyingIfaces.toString() + '\''
+                + '}';
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(ownerUid);
+        dest.writeString(iface);
+        dest.writeList(underlyingIfaces);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<UnderlyingNetworkInfo> CREATOR =
+            new Parcelable.Creator<UnderlyingNetworkInfo>() {
+        @NonNull
+        @Override
+        public UnderlyingNetworkInfo createFromParcel(@NonNull Parcel in) {
+            return new UnderlyingNetworkInfo(in);
+        }
+
+        @NonNull
+        @Override
+        public UnderlyingNetworkInfo[] newArray(int size) {
+            return new UnderlyingNetworkInfo[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof UnderlyingNetworkInfo)) return false;
+        final UnderlyingNetworkInfo that = (UnderlyingNetworkInfo) o;
+        return ownerUid == that.ownerUid
+                && Objects.equals(iface, that.iface)
+                && Objects.equals(underlyingIfaces, that.underlyingIfaces);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(ownerUid, iface, underlyingIfaces);
+    }
+}
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 250cff2..a22d41a 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -26,7 +26,7 @@
 import android.widget.TextView;
 
 import com.android.internal.util.HexDump;
-import com.android.org.bouncycastle.asn1.x509.X509Name;
+import com.android.internal.org.bouncycastle.asn1.x509.X509Name;
 
 import java.io.ByteArrayInputStream;
 import java.math.BigInteger;
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 80ac64b..4f293ee 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -16,8 +16,11 @@
 
 package android.net.vcn;
 
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnUnderlyingNetworkPolicy;
 import android.os.ParcelUuid;
 
 /**
@@ -29,4 +32,5 @@
 
     void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
     void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
+    VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(in NetworkCapabilities nc, in LinkProperties lp);
 }
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index ede8faa..5eb4ba6 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -96,7 +96,11 @@
         return mPackageName;
     }
 
-    /** Retrieves the set of configured tunnels. */
+    /**
+     * Retrieves the set of configured tunnels.
+     *
+     * @hide
+     */
     @NonNull
     public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() {
         return Collections.unmodifiableSet(mGatewayConnectionConfigs);
@@ -146,7 +150,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeParcelable(toPersistableBundle(), flags);
     }
 
@@ -164,8 +168,12 @@
                 }
             };
 
-    /** This class is used to incrementally build {@link VcnConfig} objects. */
-    public static class Builder {
+    /**
+     * This class is used to incrementally build {@link VcnConfig} objects.
+     *
+     * @hide
+     */
+    public static final class Builder {
         @NonNull private final String mPackageName;
 
         @NonNull
@@ -182,6 +190,7 @@
          *
          * @param gatewayConnectionConfig the configuration for an individual gateway connection
          * @return this {@link Builder} instance, for chaining
+         * @hide
          */
         @NonNull
         public Builder addGatewayConnectionConfig(
@@ -196,6 +205,7 @@
          * Builds and validates the VcnConfig.
          *
          * @return an immutable VcnConfig instance
+         * @hide
          */
         @NonNull
         public VcnConfig build() {
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index d531cdb..cead2f1 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -17,6 +17,7 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -25,14 +26,19 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -97,6 +103,26 @@
         ALLOWED_CAPABILITIES = Collections.unmodifiableSet(allowedCaps);
     }
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"NET_CAPABILITY_"},
+            value = {
+                NetworkCapabilities.NET_CAPABILITY_MMS,
+                NetworkCapabilities.NET_CAPABILITY_SUPL,
+                NetworkCapabilities.NET_CAPABILITY_DUN,
+                NetworkCapabilities.NET_CAPABILITY_FOTA,
+                NetworkCapabilities.NET_CAPABILITY_IMS,
+                NetworkCapabilities.NET_CAPABILITY_CBS,
+                NetworkCapabilities.NET_CAPABILITY_IA,
+                NetworkCapabilities.NET_CAPABILITY_RCS,
+                NetworkCapabilities.NET_CAPABILITY_XCAP,
+                NetworkCapabilities.NET_CAPABILITY_EIMS,
+                NetworkCapabilities.NET_CAPABILITY_INTERNET,
+                NetworkCapabilities.NET_CAPABILITY_MCX,
+            })
+    public @interface VcnSupportedCapability {}
+
     private static final int DEFAULT_MAX_MTU = 1500;
 
     /**
@@ -128,10 +154,10 @@
             };
 
     private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
-    @NonNull private final Set<Integer> mExposedCapabilities;
+    @NonNull private final SortedSet<Integer> mExposedCapabilities;
 
     private static final String UNDERLYING_CAPABILITIES_KEY = "mUnderlyingCapabilities";
-    @NonNull private final Set<Integer> mUnderlyingCapabilities;
+    @NonNull private final SortedSet<Integer> mUnderlyingCapabilities;
 
     // TODO: Add Ike/ChildSessionParams as a subclass - maybe VcnIkeGatewayConnectionConfig
 
@@ -141,14 +167,14 @@
     private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs";
     @NonNull private final long[] mRetryIntervalsMs;
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public VcnGatewayConnectionConfig(
+    /** Builds a VcnGatewayConnectionConfig with the specified parameters. */
+    private VcnGatewayConnectionConfig(
             @NonNull Set<Integer> exposedCapabilities,
             @NonNull Set<Integer> underlyingCapabilities,
             @NonNull long[] retryIntervalsMs,
             @IntRange(from = MIN_MTU_V6) int maxMtu) {
-        mExposedCapabilities = exposedCapabilities;
-        mUnderlyingCapabilities = underlyingCapabilities;
+        mExposedCapabilities = new TreeSet(exposedCapabilities);
+        mUnderlyingCapabilities = new TreeSet(underlyingCapabilities);
         mRetryIntervalsMs = retryIntervalsMs;
         mMaxMtu = maxMtu;
 
@@ -163,9 +189,9 @@
         final PersistableBundle underlyingCapsBundle =
                 in.getPersistableBundle(UNDERLYING_CAPABILITIES_KEY);
 
-        mExposedCapabilities = new ArraySet<>(PersistableBundleUtils.toList(
+        mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
-        mUnderlyingCapabilities = new ArraySet<>(PersistableBundleUtils.toList(
+        mUnderlyingCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 underlyingCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
         mMaxMtu = in.getInt(MAX_MTU_KEY);
@@ -219,52 +245,93 @@
     /**
      * Returns all exposed capabilities.
      *
+     * <p>The returned integer-value capabilities will not contain duplicates, and will be sorted in
+     * ascending numerical order.
+     *
+     * @see Builder#addExposedCapability(int)
+     * @see Builder#clearExposedCapability(int)
      * @hide
      */
     @NonNull
+    public int[] getExposedCapabilities() {
+        // Sorted set guarantees ordering
+        return ArrayUtils.convertToIntArray(new ArrayList<>(mExposedCapabilities));
+    }
+
+    /**
+     * Returns all exposed capabilities.
+     *
+     * <p>Left to prevent the need to make major changes while changes are actively in flight.
+     *
+     * @deprecated use getExposedCapabilities() instead
+     * @hide
+     */
+    @Deprecated
+    @NonNull
     public Set<Integer> getAllExposedCapabilities() {
         return Collections.unmodifiableSet(mExposedCapabilities);
     }
 
     /**
-     * Checks if this config is configured to support/expose a specific capability.
+     * Returns all capabilities required of underlying networks.
      *
-     * @param capability the capability to check for
+     * <p>The returned integer-value capabilities will be sorted in ascending numerical order.
+     *
+     * @see Builder#addRequiredUnderlyingCapability(int)
+     * @see Builder#clearRequiredUnderlyingCapability(int)
+     * @hide
      */
-    public boolean hasExposedCapability(int capability) {
-        checkValidCapability(capability);
-
-        return mExposedCapabilities.contains(capability);
+    @NonNull
+    public int[] getRequiredUnderlyingCapabilities() {
+        // Sorted set guarantees ordering
+        return ArrayUtils.convertToIntArray(new ArrayList<>(mUnderlyingCapabilities));
     }
 
     /**
      * Returns all capabilities required of underlying networks.
      *
+     * <p>Left to prevent the need to make major changes while changes are actively in flight.
+     *
+     * @deprecated use getRequiredUnderlyingCapabilities() instead
      * @hide
      */
+    @Deprecated
     @NonNull
     public Set<Integer> getAllUnderlyingCapabilities() {
         return Collections.unmodifiableSet(mUnderlyingCapabilities);
     }
 
     /**
-     * Checks if this config requires an underlying network to have the specified capability.
+     * Retrieves the configured retry intervals.
      *
-     * @param capability the capability to check for
+     * @see Builder#setRetryInterval(long[])
+     * @hide
      */
-    public boolean requiresUnderlyingCapability(int capability) {
-        checkValidCapability(capability);
-
-        return mUnderlyingCapabilities.contains(capability);
-    }
-
-    /** Retrieves the configured retry intervals. */
     @NonNull
-    public long[] getRetryIntervalsMs() {
+    public long[] getRetryInterval() {
         return Arrays.copyOf(mRetryIntervalsMs, mRetryIntervalsMs.length);
     }
 
-    /** Retrieves the maximum MTU allowed for this Gateway Connection. */
+    /**
+     * Retrieves the configured retry intervals.
+     *
+     * <p>Left to prevent the need to make major changes while changes are actively in flight.
+     *
+     * @deprecated use getRequiredUnderlyingCapabilities() instead
+     * @hide
+     */
+    @Deprecated
+    @NonNull
+    public long[] getRetryIntervalsMs() {
+        return getRetryInterval();
+    }
+
+    /**
+     * Retrieves the maximum MTU allowed for this Gateway Connection.
+     *
+     * @see Builder.setMaxMtu(int)
+     * @hide
+     */
     @IntRange(from = MIN_MTU_V6)
     public int getMaxMtu() {
         return mMaxMtu;
@@ -319,8 +386,12 @@
                 && mMaxMtu == rhs.mMaxMtu;
     }
 
-    /** This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects. */
-    public static class Builder {
+    /**
+     * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects.
+     *
+     * @hide
+     */
+    public static final class Builder {
         @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet();
         @NonNull private final Set<Integer> mUnderlyingCapabilities = new ArraySet();
         @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS;
@@ -338,8 +409,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
+         * @hide
          */
-        public Builder addExposedCapability(int exposedCapability) {
+        @NonNull
+        public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) {
             checkValidCapability(exposedCapability);
 
             mExposedCapabilities.add(exposedCapability);
@@ -354,8 +427,10 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway
          *     Connection
+         * @hide
          */
-        public Builder removeExposedCapability(int exposedCapability) {
+        @NonNull
+        public Builder clearExposedCapability(@VcnSupportedCapability int exposedCapability) {
             checkValidCapability(exposedCapability);
 
             mExposedCapabilities.remove(exposedCapability);
@@ -370,8 +445,11 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
+         * @hide
          */
-        public Builder addRequiredUnderlyingCapability(int underlyingCapability) {
+        @NonNull
+        public Builder addRequiredUnderlyingCapability(
+                @VcnSupportedCapability int underlyingCapability) {
             checkValidCapability(underlyingCapability);
 
             mUnderlyingCapabilities.add(underlyingCapability);
@@ -390,8 +468,11 @@
          * @return this {@link Builder} instance, for chaining
          * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying
          *     networks
+         * @hide
          */
-        public Builder removeRequiredUnderlyingCapability(int underlyingCapability) {
+        @NonNull
+        public Builder clearRequiredUnderlyingCapability(
+                @VcnSupportedCapability int underlyingCapability) {
             checkValidCapability(underlyingCapability);
 
             mUnderlyingCapabilities.remove(underlyingCapability);
@@ -420,6 +501,7 @@
          *     15m]}
          * @return this {@link Builder} instance, for chaining
          * @see VcnManager for additional discussion on fail-safe mode
+         * @hide
          */
         @NonNull
         public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) {
@@ -441,6 +523,7 @@
          * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than
          *     the IPv6 minimum MTU of 1280. Defaults to 1500.
          * @return this {@link Builder} instance, for chaining
+         * @hide
          */
         @NonNull
         public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) {
@@ -455,6 +538,7 @@
          * Builds and validates the VcnGatewayConnectionConfig.
          *
          * @return an immutable VcnGatewayConnectionConfig instance
+         * @hide
          */
         @NonNull
         public VcnGatewayConnectionConfig build() {
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 2ccdc26..fa090f5 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -21,6 +21,8 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -62,7 +64,7 @@
  * @hide
  */
 @SystemService(Context.VCN_MANAGEMENT_SERVICE)
-public final class VcnManager {
+public class VcnManager {
     @NonNull private static final String TAG = VcnManager.class.getSimpleName();
 
     @VisibleForTesting
@@ -222,6 +224,37 @@
     }
 
     /**
+     * Queries the underlying network policy for a network with the given parameters.
+     *
+     * <p>Prior to a new NetworkAgent being registered, or upon notification that Carrier VCN policy
+     * may have changed via {@link VcnUnderlyingNetworkPolicyListener#onPolicyChanged()}, a Network
+     * Provider MUST poll for the updated Network policy based on that Network's capabilities and
+     * properties.
+     *
+     * @param networkCapabilities the NetworkCapabilities to be used in determining the Network
+     *     policy for this Network.
+     * @param linkProperties the LinkProperties to be used in determining the Network policy for
+     *     this Network.
+     * @throws SecurityException if the caller does not have permission NETWORK_FACTORY
+     * @return the VcnUnderlyingNetworkPolicy to be used for this Network.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(
+            @NonNull NetworkCapabilities networkCapabilities,
+            @NonNull LinkProperties linkProperties) {
+        requireNonNull(networkCapabilities, "networkCapabilities must not be null");
+        requireNonNull(linkProperties, "linkProperties must not be null");
+
+        try {
+            return mService.getUnderlyingNetworkPolicy(networkCapabilities, linkProperties);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Binder wrapper for added VcnUnderlyingNetworkPolicyListeners to receive signals from System
      * Server.
      *
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 71d2177..f2b466d 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -70,17 +70,6 @@
     public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
 
     /**
-     * Modeled power components are used for testing only.  They are returned if the
-     * {@link BatteryUsageStatsQuery#FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED} is set.
-     * The modeled power components are retrieved with {@link #getConsumedPowerForCustomComponent}.
-     * The ID of a modeled power component is calculated as
-     * (FIRST_MODELED_POWER_COMPONENT_ID + powerComponentId), e.g.
-     * FIRST_MODELED_POWER_COMPONENT_ID + POWER_COMPONENT_CPU.
-     */
-    public static final int FIRST_MODELED_POWER_COMPONENT_ID = 10000;
-    public static final int LAST_MODELED_POWER_COMPONENT_ID = 19999;
-
-    /**
      * Time usage component, describing the particular part of the system
      * that was used for the corresponding amount of time.
      *
@@ -182,10 +171,9 @@
     protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
         final PowerComponents.Builder mPowerComponentsBuilder;
 
-        public BaseBuilder(int customPowerComponentCount, int customTimeComponentCount,
-                boolean includeModeledComponents) {
+        public BaseBuilder(int customPowerComponentCount, int customTimeComponentCount) {
             mPowerComponentsBuilder = new PowerComponents.Builder(customPowerComponentCount,
-                    customTimeComponentCount, includeModeledComponents);
+                    customTimeComponentCount);
         }
 
         /**
@@ -216,16 +204,6 @@
         }
 
         /**
-         * Sets the total amount of power consumed since BatteryStats reset, mAh.
-         */
-        @SuppressWarnings("unchecked")
-        @NonNull
-        public T setConsumedPower(double consumedPower) {
-            mPowerComponentsBuilder.setTotalPowerConsumed(consumedPower);
-            return (T) this;
-        }
-
-        /**
          * Sets the amount of time used by the specified component, e.g. CPU, WiFi etc.
          *
          * @param componentId              The ID of the time component, e.g.
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b846142..cc86a60 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1556,8 +1556,7 @@
         public int statSoftIrqTime;
         public int statIdlTime;
 
-        // Platform-level low power state stats
-        public String statPlatformIdleState;
+        // Low power state stats
         public String statSubsystemPowerState;
 
         public HistoryStepDetails() {
@@ -1589,7 +1588,6 @@
             out.writeInt(statIrqTime);
             out.writeInt(statSoftIrqTime);
             out.writeInt(statIdlTime);
-            out.writeString(statPlatformIdleState);
             out.writeString(statSubsystemPowerState);
         }
 
@@ -1611,7 +1609,6 @@
             statIrqTime = in.readInt();
             statSoftIrqTime = in.readInt();
             statIdlTime = in.readInt();
-            statPlatformIdleState = in.readString();
             statSubsystemPowerState = in.readString();
         }
     }
@@ -1656,6 +1653,7 @@
         public byte batteryPlugType;
 
         public short batteryTemperature;
+        // Battery voltage in millivolts (mV).
         @UnsupportedAppUsage
         public char batteryVoltage;
 
@@ -6599,9 +6597,6 @@
                             item.append(sb);
                             item.append(")");
                         }
-                        item.append(", PlatformIdleStat ");
-                        item.append(rec.stepDetails.statPlatformIdleState);
-                        item.append("\n");
 
                         item.append(", SubsystemPowerState ");
                         item.append(rec.stepDetails.statSubsystemPowerState);
@@ -6639,12 +6634,6 @@
                         item.append(',');
                         item.append(rec.stepDetails.statIdlTime);
                         item.append(',');
-                        if (rec.stepDetails.statPlatformIdleState != null) {
-                            item.append(rec.stepDetails.statPlatformIdleState);
-                            if (rec.stepDetails.statSubsystemPowerState != null) {
-                                item.append(',');
-                            }
-                        }
 
                         if (rec.stepDetails.statSubsystemPowerState != null) {
                             item.append(rec.stepDetails.statSubsystemPowerState);
diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java
index f21a812..04e529e 100644
--- a/core/java/android/os/BatteryStatsManager.java
+++ b/core/java/android/os/BatteryStatsManager.java
@@ -32,6 +32,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
 
 /**
  * This class provides an API surface for internal system components to report events that are
@@ -183,8 +184,20 @@
     @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
     @NonNull
     public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
+        return getBatteryUsageStats(List.of(query)).get(0);
+    }
+
+    /**
+     * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
+     * and per-UID basis.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BATTERY_STATS)
+    @NonNull
+    public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
         try {
-            return mBatteryStats.getBatteryUsageStats(query);
+            return mBatteryStats.getBatteryUsageStats(queries);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index a6df87d..af8e8de 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -114,7 +114,6 @@
     public static final class Builder {
         private final int mCustomPowerComponentCount;
         private final int mCustomTimeComponentCount;
-        private final boolean mIncludeModeledComponents;
         private double mConsumedPower;
         private int mDischargePercentage;
         private final SparseArray<UidBatteryConsumer.Builder> mUidBatteryConsumerBuilders =
@@ -122,11 +121,9 @@
         private final SparseArray<SystemBatteryConsumer.Builder> mSystemBatteryConsumerBuilders =
                 new SparseArray<>();
 
-        public Builder(int customPowerComponentCount, int customTimeComponentCount,
-                boolean includeModeledComponents) {
+        public Builder(int customPowerComponentCount, int customTimeComponentCount) {
             mCustomPowerComponentCount = customPowerComponentCount;
             mCustomTimeComponentCount = customTimeComponentCount;
-            mIncludeModeledComponents = includeModeledComponents;
         }
 
         /**
@@ -169,7 +166,7 @@
             UidBatteryConsumer.Builder builder = mUidBatteryConsumerBuilders.get(uid);
             if (builder == null) {
                 builder = new UidBatteryConsumer.Builder(mCustomPowerComponentCount,
-                        mCustomTimeComponentCount, mIncludeModeledComponents, batteryStatsUid);
+                        mCustomTimeComponentCount, batteryStatsUid);
                 mUidBatteryConsumerBuilders.put(uid, builder);
             }
             return builder;
@@ -185,7 +182,7 @@
             SystemBatteryConsumer.Builder builder = mSystemBatteryConsumerBuilders.get(drainType);
             if (builder == null) {
                 builder = new SystemBatteryConsumer.Builder(mCustomPowerComponentCount,
-                        mCustomTimeComponentCount, mIncludeModeledComponents, drainType);
+                        mCustomTimeComponentCount, drainType);
                 mSystemBatteryConsumerBuilders.put(drainType, builder);
             }
             return builder;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 9a00679..48e7389 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -38,18 +38,19 @@
      * @hide
      */
     @IntDef(flag = true, prefix = { "FLAG_BATTERY_USAGE_STATS_" }, value = {
-            FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED,
+            FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BatteryUsageStatsFlags {}
 
     /**
-     * Indicates that modeled battery usage stats should be returned along with
-     * measured ones.
+     * Indicates that power estimations should be based on the usage time and
+     * average power constants provided in the PowerProfile, even if on-device power monitoring
+     * is available.
      *
      * @hide
      */
-    public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED = 1;
+    public static final int FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL = 1;
 
     private final int mFlags;
 
@@ -112,11 +113,13 @@
         }
 
         /**
-         * Requests to include modeled battery usage stats along with measured ones.
+         * Requests to return modeled battery usage stats only, even if on-device
+         * power monitoring data is available.
+         *
          * Should only be used for testing and debugging.
          */
-        public Builder includeModeled() {
-            mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED;
+        public Builder powerProfileModeledOnly() {
+            mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL;
             return this;
         }
     }
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 0185ba4..16d041a 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -547,7 +547,8 @@
         }
 
         try {
-            return transactNative(code, data, reply, flags);
+            boolean replyOwnsNative = (reply == null) ? false : reply.ownsNativeParcelObject();
+            return transactNative(code, data, reply, replyOwnsNative, flags);
         } finally {
             AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
 
@@ -572,7 +573,7 @@
      * Native implementation of transact() for proxies
      */
     public native boolean transactNative(int code, Parcel data, Parcel reply,
-            int flags) throws RemoteException;
+            boolean replyOwnsNativeParcelObject, int flags) throws RemoteException;
     /**
      * See {@link IBinder#linkToDeath(DeathRecipient, int)}
      */
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index e504946..74df1b2 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -27,6 +27,7 @@
 import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.sysprop.SocProperties;
 import android.sysprop.TelephonyProperties;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -88,6 +89,14 @@
     /** The end-user-visible name for the end product. */
     public static final String MODEL = getString("ro.product.model");
 
+    /** The manufacturer of the device's primary system-on-chip. */
+    @NonNull
+    public static final String SOC_MANUFACTURER = SocProperties.soc_manufacturer().orElse(UNKNOWN);
+
+    /** The model name of the device's primary system-on-chip. */
+    @NonNull
+    public static final String SOC_MODEL = SocProperties.soc_model().orElse(UNKNOWN);
+
     /** The system bootloader version number. */
     public static final String BOOTLOADER = getString("ro.bootloader");
 
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 1c1f5c0..102f525 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -45,6 +45,7 @@
     @VisibleForTesting
     static final int FLAG_ALLOW_FDS = 1 << 10;
 
+    /** An unmodifiable {@code Bundle} that is always {@link #isEmpty() empty}. */
     public static final Bundle EMPTY;
 
     /**
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index 869a727..cb4e9cb 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -364,8 +364,22 @@
         @Override
         public long getDuration() {
             long maxDuration = Long.MIN_VALUE;
+            boolean hasUnknownStep = false;
             for (int i = 0; i < mEffects.size(); i++) {
-                maxDuration = Math.max(maxDuration, mEffects.valueAt(i).getDuration());
+                long duration = mEffects.valueAt(i).getDuration();
+                if (duration == Long.MAX_VALUE) {
+                    // If any duration is repeating, this combination duration is also repeating.
+                    return duration;
+                }
+                maxDuration = Math.max(maxDuration, duration);
+                // If any step is unknown, this combination duration will also be unknown, unless
+                // any step is repeating. Repeating vibrations take precedence over non-repeating
+                // ones in the service, so continue looping to check for repeating steps.
+                hasUnknownStep |= duration < 0;
+            }
+            if (hasUnknownStep) {
+                // If any step is unknown, this combination duration is also unknown.
+                return -1;
             }
             return maxDuration;
         }
@@ -477,16 +491,25 @@
 
         @Override
         public long getDuration() {
+            boolean hasUnknownStep = false;
             long durations = 0;
             final int effectCount = mEffects.size();
             for (int i = 0; i < effectCount; i++) {
                 CombinedVibrationEffect effect = mEffects.get(i);
                 long duration = effect.getDuration();
-                if (duration < 0) {
-                    // If any duration is unknown, this combination duration is also unknown.
+                if (duration == Long.MAX_VALUE) {
+                    // If any duration is repeating, this combination duration is also repeating.
                     return duration;
                 }
                 durations += duration;
+                // If any step is unknown, this combination duration will also be unknown, unless
+                // any step is repeating. Repeating vibrations take precedence over non-repeating
+                // ones in the service, so continue looping to check for repeating steps.
+                hasUnknownStep |= duration < 0;
+            }
+            if (hasUnknownStep) {
+                // If any step is unknown, this combination duration is also unknown.
+                return -1;
             }
             long delays = 0;
             for (int i = 0; i < effectCount; i++) {
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 804dc10..f9e2947 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.os.CombinedVibrationEffect;
+import android.os.IVibratorStateListener;
 import android.os.VibrationAttributes;
 import android.os.VibratorInfo;
 
@@ -24,6 +25,9 @@
 interface IVibratorManagerService {
     int[] getVibratorIds();
     VibratorInfo getVibratorInfo(int vibratorId);
+    boolean isVibrating(int vibratorId);
+    boolean registerVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
+    boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
     boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
             in CombinedVibrationEffect effect, in VibrationAttributes attributes);
     void vibrate(int uid, String opPkg, in CombinedVibrationEffect effect,
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index fc65090..5f5a910 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3729,4 +3729,9 @@
     public long getBlobAshmemSize() {
         return nativeGetBlobAshmemSize(mNativePtr);
     }
+
+    /** @hide */
+    /*package*/ boolean ownsNativeParcelObject() {
+        return mOwnsNativeParcelObject;
+    }
 }
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index 5e3a34d..e5e9b5f 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -47,6 +47,8 @@
 public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
         XmlUtils.WriteMapCallback {
     private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
+
+    /** An unmodifiable {@code PersistableBundle} that is always {@link #isEmpty() empty}. */
     public static final PersistableBundle EMPTY;
 
     static {
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 18dca68..1337d55 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -33,22 +33,21 @@
     private final double[] mPowerComponents;
     private final long[] mTimeComponents;
     private final int mCustomPowerComponentCount;
-    private final int mModeledPowerComponentOffset;
 
     PowerComponents(@NonNull Builder builder) {
-        mTotalPowerConsumed = builder.mTotalPowerConsumed;
         mCustomPowerComponentCount = builder.mCustomPowerComponentCount;
-        mModeledPowerComponentOffset = builder.mModeledPowerComponentOffset;
         mPowerComponents = builder.mPowerComponents;
         mTimeComponents = builder.mTimeComponents;
+        double totalPower = 0;
+        for (int i = mPowerComponents.length - 1; i >= 0; i--) {
+            totalPower += mPowerComponents[i];
+        }
+        mTotalPowerConsumed = totalPower;
     }
 
     PowerComponents(@NonNull Parcel source) {
         mTotalPowerConsumed = source.readDouble();
         mCustomPowerComponentCount = source.readInt();
-        mModeledPowerComponentOffset =
-                BatteryConsumer.POWER_COMPONENT_COUNT + mCustomPowerComponentCount
-                        - BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID;
         mPowerComponents = source.createDoubleArray();
         mTimeComponents = source.createLongArray();
     }
@@ -102,14 +101,6 @@
                 throw new IllegalArgumentException(
                         "Unsupported custom power component ID: " + componentId);
             }
-        } else if (componentId >= BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                && componentId < BatteryConsumer.LAST_MODELED_POWER_COMPONENT_ID) {
-            try {
-                return mPowerComponents[mModeledPowerComponentOffset + componentId];
-            } catch (ArrayIndexOutOfBoundsException e) {
-                throw new IllegalArgumentException(
-                        "Unsupported modeled power component ID: " + componentId);
-            }
         } else {
             throw new IllegalArgumentException(
                     "Unsupported custom power component ID: " + componentId);
@@ -158,38 +149,20 @@
      * Builder for PowerComponents.
      */
     static final class Builder {
-        private double mTotalPowerConsumed;
         private final double[] mPowerComponents;
         private final int mCustomPowerComponentCount;
         private final long[] mTimeComponents;
-        private final int mModeledPowerComponentOffset;
 
-        Builder(int customPowerComponentCount, int customTimeComponentCount,
-                boolean includeModeledPowerComponents) {
+        Builder(int customPowerComponentCount, int customTimeComponentCount) {
             mCustomPowerComponentCount = customPowerComponentCount;
             int powerComponentCount =
                     BatteryConsumer.POWER_COMPONENT_COUNT + customPowerComponentCount;
-            if (includeModeledPowerComponents) {
-                powerComponentCount += BatteryConsumer.POWER_COMPONENT_COUNT;
-            }
             mPowerComponents = new double[powerComponentCount];
-            mModeledPowerComponentOffset =
-                    BatteryConsumer.POWER_COMPONENT_COUNT + mCustomPowerComponentCount
-                            - BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID;
             mTimeComponents =
                     new long[BatteryConsumer.TIME_COMPONENT_COUNT + customTimeComponentCount];
         }
 
         /**
-         * Sets the sum amount of power consumed since BatteryStats reset.
-         */
-        @NonNull
-        public Builder setTotalPowerConsumed(double totalPowerConsumed) {
-            mTotalPowerConsumed = totalPowerConsumed;
-            return this;
-        }
-
-        /**
          * Sets the amount of drain attributed to the specified drain type, e.g. CPU, WiFi etc.
          *
          * @param componentId    The ID of the power component, e.g.
@@ -228,14 +201,6 @@
                     throw new IllegalArgumentException(
                             "Unsupported custom power component ID: " + componentId);
                 }
-            } else if (componentId >= BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                    && componentId < BatteryConsumer.LAST_MODELED_POWER_COMPONENT_ID) {
-                try {
-                    mPowerComponents[mModeledPowerComponentOffset + componentId] = componentPower;
-                } catch (ArrayIndexOutOfBoundsException e) {
-                    throw new IllegalArgumentException(
-                            "Unsupported modeled power component ID: " + componentId);
-                }
             } else {
                 throw new IllegalArgumentException(
                         "Unsupported custom power component ID: " + componentId);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 9a102a7..059e932 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -416,9 +416,21 @@
     public static final int GO_TO_SLEEP_REASON_QUIESCENT = 10;
 
     /**
+     * Go to sleep reason code: The last powered on display group has been removed.
      * @hide
      */
-    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_QUIESCENT;
+    public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED = 11;
+
+    /**
+     * Go to sleep reason code: Every display group has been turned off.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF = 12;
+
+    /**
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF;
 
     /**
      * @hide
@@ -435,6 +447,8 @@
             case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility";
             case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend";
             case GO_TO_SLEEP_REASON_INATTENTIVE: return "inattentive";
+            case GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED: return "display_group_removed";
+            case GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF: return "display_groups_turned_off";
             default: return Integer.toString(sleepReason);
         }
     }
@@ -521,6 +535,8 @@
             WAKE_REASON_WAKE_KEY,
             WAKE_REASON_WAKE_MOTION,
             WAKE_REASON_HDMI,
+            WAKE_REASON_DISPLAY_GROUP_ADDED,
+            WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface WakeReason{}
@@ -608,6 +624,18 @@
     public static final int WAKE_REASON_LID = 9;
 
     /**
+     * Wake up reason code: Waking due to display group being added.
+     * @hide
+     */
+    public static final int WAKE_REASON_DISPLAY_GROUP_ADDED = 10;
+
+    /**
+     * Wake up reason code: Waking due to display group being powered on.
+     * @hide
+     */
+    public static final int WAKE_REASON_DISPLAY_GROUP_TURNED_ON = 11;
+
+    /**
      * Convert the wake reason to a string for debugging purposes.
      * @hide
      */
@@ -623,6 +651,8 @@
             case WAKE_REASON_WAKE_MOTION: return "WAKE_REASON_WAKE_MOTION";
             case WAKE_REASON_HDMI: return "WAKE_REASON_HDMI";
             case WAKE_REASON_LID: return "WAKE_REASON_LID";
+            case WAKE_REASON_DISPLAY_GROUP_ADDED: return "WAKE_REASON_DISPLAY_GROUP_ADDED";
+            case WAKE_REASON_DISPLAY_GROUP_TURNED_ON: return "WAKE_REASON_DISPLAY_GROUP_TURNED_ON";
             default: return Integer.toString(wakeReason);
         }
     }
@@ -1253,8 +1283,15 @@
         }
     }
 
-   /**
-     * Forces the device to go to sleep.
+    /**
+     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * to turn off.
+     *
+     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * turned on it will be turned off. If all displays are off as a result of this action the
+     * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+     * default display group} is already off then nothing will happen.
+     *
      * <p>
      * Overrides all the wake locks that are held.
      * This is what happens when the power key is pressed to turn off the screen.
@@ -1277,7 +1314,14 @@
     }
 
     /**
-     * Forces the device to go to sleep.
+     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * to turn off.
+     *
+     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * turned on it will be turned off. If all displays are off as a result of this action the
+     * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+     * default display group} is already off then nothing will happen.
+     *
      * <p>
      * Overrides all the wake locks that are held.
      * This is what happens when the power key is pressed to turn off the screen.
@@ -1307,9 +1351,15 @@
     }
 
     /**
-     * Forces the device to wake up from sleep.
+     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * to turn on.
+     *
+     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
+     * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+     * on then nothing will happen.
+     *
      * <p>
-     * If the device is currently asleep, wakes it up, otherwise does nothing.
      * This is what happens when the power key is pressed to turn on the screen.
      * </p><p>
      * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
@@ -1332,9 +1382,15 @@
     }
 
     /**
-     * Forces the device to wake up from sleep.
+     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * to turn on.
+     *
+     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
+     * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+     * on then nothing will happen.
+     *
      * <p>
-     * If the device is currently asleep, wakes it up, otherwise does nothing.
      * This is what happens when the power key is pressed to turn on the screen.
      * </p><p>
      * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
@@ -1361,9 +1417,13 @@
     }
 
     /**
-     * Forces the device to wake up from sleep.
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on.
+     *
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY default display} is turned off it will
+     * be turned on. Additionally, if the device is asleep it will be awoken. If the {@link
+     * android.view.Display#DEFAULT_DISPLAY default display} is already on then nothing will happen.
+     *
      * <p>
-     * If the device is currently asleep, wakes it up, otherwise does nothing.
      * This is what happens when the power key is pressed to turn on the screen.
      * </p><p>
      * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 71344f9..f853e67 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -288,6 +288,20 @@
     }
 
     /**
+     * Get service debug info.
+     * @return an array of information for each service (like listServices, but with PIDs)
+     * @hide
+     */
+    public static ServiceDebugInfo[] getServiceDebugInfo() {
+        try {
+            return getIServiceManager().getServiceDebugInfo();
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in getServiceDebugInfo", e);
+            return null;
+        }
+    }
+
+    /**
      * This is only intended to be called when the process is first being brought
      * up and bound by the activity manager. There is only one thread in the process
      * at that time, so no locking is done.
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index b70b6b5..60acc57 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -103,6 +103,10 @@
         throw new RemoteException();
     }
 
+    public ServiceDebugInfo[] getServiceDebugInfo() throws RemoteException {
+        return mServiceManager.getServiceDebugInfo();
+    }
+
     /**
      * Same as mServiceManager but used by apps.
      *
diff --git a/core/java/android/os/SystemBatteryConsumer.java b/core/java/android/os/SystemBatteryConsumer.java
index 49bf084..fc4aa93 100644
--- a/core/java/android/os/SystemBatteryConsumer.java
+++ b/core/java/android/os/SystemBatteryConsumer.java
@@ -123,8 +123,8 @@
         private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
 
         Builder(int customPowerComponentCount, int customTimeComponentCount,
-                boolean includeModeledComponents, @DrainType int drainType) {
-            super(customPowerComponentCount, customTimeComponentCount, includeModeledComponents);
+                @DrainType int drainType) {
+            super(customPowerComponentCount, customTimeComponentCount);
             mDrainType = drainType;
         }
 
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 3161766..3ffaa9e 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -98,8 +98,8 @@
         private boolean mSystemComponent;
 
         public Builder(int customPowerComponentCount, int customTimeComponentCount,
-                boolean includeModeledComponents, BatteryStats.Uid batteryStatsUid) {
-            super(customPowerComponentCount, customTimeComponentCount, includeModeledComponents);
+                BatteryStats.Uid batteryStatsUid) {
+            super(customPowerComponentCount, customTimeComponentCount);
             mBatteryStatsUid = batteryStatsUid;
             mUid = batteryStatsUid.getUid();
         }
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 7e50ebc..2119e7b 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -326,6 +326,19 @@
     }
 
     /**
+     * Returns the uid that is composed from the userHandle and the appId.
+     *
+     * @param userHandle the UserHandle to compose the uid
+     * @param appId the AppId to compose the uid
+     * @return the uid that is composed from the userHandle and the appId
+     * @hide
+     */
+    @SystemApi
+    public static int getUid(@NonNull UserHandle userHandle, @AppIdInt int appId) {
+        return getUid(userHandle.getIdentifier(), appId);
+    }
+
+    /**
      * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
      * @hide
      */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 77183ac..ea1ce37 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1694,10 +1694,13 @@
     }
 
     /**
-     * @hide
-     * @return Whether the device is running in a headless system user mode. It means the headless
-     * user (system user) runs system services and system UI, but is not associated with any real
-     * person. Secondary users can be created to be associated with real person.
+     * Checks whether the device is running in a headless system user mode.
+     *
+     * <p>Headless system user mode means the {@link #isSystemUser() system user} runs system
+     * services and some system UI, but it is not associated with any real person and additional
+     * users must be created to be associated with real persons.
+     *
+     * @return whether the device is running in a headless system user mode.
      */
     public static boolean isHeadlessSystemUserMode() {
         return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 2093077..217f178 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -268,7 +268,7 @@
     }
 
     /** @hide */
-    public String usageToString(int usage) {
+    public static String usageToString(int usage) {
         switch (usage) {
             case USAGE_UNKNOWN:
                 return "UNKNOWN";
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index 7db5a80..3fbc284 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -38,14 +38,20 @@
      * Opens or creates a storage given a target path and data loader params. Returns the storage ID.
      */
     int openStorage(in @utf8InCpp String path);
-    int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode,
-                      in IDataLoaderStatusListener statusListener,
-                      in StorageHealthCheckParams healthCheckParams,
-                      in IStorageHealthListener healthListener,
-                      in PerUidReadTimeouts[] perUidReadTimeouts);
+    int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode);
     int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
 
     /**
+     * Loops DataLoader through bind/create/start with params.
+     */
+    boolean startLoading(int storageId,
+                         in DataLoaderParamsParcel params,
+                         in IDataLoaderStatusListener statusListener,
+                         in StorageHealthCheckParams healthCheckParams,
+                         in IStorageHealthListener healthListener,
+                         in PerUidReadTimeouts[] perUidReadTimeouts);
+
+    /**
      * Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
      */
     const int BIND_TEMPORARY = 0;
@@ -101,6 +107,14 @@
     int isFileFullyLoaded(int storageId, in @utf8InCpp String path);
 
     /**
+     * Checks if all files in the storage are fully loaded.
+     * 0 - fully loaded
+     * >0 - certain pages missing
+     * <0 - -errcode
+     */
+    int isFullyLoaded(int storageId);
+
+    /**
      * Returns overall loading progress of all the files on a storage, progress value between [0,1].
      * Returns a negative value on error.
      */
@@ -113,11 +127,6 @@
     byte[] getMetadataById(int storageId, in byte[] fileId);
 
     /**
-     * Starts loading data for a storage.
-     */
-    boolean startLoading(int storageId);
-
-    /**
      * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
      */
     void deleteStorage(int storageId);
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 9fdc72b..f2fe719 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -36,9 +36,7 @@
 import android.content.Context;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.IDataLoaderStatusListener;
-import android.content.pm.IPackageLoadingProgressCallback;
 import android.content.pm.InstallationFileParcel;
-import android.text.TextUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -54,6 +52,7 @@
 
     private @NonNull final IncrementalManager mIncrementalManager;
     private @NonNull final File mStageDir;
+    private @Nullable IncrementalStorage mInheritedStorage;
     private @Nullable IncrementalStorage mDefaultStorage;
 
     /**
@@ -66,13 +65,13 @@
      */
     public static IncrementalFileStorages initialize(Context context,
             @NonNull File stageDir,
+            @Nullable File inheritedDir,
             @NonNull DataLoaderParams dataLoaderParams,
             @Nullable IDataLoaderStatusListener statusListener,
             @Nullable StorageHealthCheckParams healthCheckParams,
             @Nullable IStorageHealthListener healthListener,
             @NonNull List<InstallationFileParcel> addedFiles,
-            @NonNull PerUidReadTimeouts[] perUidReadTimeouts,
-            IPackageLoadingProgressCallback progressCallback) throws IOException {
+            @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
         // TODO(b/136132412): validity check if session should not be incremental
         IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
                 Context.INCREMENTAL_SERVICE);
@@ -81,9 +80,8 @@
             throw new IOException("Failed to obtain incrementalManager.");
         }
 
-        final IncrementalFileStorages result = new IncrementalFileStorages(stageDir,
-                incrementalManager, dataLoaderParams, statusListener, healthCheckParams,
-                healthListener, perUidReadTimeouts);
+        final IncrementalFileStorages result = new IncrementalFileStorages(stageDir, inheritedDir,
+                incrementalManager, dataLoaderParams);
         for (InstallationFileParcel file : addedFiles) {
             if (file.location == LOCATION_DATA_APP) {
                 try {
@@ -97,46 +95,45 @@
                 throw new IOException("Unknown file location: " + file.location);
             }
         }
-        // Register progress loading callback after files have been added
-        if (progressCallback != null) {
-            incrementalManager.registerLoadingProgressCallback(stageDir.getAbsolutePath(),
-                    progressCallback);
-        }
-        result.startLoading();
+        result.startLoading(dataLoaderParams, statusListener, healthCheckParams, healthListener,
+                perUidReadTimeouts);
 
         return result;
     }
 
     private IncrementalFileStorages(@NonNull File stageDir,
+            @Nullable File inheritedDir,
             @NonNull IncrementalManager incrementalManager,
-            @NonNull DataLoaderParams dataLoaderParams,
-            @Nullable IDataLoaderStatusListener statusListener,
-            @Nullable StorageHealthCheckParams healthCheckParams,
-            @Nullable IStorageHealthListener healthListener,
-            @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
+            @NonNull DataLoaderParams dataLoaderParams) throws IOException {
         try {
             mStageDir = stageDir;
             mIncrementalManager = incrementalManager;
-            if (dataLoaderParams.getComponentName().getPackageName().equals("local")) {
-                final String incrementalPath = dataLoaderParams.getArguments();
-                if (TextUtils.isEmpty(incrementalPath)) {
-                    throw new IOException("Failed to create storage: incrementalPath is empty");
+            if (inheritedDir != null && IncrementalManager.isIncrementalPath(
+                    inheritedDir.getAbsolutePath())) {
+                mInheritedStorage = mIncrementalManager.openStorage(
+                        inheritedDir.getAbsolutePath());
+                if (mInheritedStorage != null) {
+                    if (!mInheritedStorage.isFullyLoaded()) {
+                        throw new IOException("Inherited storage has missing pages.");
+                    }
+
+                    mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
+                            mInheritedStorage, IncrementalManager.CREATE_MODE_CREATE
+                                    | IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
+                    if (mDefaultStorage == null) {
+                        throw new IOException(
+                                "Couldn't create linked incremental storage at " + stageDir);
+                    }
+                    return;
                 }
-                mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
-                if (mDefaultStorage == null) {
-                    throw new IOException(
-                            "Couldn't open incremental storage at " + incrementalPath);
-                }
-                mDefaultStorage.bind(stageDir.getAbsolutePath());
-            } else {
-                mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
-                        dataLoaderParams, IncrementalManager.CREATE_MODE_CREATE
-                                | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false,
-                        statusListener, healthCheckParams, healthListener, perUidReadTimeouts);
-                if (mDefaultStorage == null) {
-                    throw new IOException(
-                            "Couldn't create incremental storage at " + stageDir);
-                }
+            }
+
+            mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
+                    dataLoaderParams, IncrementalManager.CREATE_MODE_CREATE
+                            | IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
+            if (mDefaultStorage == null) {
+                throw new IOException(
+                        "Couldn't create incremental storage at " + stageDir);
             }
         } catch (IOException e) {
             cleanUp();
@@ -155,9 +152,16 @@
     /**
      * Starts or re-starts loading of data.
      */
-    public void startLoading() throws IOException {
-        if (!mDefaultStorage.startLoading()) {
-            throw new IOException("Failed to start loading data for Incremental installation.");
+    void startLoading(
+            @NonNull DataLoaderParams dataLoaderParams,
+            @Nullable IDataLoaderStatusListener statusListener,
+            @Nullable StorageHealthCheckParams healthCheckParams,
+            @Nullable IStorageHealthListener healthListener,
+            @NonNull PerUidReadTimeouts[] perUidReadTimeouts) throws IOException {
+        if (!mDefaultStorage.startLoading(dataLoaderParams, statusListener, healthCheckParams,
+                healthListener, perUidReadTimeouts)) {
+            throw new IOException(
+                    "Failed to start or restart loading data for Incremental installation.");
         }
     }
 
@@ -169,6 +173,21 @@
     }
 
     /**
+     * Creates a hardlink from inherited storage to default.
+     */
+    public boolean makeLink(@NonNull String relativePath, @NonNull String fromBase,
+            @NonNull String toBase) throws IOException {
+        if (mInheritedStorage == null) {
+            return false;
+        }
+        final File sourcePath = new File(fromBase, relativePath);
+        final File destPath = new File(toBase, relativePath);
+        mInheritedStorage.makeLink(sourcePath.getAbsolutePath(), mDefaultStorage,
+                destPath.getAbsolutePath());
+        return true;
+    }
+
+    /**
      * Permanently disables readlogs.
      */
     public void disallowReadLogs() {
@@ -186,7 +205,6 @@
 
         try {
             mDefaultStorage.unBind(mStageDir.getAbsolutePath());
-            mDefaultStorage.unregisterLoadingProgressListener();
         } catch (IOException ignored) {
         }
         mDefaultStorage = null;
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 4b93270..0ff68fc 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -22,7 +22,6 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.DataLoaderParams;
-import android.content.pm.IDataLoaderStatusListener;
 import android.content.pm.IPackageLoadingProgressCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -95,32 +94,20 @@
      * @param params              IncrementalDataLoaderParams object to configure data loading.
      * @param createMode          Mode for opening an old Incremental File System mount or creating
      *                            a new mount.
-     * @param autoStartDataLoader Set true to immediately start data loader after creating storage.
      * @return IncrementalStorage object corresponding to the mounted directory.
      */
     @Nullable
     public IncrementalStorage createStorage(@NonNull String path,
             @NonNull DataLoaderParams params,
-            @CreateMode int createMode,
-            boolean autoStartDataLoader,
-            @Nullable IDataLoaderStatusListener statusListener,
-            @Nullable StorageHealthCheckParams healthCheckParams,
-            @Nullable IStorageHealthListener healthListener,
-            @NonNull PerUidReadTimeouts[] perUidReadTimeouts) {
+            @CreateMode int createMode) {
         Objects.requireNonNull(path);
         Objects.requireNonNull(params);
-        Objects.requireNonNull(perUidReadTimeouts);
         try {
-            final int id = mService.createStorage(path, params.getData(), createMode,
-                    statusListener, healthCheckParams, healthListener, perUidReadTimeouts);
+            final int id = mService.createStorage(path, params.getData(), createMode);
             if (id < 0) {
                 return null;
             }
-            final IncrementalStorage storage = new IncrementalStorage(mService, id);
-            if (autoStartDataLoader) {
-                storage.startLoading();
-            }
-            return storage;
+            return new IncrementalStorage(mService, id);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -281,15 +268,18 @@
      * Unbinds the target dir and deletes the corresponding storage instance.
      * Deletes the package name and associated storage id from maps.
      */
-    public void onPackageRemoved(@NonNull String codePath) {
+    public void onPackageRemoved(@NonNull File codeFile) {
         try {
+            final String codePath = codeFile.getAbsolutePath();
             final IncrementalStorage storage = openStorage(codePath);
             if (storage == null) {
                 return;
             }
             mLoadingProgressCallbacks.cleanUpCallbacks(storage);
             unregisterHealthListener(codePath);
-            mService.deleteStorage(storage.getId());
+
+            // Parent since we bind-mount a folder one level above.
+            mService.deleteBindMount(storage.getId(), codeFile.getParent());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -314,20 +304,16 @@
     }
 
     /**
-     * Called when a callback wants to stop listen to the loading progress of an installed package.
-     * Decrease the count of the callbacks on the associated to the corresponding storage.
-     * If the count becomes zero, unregister the storage listener.
+     * Called to stop all listeners from listening to loading progress of an installed package.
      * @param codePath Path of the installed package
-     * @return True if the package name and associated storage id are valid. False otherwise.
      */
-    public boolean unregisterLoadingProgressCallback(@NonNull String codePath,
-            @NonNull IPackageLoadingProgressCallback callback) {
+    public void unregisterLoadingProgressCallbacks(@NonNull String codePath) {
         final IncrementalStorage storage = openStorage(codePath);
         if (storage == null) {
             // storage does not exist, package not installed
-            return false;
+            return;
         }
-        return mLoadingProgressCallbacks.unregisterCallback(storage, callback);
+        mLoadingProgressCallbacks.cleanUpCallbacks(storage);
     }
 
     private static class LoadingProgressCallbacks extends IStorageLoadingProgressListener.Stub {
@@ -335,7 +321,6 @@
         private final SparseArray<RemoteCallbackList<IPackageLoadingProgressCallback>> mCallbacks =
                 new SparseArray<>();
 
-        // TODO(b/165841827): disable callbacks when app state changes to fully loaded
         public void cleanUpCallbacks(@NonNull IncrementalStorage storage) {
             final int storageId = storage.getId();
             final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage;
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index 5b688bb..e6ce8cd 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -18,11 +18,14 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.DataLoaderParams;
+import android.content.pm.IDataLoaderStatusListener;
 import android.os.RemoteException;
 
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -323,6 +326,24 @@
         }
     }
 
+
+    /**
+     * Checks if all files in the storage are fully loaded.
+     */
+    public boolean isFullyLoaded() throws IOException {
+        try {
+            final int res = mService.isFullyLoaded(mId);
+            if (res < 0) {
+                throw new IOException(
+                        "isFullyLoaded() failed at querying loading progress, errno " + -res);
+            }
+            return res == 0;
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
+
     /**
      * Returns the loading progress of a storage
      *
@@ -376,13 +397,21 @@
     }
 
     /**
-     * Informs the data loader service associated with the current storage to start data loader
-     *
-     * @return True if data loader is successfully started.
+     * Iinitializes and starts the DataLoader.
+     * This makes sure all install-time parameters are applied.
+     * Does not affect persistent DataLoader params.
+     * @return True if start request was successfully queued.
      */
-    public boolean startLoading() {
+    public boolean startLoading(
+            @NonNull DataLoaderParams dataLoaderParams,
+            @Nullable IDataLoaderStatusListener statusListener,
+            @Nullable StorageHealthCheckParams healthCheckParams,
+            @Nullable IStorageHealthListener healthListener,
+            @NonNull PerUidReadTimeouts[] perUidReadTimeouts) {
+        Objects.requireNonNull(perUidReadTimeouts);
         try {
-            return mService.startLoading(mId);
+            return mService.startLoading(mId, dataLoaderParams.getData(), statusListener,
+                    healthCheckParams, healthListener, perUidReadTimeouts);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
             return false;
diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS
index 7e17a08..ff126e1 100644
--- a/core/java/android/os/storage/OWNERS
+++ b/core/java/android/os/storage/OWNERS
@@ -1,10 +1,10 @@
 # Bug component: 95221
 
-narayan@google.com
-nandana@google.com
 corinac@google.com
+nandana@google.com
 zezeozue@google.com
 maco@google.com
 sahanas@google.com
 abkaur@google.com
 chiangi@google.com
+narayan@google.com
diff --git a/core/java/android/os/strictmode/IncorrectContextUseViolation.java b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
index 647db17..11d26ca 100644
--- a/core/java/android/os/strictmode/IncorrectContextUseViolation.java
+++ b/core/java/android/os/strictmode/IncorrectContextUseViolation.java
@@ -16,19 +16,20 @@
 
 package android.os.strictmode;
 
+import android.annotation.NonNull;
 import android.content.Context;
 
 /**
- * Incorrect usage of {@link Context}, such as obtaining a visual service from non-visual
- * {@link Context} instance.
+ * Incorrect usage of {@link Context}, such as obtaining a UI service from non-UI {@link Context}
+ * instance.
+ *
  * @see Context#getSystemService(String)
- * @see Context#getDisplayNoVerify()
- * @hide
+ * @see Context#isUiContext(Context)
+ * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
  */
 public final class IncorrectContextUseViolation extends Violation {
 
-    /** @hide */
-    public IncorrectContextUseViolation(String message, Throwable originStack) {
+    public IncorrectContextUseViolation(@NonNull String message, @NonNull Throwable originStack) {
         super(message);
         initCause(originStack);
     }
diff --git a/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java
index 891fb59..f0f3cef 100644
--- a/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java
+++ b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java
@@ -17,10 +17,13 @@
 package android.os.strictmode;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.net.Uri;
 
+import java.util.Objects;
+
 /**
  * Violation raised when your app launches an {@link Intent} which originated
  * from outside your app.
@@ -46,8 +49,20 @@
  * not protected, your app is likely vulnerable to malicious apps.
  */
 public final class UnsafeIntentLaunchViolation extends Violation {
-    /** @hide */
+    private transient Intent mIntent;
+
     public UnsafeIntentLaunchViolation(@NonNull Intent intent) {
         super("Launch of unsafe intent: " + intent);
+        mIntent = Objects.requireNonNull(intent);
+    }
+
+    /**
+     * Return the {@link Intent} which caused this violation to be raised. Note
+     * that this value is not available if this violation has been serialized
+     * since intents cannot be serialized.
+     */
+    @SuppressWarnings("IntentBuilderName")
+    public @Nullable Intent getIntent() {
+        return mIntent;
     }
 }
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index db55e1c..85e9fdb 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -28,7 +28,10 @@
 import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
 import static android.app.AppOpsManager.opToPermission;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
+import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
@@ -41,12 +44,14 @@
 import android.content.pm.ResolveInfo;
 import android.icu.text.ListFormatter;
 import android.location.LocationManager;
+import android.media.AudioManager;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.speech.RecognitionService;
 import android.speech.RecognizerIntent;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.view.inputmethod.InputMethodInfo;
@@ -75,6 +80,9 @@
     private static final String PROPERTY_LOCATION_INDICATORS_ENABLED =
             "location_indicators_enabled";
 
+    /** Whether to show the Permissions Hub.  */
+    private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled";
+
     /** How long after an access to show it as "recent" */
     private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms";
 
@@ -84,19 +92,25 @@
     /** The name of the expected voice IME subtype */
     private static final String VOICE_IME_SUBTYPE = "voice";
 
+    private static final String SYSTEM_PKG = "android";
+
     private static final long DEFAULT_RUNNING_TIME_MS = 5000L;
-    private static final long DEFAULT_RECENT_TIME_MS = 30000L;
+    private static final long DEFAULT_RECENT_TIME_MS = 15000L;
+
+    private static boolean shouldShowPermissionsHub() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_PERMISSIONS_HUB_2_ENABLED, false);
+    }
 
     private static boolean shouldShowIndicators() {
-        return true;
-        // TODO ntmyren: remove true set when device config is configured correctly
-        //DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-        //PROPERTY_CAMERA_MIC_ICONS_ENABLED, true);
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_CAMERA_MIC_ICONS_ENABLED, true) || shouldShowPermissionsHub();
     }
 
     private static boolean shouldShowLocationIndicator() {
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
-                PROPERTY_LOCATION_INDICATORS_ENABLED, false);
+                PROPERTY_LOCATION_INDICATORS_ENABLED, false)
+                || shouldShowPermissionsHub();
     }
 
     private static long getRecentThreshold(Long now) {
@@ -115,7 +129,7 @@
     );
 
     private static final List<String> MIC_OPS = List.of(
-            OPSTR_PHONE_CALL_CAMERA,
+            OPSTR_PHONE_CALL_MICROPHONE,
             OPSTR_RECORD_AUDIO
     );
 
@@ -142,7 +156,7 @@
     }
 
     private Context mContext;
-    private Map<UserHandle, Context> mUserContexts;
+    private ArrayMap<UserHandle, Context> mUserContexts;
     private PackageManager mPkgManager;
     private AppOpsManager mAppOpsManager;
 
@@ -154,7 +168,8 @@
         mContext = context;
         mPkgManager = context.getPackageManager();
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
-        mUserContexts = Map.of(Process.myUserHandle(), mContext);
+        mUserContexts = new ArrayMap<>();
+        mUserContexts.put(Process.myUserHandle(), mContext);
     }
 
     private Context getUserContext(UserHandle user) {
@@ -164,6 +179,13 @@
         return mUserContexts.get(user);
     }
 
+    // TODO ntmyren: Replace this with better check if this moves beyond teamfood
+    private boolean isAppPredictor(String packageName, UserHandle user) {
+        return shouldShowPermissionsHub() && getUserContext(user).getPackageManager()
+                .checkPermission(Manifest.permission.MANAGE_APP_PREDICTIONS, packageName)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     /**
      * @see PermissionManager.getIndicatorAppOpUsageData
      */
@@ -187,7 +209,28 @@
         Map<PackageAttribution, CharSequence> packagesWithAttributionLabels =
                 getTrustedAttributions(rawUsages.get(MICROPHONE), proxyChains);
 
-        List<String> usedPermGroups = new ArrayList<>(rawUsages.keySet());
+        ArrayList<String> usedPermGroups = new ArrayList<>(rawUsages.keySet());
+
+        // If we have a phone call, and a carrier privileged app using microphone, hide the
+        // phone call.
+        AudioManager audioManager = mContext.getSystemService(AudioManager.class);
+        boolean hasPhoneCall = usedPermGroups.contains(OPSTR_PHONE_CALL_CAMERA)
+                || usedPermGroups.contains(OPSTR_PHONE_CALL_MICROPHONE);
+        if (hasPhoneCall && usedPermGroups.contains(MICROPHONE) && audioManager.getMode()
+                == MODE_IN_COMMUNICATION) {
+            TelephonyManager telephonyManager =
+                    mContext.getSystemService(TelephonyManager.class);
+            List<OpUsage> permUsages = rawUsages.get(MICROPHONE);
+            for (int usageNum = 0; usageNum < permUsages.size(); usageNum++) {
+                if (telephonyManager.checkCarrierPrivilegesForPackage(
+                        permUsages.get(usageNum).packageName)
+                        == CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                    usedPermGroups.remove(OPSTR_PHONE_CALL_CAMERA);
+                    usedPermGroups.remove(OPSTR_PHONE_CALL_MICROPHONE);
+                }
+            }
+        }
+
         for (int permGroupNum = 0; permGroupNum < usedPermGroups.size(); permGroupNum++) {
             boolean isPhone = false;
             String permGroup = usedPermGroups.get(permGroupNum);
@@ -270,8 +313,11 @@
                     if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) {
                         continue;
                     }
-                    if (!isUserSensitive(packageName, user, op)
-                            && !isLocationProvider(packageName, user)) {
+
+                    if (packageName.equals(SYSTEM_PKG)
+                            || (!isUserSensitive(packageName, user, op)
+                            && !isLocationProvider(packageName, user)
+                            && !isAppPredictor(packageName, user))) {
                         continue;
                     }
 
diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md
index 4224b7a..dfe748b 100644
--- a/core/java/android/permission/Permissions.md
+++ b/core/java/android/permission/Permissions.md
@@ -809,6 +809,9 @@
 permissions. It is unlikely that 3rd party apps will be able to use APIs protected by signature
 permissions as they are usually not signed with the platform certificate.
 
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
 Such permissions are defined and checked like an install time permission.
 
 ### Preinstalled permissions
@@ -819,6 +822,9 @@
 Hence this permission level is discouraged unless there are
 [further restrictions](#restricted-by-tests).
 
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
 Such permissions are defined and checked like an install time permission.
 
 ### Privileged permissions
@@ -833,6 +839,9 @@
 Hence this permission level is discouraged unless there are
 [further restrictions](#restricted-by-tests).
 
+If possible, [role protected permissions](#role-protected-permissions) should also be considered as
+an alternative to better restrict which apps may get the permission.
+
 Such permissions are defined and checked like an install time permission.
 
 #### Restricted by tests
@@ -890,8 +899,16 @@
 Which apps qualify for such a permission level is flexible and custom for each such level. Usually
 they refer to a single or small set of apps, usually - but not always - apps defined in AOSP.
 
+This type of permission is deprecated in favor of
+[role protected permissions](#role-protected-permissions).
+
 These permissions are defined and checked like an install time permission.
 
+### Role protected permissions
+
+See
+[Using role for permission protection](../../../../../../packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md).
+
 ### Development permissions
 
 > Not recommended
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 33e33ee..9bfd75e 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -249,11 +249,13 @@
             // Nasty casework for the shadow calllog begins...
             // First see if we're just inserting for one user. If so, insert into the shadow
             // based on whether that user is unlocked.
-            if (user != null) {
-                Uri baseUri = userManager.isUserUnlocked(user) ? CALL_COMPOSER_PICTURE_URI
+            UserHandle realUser = UserHandle.CURRENT.equals(user)
+                    ? android.os.Process.myUserHandle() : user;
+            if (realUser != null) {
+                Uri baseUri = userManager.isUserUnlocked(realUser) ? CALL_COMPOSER_PICTURE_URI
                         : SHADOW_CALL_COMPOSER_PICTURE_URI;
                 Uri pictureInsertionUri = ContentProvider.maybeAddUserId(baseUri,
-                        user.getIdentifier());
+                        realUser.getIdentifier());
                 Log.i(LOG_TAG, "Inserting call composer for single user at "
                         + pictureInsertionUri);
 
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index a29b4b9..91e091c 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -368,6 +368,14 @@
     public static final String NAMESPACE_SYSTEMUI = "systemui";
 
     /**
+     * Namespace for system time and time zone detection related features / behavior.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_SYSTEM_TIME = "system_time";
+
+    /**
      * Telephony related properties.
      *
      * @hide
diff --git a/core/java/android/provider/OWNERS b/core/java/android/provider/OWNERS
index cb1509a..cb06515 100644
--- a/core/java/android/provider/OWNERS
+++ b/core/java/android/provider/OWNERS
@@ -1,5 +1,6 @@
 per-file *BlockedNumber* = file:/telephony/OWNERS
 per-file *Telephony* = file:/telephony/OWNERS
+per-file *SimPhonebook* = file:/telephony/OWNERS
 
 per-file *CallLog* = file:platform/packages/providers/ContactsProvider:/OWNERS
 per-file *Contacts* = file:platform/packages/providers/ContactsProvider:/OWNERS
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 79f055e..9603f4d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5931,6 +5931,13 @@
         public static final String ADAPTIVE_SLEEP = "adaptive_sleep";
 
         /**
+         * Setting key to indicate whether camera-based autorotate is enabled.
+         *
+         * @hide
+         */
+        public static final String CAMERA_AUTOROTATE = "camera_autorotate";
+
+        /**
          * @deprecated Use {@link android.provider.Settings.Global#DEVELOPMENT_SETTINGS_ENABLED}
          * instead
          */
@@ -13393,6 +13400,24 @@
                 "euicc_factory_reset_timeout_millis";
 
         /**
+         * Flag to set the waiting time for euicc slot switch.
+         * Type: long
+         *
+         * @hide
+         */
+        public static final String EUICC_SWITCH_SLOT_TIMEOUT_MILLIS =
+                "euicc_switch_slot_timeout_millis";
+
+        /**
+         * Flag to set the waiting time for enabling multi SIM slot.
+         * Type: long
+         *
+         * @hide
+         */
+        public static final String ENABLE_MULTI_SLOT_TIMEOUT_MILLIS =
+                "enable_multi_slot_timeout_millis";
+
+        /**
          * Flag to set the timeout for when to refresh the storage settings cached data.
          * Type: long
          *
@@ -13555,6 +13580,28 @@
         public static final String POWER_BUTTON_VERY_LONG_PRESS =
                 "power_button_very_long_press";
 
+
+        /**
+         * Keyguard should be on the left hand side of the screen, for wide screen layouts.
+         *
+         * @hide
+         */
+        public static final int ONE_HANDED_KEYGUARD_SIDE_LEFT = 0;
+
+        /**
+         * Keyguard should be on the right hand side of the screen, for wide screen layouts.
+         *
+         * @hide
+         */
+        public static final int ONE_HANDED_KEYGUARD_SIDE_RIGHT = 1;
+        /**
+         * In one handed mode, which side the keyguard should be on. Allowable values are one of
+         * the ONE_HANDED_KEYGUARD_SIDE_* constants.
+         *
+         * @hide
+         */
+        public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side";
+
         /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
@@ -14721,9 +14768,8 @@
          *     touch, allow the UID to propagate the touch.
          * </ul>
          *
-         * @see android.hardware.input.InputManager#getMaximumObscuringOpacityForTouch(Context)
-         * @see android.hardware.input.InputManager#setMaximumObscuringOpacityForTouch(Context,
-         * float)
+         * @see android.hardware.input.InputManager#getMaximumObscuringOpacityForTouch()
+         * @see android.hardware.input.InputManager#setMaximumObscuringOpacityForTouch(float)
          *
          * @hide
          */
diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java
new file mode 100644
index 0000000..2efc212
--- /dev/null
+++ b/core/java/android/provider/SimPhonebookContract.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2020 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 android.provider;
+
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN_PATH_SEGMENT;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN_PATH_SEGMENT;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN;
+import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN_PATH_SEGMENT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The contract between the provider of contact records on the device's SIM cards and applications.
+ * Contains definitions of the supported URIs and columns.
+ *
+ * <p>This content provider does not support any of the QUERY_ARG_SQL* bundle arguments. An
+ * IllegalArgumentException will be thrown if these are included.
+ */
+public final class SimPhonebookContract {
+
+    /** The authority for the SIM phonebook provider. */
+    public static final String AUTHORITY = "com.android.simphonebook";
+    /** The content:// style uri to the authority for the SIM phonebook provider. */
+    @NonNull
+    public static final Uri AUTHORITY_URI = Uri.parse("content://com.android.simphonebook");
+    /**
+     * The Uri path element used to indicate that the following path segment is a subscription ID
+     * for the SIM card that will be operated on.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";
+
+    private SimPhonebookContract() {
+    }
+
+    /**
+     * Returns the Uri path segment used to reference the specified elementary file type for Uris
+     * returned by this API.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public static String getEfUriPath(@ElementaryFiles.EfType int efType) {
+        switch (efType) {
+            case EF_ADN:
+                return EF_ADN_PATH_SEGMENT;
+            case EF_FDN:
+                return EF_FDN_PATH_SEGMENT;
+            case EF_SDN:
+                return EF_SDN_PATH_SEGMENT;
+            default:
+                throw new IllegalArgumentException("Unsupported EfType " + efType);
+        }
+    }
+
+    /** Constants for the contact records on a SIM card. */
+    public static final class SimRecords {
+
+        /**
+         * The subscription ID of the SIM the record is from.
+         *
+         * @see SubscriptionInfo#getSubscriptionId()
+         */
+        public static final String SUBSCRIPTION_ID = "subscription_id";
+        /**
+         * The type of the elementary file the record is from.
+         *
+         * @see ElementaryFiles#EF_ADN
+         * @see ElementaryFiles#EF_FDN
+         * @see ElementaryFiles#EF_SDN
+         */
+        public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type";
+        /**
+         * The 1-based offset of the record in the elementary file that contains it.
+         *
+         * <p>This can be used to access individual SIM records by appending it to the
+         * elementary file URIs but it is not like a normal database ID because it is not
+         * auto-incrementing and it is not unique across SIM cards or elementary files. Hence, care
+         * should be taken when using it to ensure that it is applied to the correct SIM and EF.
+         *
+         * @see #getItemUri(int, int, int)
+         */
+        public static final String RECORD_NUMBER = "record_number";
+        /**
+         * The name for this record.
+         *
+         * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this
+         * exceeds the maximum supported length or contains unsupported characters.
+         * {@link #validateName(ContentResolver, int, int, String)} )} can be used to
+         * check whether the name is supported.
+         *
+         * @see ElementaryFiles#NAME_MAX_LENGTH
+         * @see #validateName(ContentResolver, int, int, String) )
+         */
+        public static final String NAME = "name";
+        /**
+         * The phone number for this record.
+         *
+         * <p>Only dialable characters are supported.
+         *
+         * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this
+         * exceeds the maximum supported length or contains unsupported characters.
+         *
+         * @see ElementaryFiles#PHONE_NUMBER_MAX_LENGTH
+         * @see android.telephony.PhoneNumberUtils#isDialable(char)
+         */
+        public static final String PHONE_NUMBER = "phone_number";
+
+        /** The MIME type of a CONTENT_URI subdirectory of a single SIM record. */
+        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2";
+        /** The MIME type of CONTENT_URI providing a directory of SIM records. */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2";
+
+        /**
+         * The path segment that is appended to {@link #getContentUri(int, int)} which indicates
+         * that the following path segment contains a name to be validated.
+         *
+         * @hide
+         * @see #validateName(ContentResolver, int, int, String)
+         */
+        @SystemApi
+        public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name";
+
+        /**
+         * The key for a cursor extra that contains the result of a validate name query.
+         *
+         * @hide
+         * @see #validateName(ContentResolver, int, int, String)
+         */
+        @SystemApi
+        public static final String EXTRA_NAME_VALIDATION_RESULT =
+                "android.provider.extra.NAME_VALIDATION_RESULT";
+
+
+        /**
+         * Key for the PIN2 needed to modify FDN record that should be passed in the Bundle
+         * passed to {@link ContentResolver#insert(Uri, ContentValues, Bundle)},
+         * {@link ContentResolver#update(Uri, ContentValues, Bundle)}
+         * and {@link ContentResolver#delete(Uri, Bundle)}.
+         *
+         * <p>Modifying FDN records also requires either
+         * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
+         * {@link TelephonyManager#hasCarrierPrivileges()}
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2";
+
+        private SimRecords() {
+        }
+
+        /**
+         * Returns the content Uri for the specified elementary file on the specified SIM.
+         *
+         * <p>When queried this Uri will return all of the contact records in the specified
+         * elementary file on the specified SIM. The available subscriptionIds and efTypes can
+         * be discovered by querying {@link ElementaryFiles#CONTENT_URI}.
+         *
+         * <p>If a SIM with the provided subscription ID does not exist or the SIM with the provided
+         * subscription ID doesn't support the specified entity file then queries will return
+         * and empty cursor and inserts will throw an {@link IllegalArgumentException}
+         *
+         * @param subscriptionId the subscriptionId of the SIM card that this Uri will reference
+         * @param efType         the elementary file on the SIM that this Uri will reference
+         * @see ElementaryFiles#EF_ADN
+         * @see ElementaryFiles#EF_FDN
+         * @see ElementaryFiles#EF_SDN
+         */
+        @NonNull
+        public static Uri getContentUri(int subscriptionId, @ElementaryFiles.EfType int efType) {
+            return buildContentUri(subscriptionId, efType).build();
+        }
+
+        /**
+         * Content Uri for the specific SIM record with the provided {@link #RECORD_NUMBER}.
+         *
+         * <p>When queried this will return the record identified by the provided arguments.
+         *
+         * <p>For a non-existent record:
+         * <ul>
+         *     <li>query will return an empty cursor</li>
+         *     <li>update will return 0</li>
+         *     <li>delete will return 0</li>
+         * </ul>
+         *
+         * @param subscriptionId the subscription ID of the SIM containing the record. If no SIM
+         *                       with this subscription ID exists then it will be treated as a
+         *                       non-existent record
+         * @param efType         the elementary file type containing the record. If the specified
+         *                       SIM doesn't support this elementary file then it will be treated
+         *                       as a non-existent record.
+         * @param recordNumber   the record number of the record this Uri should reference. This
+         *                       must be greater than 0. If there is no record with this record
+         *                       number in the specified entity file then it will be treated as a
+         *                       non-existent record.
+         */
+        @NonNull
+        public static Uri getItemUri(
+                int subscriptionId, @ElementaryFiles.EfType int efType, int recordNumber) {
+            // Elementary file record indices are 1-based.
+            Preconditions.checkArgument(recordNumber > 0, "Invalid recordNumber");
+
+            return buildContentUri(subscriptionId, efType)
+                    .appendPath(String.valueOf(recordNumber))
+                    .build();
+        }
+
+        /**
+         * Validates a value that is being provided for the {@link #NAME} column.
+         *
+         * <p>The return value can be used to check if the name is valid. If it is not valid then
+         * inserts and updates to the specified elementary file that use the provided name value
+         * will throw an {@link IllegalArgumentException}.
+         *
+         * <p>If the specified SIM or elementary file don't exist then
+         * {@link NameValidationResult#getMaxEncodedLength()} will be zero and
+         * {@link NameValidationResult#isValid()} will return false.
+         */
+        @NonNull
+        @WorkerThread
+        public static NameValidationResult validateName(
+                @NonNull ContentResolver resolver, int subscriptionId,
+                @ElementaryFiles.EfType int efType,
+                @NonNull String name) {
+            Bundle queryArgs = new Bundle();
+            queryArgs.putString(SimRecords.NAME, name);
+            try (Cursor cursor =
+                         resolver.query(buildContentUri(subscriptionId, efType)
+                                 .appendPath(VALIDATE_NAME_PATH_SEGMENT)
+                                 .build(), null, queryArgs, null)) {
+                NameValidationResult result = cursor.getExtras()
+                        .getParcelable(EXTRA_NAME_VALIDATION_RESULT);
+                return result != null ? result : new NameValidationResult(name, "", 0, 0);
+            }
+        }
+
+        private static Uri.Builder buildContentUri(
+                int subscriptionId, @ElementaryFiles.EfType int efType) {
+            return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority(AUTHORITY)
+                    .appendPath(SUBSCRIPTION_ID_PATH_SEGMENT)
+                    .appendPath(String.valueOf(subscriptionId))
+                    .appendPath(getEfUriPath(efType));
+        }
+
+        /** Contains details about the validity of a value provided for the {@link #NAME} column. */
+        public static final class NameValidationResult implements Parcelable {
+
+            @NonNull
+            public static final Creator<NameValidationResult> CREATOR =
+                    new Creator<NameValidationResult>() {
+
+                        @Override
+                        public NameValidationResult createFromParcel(@NonNull Parcel in) {
+                            return new NameValidationResult(in);
+                        }
+
+                        @NonNull
+                        @Override
+                        public NameValidationResult[] newArray(int size) {
+                            return new NameValidationResult[size];
+                        }
+                    };
+
+            private final String mName;
+            private final String mSanitizedName;
+            private final int mEncodedLength;
+            private final int mMaxEncodedLength;
+
+            /** Creates a new instance from the provided values. */
+            public NameValidationResult(@NonNull String name, @NonNull String sanitizedName,
+                    int encodedLength, int maxEncodedLength) {
+                this.mName = Objects.requireNonNull(name);
+                this.mSanitizedName = Objects.requireNonNull(sanitizedName);
+                this.mEncodedLength = encodedLength;
+                this.mMaxEncodedLength = maxEncodedLength;
+            }
+
+            private NameValidationResult(Parcel in) {
+                this(in.readString(), in.readString(), in.readInt(), in.readInt());
+            }
+
+            /** Returns the original name that is being validated. */
+            @NonNull
+            public String getName() {
+                return mName;
+            }
+
+            /**
+             * Returns a sanitized copy of the original name with all unsupported characters
+             * replaced with spaces.
+             */
+            @NonNull
+            public String getSanitizedName() {
+                return mSanitizedName;
+            }
+
+            /**
+             * Returns whether the original name isValid.
+             *
+             * <p>If this returns false then inserts and updates using the name will throw an
+             * {@link IllegalArgumentException}
+             */
+            public boolean isValid() {
+                return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength
+                        && Objects.equals(
+                        mName, mSanitizedName);
+            }
+
+            /** Returns whether the character at the specified position is supported by the SIM. */
+            public boolean isSupportedCharacter(int position) {
+                return mName.charAt(position) == mSanitizedName.charAt(position);
+            }
+
+            /**
+             * Returns the number of bytes required to save the name.
+             *
+             * <p>This may be more than the number of characters in the name.
+             */
+            public int getEncodedLength() {
+                return mEncodedLength;
+            }
+
+            /**
+             * Returns the maximum number of bytes that are supported for the name.
+             *
+             * @see ElementaryFiles#NAME_MAX_LENGTH
+             */
+            public int getMaxEncodedLength() {
+                return mMaxEncodedLength;
+            }
+
+            @Override
+            public int describeContents() {
+                return 0;
+            }
+
+            @Override
+            public void writeToParcel(@NonNull Parcel dest, int flags) {
+                dest.writeString(mName);
+                dest.writeString(mSanitizedName);
+                dest.writeInt(mEncodedLength);
+                dest.writeInt(mMaxEncodedLength);
+            }
+        }
+    }
+
+    /** Constants for metadata about the elementary files of the SIM cards in the phone. */
+    public static final class ElementaryFiles {
+
+        /** {@link SubscriptionInfo#getSimSlotIndex()} of the SIM for this row. */
+        public static final String SLOT_INDEX = "slot_index";
+        /** {@link SubscriptionInfo#getSubscriptionId()} of the SIM for this row. */
+        public static final String SUBSCRIPTION_ID = "subscription_id";
+        /**
+         * The elementary file type for this row.
+         *
+         * @see ElementaryFiles#EF_ADN
+         * @see ElementaryFiles#EF_FDN
+         * @see ElementaryFiles#EF_SDN
+         */
+        public static final String EF_TYPE = "ef_type";
+        /** The maximum number of records supported by the elementary file. */
+        public static final String MAX_RECORDS = "max_records";
+        /** Count of the number of records that are currently stored in the elementary file. */
+        public static final String RECORD_COUNT = "record_count";
+        /** The maximum length supported for the name of a record in the elementary file. */
+        public static final String NAME_MAX_LENGTH = "name_max_length";
+        /**
+         * The maximum length supported for the phone number of a record in the elementary file.
+         */
+        public static final String PHONE_NUMBER_MAX_LENGTH = "phone_number_max_length";
+
+        /**
+         * A value for an elementary file that is not recognized.
+         *
+         * <p>Generally this should be ignored. If new values are added then this will be used
+         * for apps that target SDKs where they aren't defined.
+         */
+        public static final int EF_UNKNOWN = 0;
+        /**
+         * Type for accessing records in the "abbreviated dialing number" (ADN) elementary file on
+         * the SIM.
+         *
+         * <p>ADN records are typically user created.
+         */
+        public static final int EF_ADN = 1;
+        /**
+         * Type for accessing records in the "fixed dialing number" (FDN) elementary file on the
+         * SIM.
+         *
+         * <p>FDN numbers are the numbers that are allowed to dialed for outbound calls when FDN is
+         * enabled.
+         *
+         * <p>FDN records cannot be modified by applications. Hence, insert, update and
+         * delete methods operating on this Uri will throw UnsupportedOperationException
+         */
+        public static final int EF_FDN = 2;
+        /**
+         * Type for accessing records in the "service dialing number" (SDN) elementary file on the
+         * SIM.
+         *
+         * <p>Typically SDNs are preset numbers provided by the carrier for common operations (e.g.
+         * voicemail, check balance, etc).
+         *
+         * <p>SDN records cannot be modified by applications. Hence, insert, update and delete
+         * methods operating on this Uri will throw UnsupportedOperationException
+         */
+        public static final int EF_SDN = 3;
+        /** @hide */
+        @SystemApi
+        public static final String EF_ADN_PATH_SEGMENT = "adn";
+        /** @hide */
+        @SystemApi
+        public static final String EF_FDN_PATH_SEGMENT = "fdn";
+        /** @hide */
+        @SystemApi
+        public static final String EF_SDN_PATH_SEGMENT = "sdn";
+        /** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file";
+        /** The MIME type of a CONTENT_URI subdirectory of a single ADN-like elementary file. */
+        public static final String CONTENT_ITEM_TYPE =
+                "vnd.android.cursor.item/sim-elementary-file";
+        /**
+         * The Uri path segment used to construct Uris for the metadata defined in this class.
+         *
+         * @hide
+         */
+        @SystemApi
+        public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files";
+
+        /** Content URI for the ADN-like elementary files available on the device. */
+        @NonNull
+        public static final Uri CONTENT_URI = AUTHORITY_URI
+                .buildUpon()
+                .appendPath(ELEMENTARY_FILES_PATH_SEGMENT).build();
+
+        private ElementaryFiles() {
+        }
+
+        /**
+         * Returns a content uri for a specific elementary file.
+         *
+         * <p>If a SIM with the specified subscriptionId is not present an exception will be thrown.
+         * If the SIM doesn't support the specified elementary file it will have a zero value for
+         * {@link #MAX_RECORDS}.
+         */
+        @NonNull
+        public static Uri getItemUri(int subscriptionId, @EfType int efType) {
+            return CONTENT_URI.buildUpon().appendPath(SUBSCRIPTION_ID_PATH_SEGMENT)
+                    .appendPath(String.valueOf(subscriptionId))
+                    .appendPath(getEfUriPath(efType))
+                    .build();
+        }
+
+        /**
+         * Annotation for the valid elementary file types.
+         *
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(
+                prefix = {"EF"},
+                value = {EF_UNKNOWN, EF_ADN, EF_FDN, EF_SDN})
+        public @interface EfType {
+        }
+    }
+}
diff --git a/core/java/android/rotationresolver/OWNERS b/core/java/android/rotationresolver/OWNERS
new file mode 100644
index 0000000..733fca9
--- /dev/null
+++ b/core/java/android/rotationresolver/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/service/rotationresolver/OWNERS
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 017f405..c39b8c5 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -80,6 +80,7 @@
     public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS =
             Tag.MIN_SECONDS_BETWEEN_OPS; // KM_UINT | 403;
     public static final int KM_TAG_MAX_USES_PER_BOOT = Tag.MAX_USES_PER_BOOT; // KM_UINT | 404;
+    public static final int KM_TAG_USAGE_COUNT_LIMIT = Tag.USAGE_COUNT_LIMIT; // KM_UINT | 405;
 
     public static final int KM_TAG_USER_ID = Tag.USER_ID; // KM_UINT | 501;
     public static final int KM_TAG_USER_SECURE_ID = Tag.USER_SECURE_ID; // KM_ULONG_REP | 502;
@@ -177,6 +178,7 @@
     public static final int KM_PURPOSE_SIGN = KeyPurpose.SIGN;
     public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY;
     public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY;
+    public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY;
 
     // Key formats.
     public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509;
diff --git a/core/java/android/service/attestation/ImpressionAttestationService.java b/core/java/android/service/attestation/ImpressionAttestationService.java
deleted file mode 100644
index 968d533..0000000
--- a/core/java/android/service/attestation/ImpressionAttestationService.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2020 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 android.service.attestation;
-
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteCallback;
-
-/**
- * A service that handles generating and verify ImpressionTokens.
- *
- * The service will generate an ImpressionToken based on arguments passed in. Then later that same
- * ImpressionToken can be verified to determine that it was created by the system.
- *
- * @hide
- */
-@SystemApi
-public abstract class ImpressionAttestationService extends Service {
-    /** @hide **/
-    public static final String EXTRA_IMPRESSION_TOKEN =
-            "android.service.attestation.extra.IMPRESSION_TOKEN";
-
-    /** @hide **/
-    public static final String EXTRA_VERIFICATION_STATUS =
-            "android.service.attestation.extra.VERIFICATION_STATUS";
-
-    /**
-     * Manifest metadata key for the resource string array containing the names of all impression
-     * attestation algorithms provided by the service.
-     *
-     * @hide
-     */
-    public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
-            "android.attestation.available_algorithms";
-
-    /**
-     * The {@link Intent} action that must be declared as handled by a service in its manifest
-     * for the system to recognize it as an impression attestation providing service.
-     *
-     * @hide
-     */
-    public static final String SERVICE_INTERFACE =
-            "android.service.attestation.ImpressionAttestationService";
-
-    private ImpressionAttestationServiceWrapper mWrapper;
-    private Handler mHandler;
-
-    public ImpressionAttestationService() {
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mWrapper = new ImpressionAttestationServiceWrapper();
-        mHandler = new Handler(Looper.getMainLooper(), null, true);
-    }
-
-    @NonNull
-    @Override
-    public final IBinder onBind(@NonNull Intent intent) {
-        return mWrapper;
-    }
-
-    /**
-     * Generates the impression token that can be used to validate that the system
-     * generated the token.
-     *
-     * @param salt          The salt to use when generating the hmac. This should be unique to the
-     *                      caller so the token cannot be verified by any other process.
-     * @param screenshot    The screenshot buffer for the content to attest.
-     * @param bounds        The size and position of the content being attested in the window.
-     * @param hashAlgorithm The String for the hashing algorithm to use based values in
-     *                      {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS)}.
-     * @return An impression token that can be used to validate information about the content.
-     * Returns null when the arguments sent are invalid.
-     */
-    @Nullable
-    public abstract ImpressionToken onGenerateImpressionToken(@NonNull byte[] salt,
-            @NonNull HardwareBuffer screenshot, @NonNull Rect bounds,
-            @NonNull String hashAlgorithm);
-
-    /**
-     * Call to verify that the impressionToken passed in was generated by the system.
-     *
-     * @param salt            The salt value to use when verifying the hmac. This should be the
-     *                        same value that was passed to
-     *                        {@link #onGenerateImpressionToken(byte[],
-     *                        HardwareBuffer, Rect, String)} to
-     *                        generate the token.
-     * @param impressionToken The token to verify that it was generated by the system.
-     * @return true if the token can be verified that it was generated by the system.
-     */
-    public abstract boolean onVerifyImpressionToken(@NonNull byte[] salt,
-            @NonNull ImpressionToken impressionToken);
-
-    private void generateImpressionToken(byte[] salt, HardwareBuffer screenshot, Rect bounds,
-            String hashAlgorithm, RemoteCallback callback) {
-        ImpressionToken impressionToken = onGenerateImpressionToken(salt, screenshot, bounds,
-                hashAlgorithm);
-        final Bundle data = new Bundle();
-        data.putParcelable(EXTRA_IMPRESSION_TOKEN, impressionToken);
-        callback.sendResult(data);
-    }
-
-    private void verifyImpressionToken(byte[] salt, ImpressionToken impressionToken,
-            RemoteCallback callback) {
-        boolean verificationStatus = onVerifyImpressionToken(salt, impressionToken);
-        final Bundle data = new Bundle();
-        data.putBoolean(EXTRA_VERIFICATION_STATUS, verificationStatus);
-        callback.sendResult(data);
-    }
-
-    private final class ImpressionAttestationServiceWrapper extends
-            IImpressionAttestationService.Stub {
-        @Override
-        public void generateImpressionToken(byte[] salt, HardwareBuffer screenshot, Rect bounds,
-                String hashAlgorithm, RemoteCallback callback) {
-            mHandler.sendMessage(
-                    obtainMessage(ImpressionAttestationService::generateImpressionToken,
-                            ImpressionAttestationService.this, salt, screenshot, bounds,
-                            hashAlgorithm, callback));
-        }
-
-        @Override
-        public void verifyImpressionToken(byte[] salt, ImpressionToken impressionToken,
-                RemoteCallback callback) {
-            mHandler.sendMessage(obtainMessage(ImpressionAttestationService::verifyImpressionToken,
-                    ImpressionAttestationService.this, salt, impressionToken, callback));
-        }
-    }
-}
diff --git a/core/java/android/service/attestation/OWNERS b/core/java/android/service/attestation/OWNERS
deleted file mode 100644
index b9e7b99..0000000
--- a/core/java/android/service/attestation/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-chaviw@google.com
-ogunwale@google.com
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 4679c56..e3d0741 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -129,14 +129,14 @@
 
     /** @hide */
     @TestApi
-    @SuppressLint("ConcreteCollection")
+    @SuppressLint({"ConcreteCollection", "NullableCollection"})
     public @Nullable ArrayList<AutofillId> getFieldIds() {
         return mFieldIds;
     }
 
     /** @hide */
     @TestApi
-    @SuppressLint("ConcreteCollection")
+    @SuppressLint({"ConcreteCollection", "NullableCollection"})
     public @Nullable ArrayList<AutofillValue> getFieldValues() {
         return mFieldValues;
     }
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index e35b8b7..ad6316c 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -119,6 +119,7 @@
                     IoUtils.closeQuietly(control.incremental.cmd);
                     IoUtils.closeQuietly(control.incremental.pendingReads);
                     IoUtils.closeQuietly(control.incremental.log);
+                    IoUtils.closeQuietly(control.incremental.blocksWritten);
                 }
             }
         }
diff --git a/core/java/android/service/dataloader/OWNERS b/core/java/android/service/dataloader/OWNERS
new file mode 100644
index 0000000..7f3906b
--- /dev/null
+++ b/core/java/android/service/dataloader/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/os/incremental/OWNERS
\ No newline at end of file
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index e0f3018..44daeff 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -46,7 +46,7 @@
     void onNotificationChannelGroupModification(String pkgName, in UserHandle user, in NotificationChannelGroup group, int modificationType);
 
     // assistants only
-    void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel);
+    void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel, in NotificationRankingUpdate update);
     void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
     void onNotificationsSeen(in List<String> keys);
     void onPanelRevealed(int items);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index cf2152c..1d49a72 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -126,7 +126,7 @@
      * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel)}.</p>
      *
      * @param sbn the new notification
-     * @return an adjustment or null to take no action, within 100ms.
+     * @return an adjustment or null to take no action, within 200ms.
      */
     abstract public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn);
 
@@ -135,7 +135,7 @@
      *
      * @param sbn the new notification
      * @param channel the channel the notification was posted to
-     * @return an adjustment or null to take no action, within 100ms.
+     * @return an adjustment or null to take no action, within 200ms.
      */
     public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn,
             @NonNull NotificationChannel channel) {
@@ -143,6 +143,20 @@
     }
 
     /**
+     * A notification was posted by an app. Called before post.
+     *
+     * @param sbn the new notification
+     * @param channel the channel the notification was posted to
+     * @param rankingMap The current ranking map that can be used to retrieve ranking information
+     *                   for active notifications.
+     * @return an adjustment or null to take no action, within 200ms.
+     */
+    public @Nullable Adjustment onNotificationEnqueued(@NonNull StatusBarNotification sbn,
+            @NonNull NotificationChannel channel, @NonNull RankingMap rankingMap) {
+        return onNotificationEnqueued(sbn, channel);
+    }
+
+    /**
      * Implement this method to learn when notifications are removed, how they were interacted with
      * before removal, and why they were removed.
      * <p>
@@ -316,7 +330,7 @@
     private class NotificationAssistantServiceWrapper extends NotificationListenerWrapper {
         @Override
         public void onNotificationEnqueuedWithChannel(IStatusBarNotificationHolder sbnHolder,
-                NotificationChannel channel) {
+                NotificationChannel channel, NotificationRankingUpdate update) {
             StatusBarNotification sbn;
             try {
                 sbn = sbnHolder.get();
@@ -330,9 +344,11 @@
                 return;
             }
 
+            applyUpdateLocked(update);
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = sbn;
             args.arg2 = channel;
+            args.arg3 = getCurrentRanking();
             mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_ENQUEUED,
                     args).sendToTarget();
         }
@@ -472,8 +488,9 @@
                     SomeArgs args = (SomeArgs) msg.obj;
                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
                     NotificationChannel channel = (NotificationChannel) args.arg2;
+                    RankingMap ranking = (RankingMap) args.arg3;
                     args.recycle();
-                    Adjustment adjustment = onNotificationEnqueued(sbn, channel);
+                    Adjustment adjustment = onNotificationEnqueued(sbn, channel, ranking);
                     setAdjustmentIssuer(adjustment);
                     if (adjustment != null) {
                         if (!isBound()) {
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index c41e599..64cddc3 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1431,7 +1431,8 @@
 
         @Override
         public void onNotificationEnqueuedWithChannel(
-                IStatusBarNotificationHolder notificationHolder, NotificationChannel channel)
+                IStatusBarNotificationHolder notificationHolder, NotificationChannel channel,
+                NotificationRankingUpdate update)
                 throws RemoteException {
             // no-op in the listener
         }
diff --git a/core/java/android/service/resumeonreboot/ResumeOnRebootService.java b/core/java/android/service/resumeonreboot/ResumeOnRebootService.java
index 4ebaa96..ad49ffd 100644
--- a/core/java/android/service/resumeonreboot/ResumeOnRebootService.java
+++ b/core/java/android/service/resumeonreboot/ResumeOnRebootService.java
@@ -87,7 +87,9 @@
      * Implementation for wrapping the opaque blob used for resume-on-reboot prior to
      * reboot. The service should not assume any structure of the blob to be wrapped. The
      * implementation should wrap the opaque blob in a reasonable time or throw {@link IOException}
-     * if it's unable to complete the action.
+     * if it's unable to complete the action due to retry-able errors (e.g network errors)
+     * and {@link IllegalArgumentException} if {@code wrapBlob} fails due to fatal errors
+     * (e.g corrupted blob).
      *
      * @param blob             The opaque blob with size on the order of 100 bytes.
      * @param lifeTimeInMillis The life time of the blob. This must be strictly enforced by the
@@ -95,7 +97,8 @@
      *                         this function after expiration should
      *                         fail.
      * @return Wrapped blob to be persisted across reboot with size on the order of 100 bytes.
-     * @throws IOException if the implementation is unable to wrap the blob successfully.
+     * @throws IOException if the implementation is unable to wrap the blob successfully due to
+     * retry-able errors.
      */
     @NonNull
     public abstract byte[] onWrap(@NonNull byte[] blob, @DurationMillisLong long lifeTimeInMillis)
@@ -106,12 +109,13 @@
      * operation would happen after reboot during direct boot mode (i.e before device is unlocked
      * for the first time). The implementation should unwrap the wrapped blob in a reasonable time
      * and returns the result or throw {@link IOException} if it's unable to complete the action
-     * and {@link IllegalArgumentException} if {@code unwrapBlob} fails because the wrappedBlob is
-     * stale.
+     * due to retry-able errors (e.g network error) and {@link IllegalArgumentException}
+     * if {@code unwrapBlob} fails due to fatal errors (e.g stale or corrupted blob).
      *
      * @param wrappedBlob The wrapped blob with size on the order of 100 bytes.
      * @return Unwrapped blob used for resume-on-reboot with the size on the order of 100 bytes.
-     * @throws IOException if the implementation is unable to unwrap the wrapped blob successfully.
+     * @throws IOException if the implementation is unable to unwrap the wrapped blob successfully
+     * due to retry-able errors.
      */
     @NonNull
     public abstract byte[] onUnwrap(@NonNull byte[] wrappedBlob) throws IOException;
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
new file mode 100644
index 0000000..e381d17
--- /dev/null
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 814982
+
+asalo@google.com
+augale@google.com
+bquezada@google.com
+eejiang@google.com
+payamp@google.com
+siddikap@google.com
+svetoslavganov@google.com
+tgadh@google.com
diff --git a/core/java/android/service/rotationresolver/RotationResolutionRequest.java b/core/java/android/service/rotationresolver/RotationResolutionRequest.java
index 94a6052..8e76e2f 100644
--- a/core/java/android/service/rotationresolver/RotationResolutionRequest.java
+++ b/core/java/android/service/rotationresolver/RotationResolutionRequest.java
@@ -20,6 +20,7 @@
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.Surface;
 
 /**
  * This class represents a request to an {@link RotationResolverService}. The request contains
@@ -54,7 +55,7 @@
         mTimeoutMillis = timeoutMillis;
     }
 
-    public int getProposedRotation() {
+    @Surface.Rotation public int getProposedRotation() {
         return mProposedRotation;
     }
 
diff --git a/core/java/android/service/rotationresolver/RotationResolverService.java b/core/java/android/service/rotationresolver/RotationResolverService.java
index 593a642..604dd0a 100644
--- a/core/java/android/service/rotationresolver/RotationResolverService.java
+++ b/core/java/android/service/rotationresolver/RotationResolverService.java
@@ -146,11 +146,8 @@
         }
         mPendingCallback = new RotationResolverCallbackWrapper(callback, this);
         mCancellationSignal = CancellationSignal.fromTransport(transport);
-        try {
-            onResolveRotation(request, mCancellationSignal, mPendingCallback);
-        } catch (UnsupportedOperationException e) {
-            reportFailures(callback, ROTATION_RESULT_FAILURE_CANCELLED);
-        }
+
+        onResolveRotation(request, mCancellationSignal, mPendingCallback);
     }
 
     @MainThread
diff --git a/core/java/android/service/attestation/IImpressionAttestationService.aidl b/core/java/android/service/screenshot/IScreenshotHasherService.aidl
similarity index 66%
rename from core/java/android/service/attestation/IImpressionAttestationService.aidl
rename to core/java/android/service/screenshot/IScreenshotHasherService.aidl
index 5ff8f17..d14d147 100644
--- a/core/java/android/service/attestation/IImpressionAttestationService.aidl
+++ b/core/java/android/service/screenshot/IScreenshotHasherService.aidl
@@ -14,43 +14,43 @@
  * limitations under the License.
  */
 
-package android.service.attestation;
+package android.service.screenshot;
 
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.os.RemoteCallback;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
 
 /**
- * Service used to handle impression attestation requests.
+ * Service used to handle ScreenshotHash requests.
  *
  * @hide
  */
-oneway interface IImpressionAttestationService {
+oneway interface IScreenshotHasherService {
     /**
-     * Generates the impression token that can be used to validate that the system generated the
+     * Generates the ScreenshotHash token that can be used to validate that the system generated the
      * token.
      *
      * @param salt The salt to use when generating the hmac. This should be unique to the caller so
      *        the token cannot be verified by any other process.
      * @param screenshot The screenshot to generate the hash and add to the token.
-     * @param bounds The size and position of the content being attested in the window.
+     * @param bounds The size and position of the content being screenshot in the window.
      * @param hashAlgorithm The String for the hashing algorithm to use based on values in
      *        {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS}.
-     * @param Callback The callback invoked to send back the impression token.
+     * @param Callback The callback invoked to send back the ScreenshotHash token.
      */
-    void generateImpressionToken(in byte[] salt, in HardwareBuffer screenshot, in Rect bounds,
+    void generateScreenshotHash(in byte[] salt, in HardwareBuffer screenshot, in Rect bounds,
                                  in String hashAlgorithm, in RemoteCallback callback);
 
     /**
-     * Call to verify that the impressionToken passed in was generated by the system. The result
+     * Call to verify that the ScreenshotHash passed in was generated by the system. The result
      * will be sent in the callback as a boolean with the key {@link #EXTRA_VERIFICATION_STATUS}.
      *
      * @param salt The salt value to use when verifying the hmac. This should be the same value that
-     *        was passed to {@link generateImpressionToken()} to generate the token.
-     * @param impressionToken The token to verify that it was generated by the system.
+     *        was passed to {@link generateScreenshotHash()} to generate the token.
+     * @param screenshotHash The hash to verify that it was generated by the system.
      * @param callback The callback invoked to send back the verification status.
      */
-    void verifyImpressionToken(in byte[] salt, in ImpressionToken impressionToken,
+    void verifyScreenshotHash(in byte[] salt, in ScreenshotHash screenshotHash,
                                in RemoteCallback callback);
 }
diff --git a/core/java/android/service/screenshot/OWNERS b/core/java/android/service/screenshot/OWNERS
new file mode 100644
index 0000000..0862c05
--- /dev/null
+++ b/core/java/android/service/screenshot/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/service/attestation/ImpressionToken.aidl b/core/java/android/service/screenshot/ScreenshotHash.aidl
similarity index 90%
rename from core/java/android/service/attestation/ImpressionToken.aidl
rename to core/java/android/service/screenshot/ScreenshotHash.aidl
index 284a4ba..a7c50db 100644
--- a/core/java/android/service/attestation/ImpressionToken.aidl
+++ b/core/java/android/service/screenshot/ScreenshotHash.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.service.attestation;
+package android.service.screenshot;
 
-parcelable ImpressionToken;
\ No newline at end of file
+parcelable ScreenshotHash;
diff --git a/core/java/android/service/attestation/ImpressionToken.java b/core/java/android/service/screenshot/ScreenshotHash.java
similarity index 81%
rename from core/java/android/service/attestation/ImpressionToken.java
rename to core/java/android/service/screenshot/ScreenshotHash.java
index 4355dc0..9ae4192 100644
--- a/core/java/android/service/attestation/ImpressionToken.java
+++ b/core/java/android/service/screenshot/ScreenshotHash.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.service.attestation;
+package android.service.screenshot;
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -25,14 +25,14 @@
 import com.android.internal.util.DataClass;
 
 /**
- * The ads impression token used to validate information about what was present on screen.
+ * The screenshot hash used to validate information about what was present on screen.
  * @hide
  *
  * TODO: Remove hide and SystemAPI since this will be a public class
  */
 @SystemApi
 @DataClass(genToString = true, genAidl = true)
-public final class ImpressionToken implements Parcelable {
+public final class ScreenshotHash implements Parcelable {
     /**
      * The timestamp when the screenshot was generated.
      */
@@ -42,23 +42,27 @@
      * The bounds of the requested area to take the screenshot. This is in window space passed in
      * by the client.
      */
-    private @NonNull final Rect mBoundsInWindow;
+    @NonNull
+    private final Rect mBoundsInWindow;
 
     /**
      * The selected hashing algorithm that generated the image hash.
      */
-    private @NonNull final String mHashingAlgorithm;
+    @NonNull
+    private final String mHashingAlgorithm;
 
     /**
-     * The image hash generated when creating the impression token from the screenshot taken.
+     * The image hash generated when creating the ScreenshotHash from the screenshot taken.
      */
-    private @NonNull final byte[] mImageHash;
+    @NonNull
+    private final byte[] mImageHash;
 
     /**
      * The hmac generated by the system and used to verify whether this token was generated by
      * the system. This should only be accessed by a system process.
      */
-    private @NonNull final byte[] mHmac;
+    @NonNull
+    private final byte[] mHmac;
 
     /**
      * The hmac generated by the system and used to verify whether this token was generated by
@@ -67,19 +71,21 @@
      * @hide
      */
     @SystemApi
-    public @NonNull byte[] getHmac() {
+    @NonNull
+    public byte[] getHmac() {
         return mHmac;
     }
 
 
 
-    // Code below generated by codegen v1.0.18.
+    // Code below generated by codegen v1.0.22.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/attestation/ImpressionToken.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/screenshot
+    // /ScreenshotHash.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -87,7 +93,7 @@
 
 
     /**
-     * Creates a new ImpressionToken.
+     * Creates a new ScreenshotHash.
      *
      * @param screenshotTimeMillis
      *   The timestamp when the screenshot was generated.
@@ -97,13 +103,13 @@
      * @param hashingAlgorithm
      *   The selected hashing algorithm that generated the image hash.
      * @param imageHash
-     *   The image hash generated when creating the impression token from the screenshot taken.
+     *   The image hash generated when creating the ScreenshotHash from the screenshot taken.
      * @param hmac
      *   The hmac generated by the system and used to verify whether this token was generated by
      *   the system. This should only be accessed by a system process.
      */
     @DataClass.Generated.Member
-    public ImpressionToken(
+    public ScreenshotHash(
             long screenshotTimeMillis,
             @NonNull Rect boundsInWindow,
             @NonNull String hashingAlgorithm,
@@ -152,7 +158,7 @@
     }
 
     /**
-     * The image hash generated when creating the impression token from the screenshot taken.
+     * The image hash generated when creating the ScreenshotHash from the screenshot taken.
      */
     @DataClass.Generated.Member
     public @NonNull byte[] getImageHash() {
@@ -165,7 +171,7 @@
         // You can override field toString logic by defining methods like:
         // String fieldNameToString() { ... }
 
-        return "ImpressionToken { " +
+        return "ScreenshotHash { " +
                 "screenshotTimeMillis = " + mScreenshotTimeMillis + ", " +
                 "boundsInWindow = " + mBoundsInWindow + ", " +
                 "hashingAlgorithm = " + mHashingAlgorithm + ", " +
@@ -194,7 +200,7 @@
     /** @hide */
     @SuppressWarnings({"unchecked", "RedundantCast"})
     @DataClass.Generated.Member
-    /* package-private */ ImpressionToken(@NonNull Parcel in) {
+    /* package-private */ ScreenshotHash(@NonNull Parcel in) {
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
@@ -222,24 +228,24 @@
     }
 
     @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<ImpressionToken> CREATOR
-            = new Parcelable.Creator<ImpressionToken>() {
+    public static final @NonNull Parcelable.Creator<ScreenshotHash> CREATOR
+            = new Parcelable.Creator<ScreenshotHash>() {
         @Override
-        public ImpressionToken[] newArray(int size) {
-            return new ImpressionToken[size];
+        public ScreenshotHash[] newArray(int size) {
+            return new ScreenshotHash[size];
         }
 
         @Override
-        public ImpressionToken createFromParcel(@NonNull Parcel in) {
-            return new ImpressionToken(in);
+        public ScreenshotHash createFromParcel(@NonNull Parcel in) {
+            return new ScreenshotHash(in);
         }
     };
 
     @DataClass.Generated(
-            time = 1604539951959L,
-            codegenVersion = "1.0.18",
-            sourceFile = "frameworks/base/core/java/android/service/attestation/ImpressionToken.java",
-            inputSignatures = "private final  long mScreenshotTimeMillis\nprivate final @android.annotation.NonNull android.graphics.Rect mBoundsInWindow\nprivate final @android.annotation.NonNull java.lang.String mHashingAlgorithm\nprivate final @android.annotation.NonNull byte[] mImageHash\nprivate final @android.annotation.NonNull byte[] mHmac\npublic @android.annotation.SystemApi @android.annotation.NonNull byte[] getHmac()\nclass ImpressionToken extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genAidl=true)")
+            time = 1612383172822L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/service/screenshot/ScreenshotHash.java",
+            inputSignatures = "private final  long mScreenshotTimeMillis\nprivate final @android.annotation.NonNull android.graphics.Rect mBoundsInWindow\nprivate final @android.annotation.NonNull java.lang.String mHashingAlgorithm\nprivate final @android.annotation.NonNull byte[] mImageHash\nprivate final @android.annotation.NonNull byte[] mHmac\npublic @android.annotation.SystemApi @android.annotation.NonNull byte[] getHmac()\nclass ScreenshotHash extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genAidl=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/screenshot/ScreenshotHasherService.java b/core/java/android/service/screenshot/ScreenshotHasherService.java
new file mode 100644
index 0000000..d96cc7e
--- /dev/null
+++ b/core/java/android/service/screenshot/ScreenshotHasherService.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 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 android.service.screenshot;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteCallback;
+
+/**
+ * A service that handles generating and verify {@link ScreenshotHash}.
+ *
+ * The service will generate a ScreenshotHash based on arguments passed in. Then later that
+ * same ScreenshotHash can be verified to determine that it was created by the system.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class ScreenshotHasherService extends Service {
+    /** @hide **/
+    public static final String EXTRA_SCREENSHOT_HASH =
+            "android.service.screenshot.extra.SCREENSHOT_HASH";
+
+    /** @hide **/
+    public static final String EXTRA_VERIFICATION_STATUS =
+            "android.service.screenshot.extra.VERIFICATION_STATUS";
+
+    /**
+     * Manifest metadata key for the resource string array containing the names of all hashing
+     * algorithms provided by the service.
+     *
+     * @hide
+     */
+    public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
+            "android.screenshot.available_algorithms";
+
+    /**
+     * The {@link Intent} action that must be declared as handled by a service in its manifest
+     * for the system to recognize it as a ScreenshotHash providing service.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String SERVICE_INTERFACE =
+            "android.service.screenshot.ScreenshotHasherService";
+
+    private ScreenshotHasherServiceWrapper mWrapper;
+    private Handler mHandler;
+
+    public ScreenshotHasherService() {
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWrapper = new ScreenshotHasherServiceWrapper();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+    }
+
+    @NonNull
+    @Override
+    public final IBinder onBind(@NonNull Intent intent) {
+        return mWrapper;
+    }
+
+    /**
+     * Generates the ScreenshotHash that can be used to validate that the system generated the
+     * token.
+     *
+     * @param salt          The salt to use when generating the hmac. This should be unique to the
+     *                      caller so the token cannot be verified by any other process.
+     * @param screenshot    The screenshot buffer for the content.
+     * @param bounds        The size and position of the content being screenshot in the window.
+     * @param hashAlgorithm The String for the hashing algorithm to use based values in
+     *                      {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS)}.
+     * @return A ScreenshotHash that can be used to validate information about the content.
+     * Returns null when the arguments sent are invalid.
+     */
+    @Nullable
+    public abstract ScreenshotHash onGenerateScreenshotHash(@NonNull byte[] salt,
+            @NonNull HardwareBuffer screenshot, @NonNull Rect bounds,
+            @NonNull String hashAlgorithm);
+
+    /**
+     * Call to verify that the ScreenshotHash passed in was generated by the system.
+     *
+     * @param salt               The salt value to use when verifying the hmac. This should be the
+     *                           same value that was passed to
+     *                           {@link #onGenerateScreenshotHash(byte[],
+     *                           HardwareBuffer, Rect, String)} to
+     *                           generate the token.
+     * @param screenshotHash The token to verify that it was generated by the system.
+     * @return true if the token can be verified that it was generated by the system.
+     */
+    public abstract boolean onVerifyScreenshotHash(@NonNull byte[] salt,
+            @NonNull ScreenshotHash screenshotHash);
+
+    private void generateScreenshotHash(byte[] salt, HardwareBuffer screenshot, Rect bounds,
+            String hashAlgorithm, RemoteCallback callback) {
+        ScreenshotHash screenshotHash = onGenerateScreenshotHash(salt, screenshot,
+                bounds,
+                hashAlgorithm);
+        final Bundle data = new Bundle();
+        data.putParcelable(EXTRA_SCREENSHOT_HASH, screenshotHash);
+        callback.sendResult(data);
+    }
+
+    private void verifyScreenshotHash(byte[] salt, ScreenshotHash screenshotHash,
+            RemoteCallback callback) {
+        boolean verificationStatus = onVerifyScreenshotHash(salt, screenshotHash);
+        final Bundle data = new Bundle();
+        data.putBoolean(EXTRA_VERIFICATION_STATUS, verificationStatus);
+        callback.sendResult(data);
+    }
+
+    private final class ScreenshotHasherServiceWrapper extends
+            IScreenshotHasherService.Stub {
+        @Override
+        public void generateScreenshotHash(byte[] salt, HardwareBuffer screenshot, Rect bounds,
+                String hashAlgorithm, RemoteCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(ScreenshotHasherService::generateScreenshotHash,
+                            ScreenshotHasherService.this, salt, screenshot, bounds,
+                            hashAlgorithm, callback));
+        }
+
+        @Override
+        public void verifyScreenshotHash(byte[] salt, ScreenshotHash screenshotHash,
+                RemoteCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(ScreenshotHasherService::verifyScreenshotHash,
+                            ScreenshotHasherService.this, salt, screenshotHash,
+                            callback));
+        }
+    }
+}
diff --git a/core/java/android/service/smartspace/ISmartspaceService.aidl b/core/java/android/service/smartspace/ISmartspaceService.aidl
new file mode 100644
index 0000000..c9c6807
--- /dev/null
+++ b/core/java/android/service/smartspace/ISmartspaceService.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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 android.service.smartspace;
+
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.ISmartspaceCallback;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * Interface from the system to Smartspace service.
+ *
+ * @hide
+ */
+oneway interface ISmartspaceService {
+
+    void onCreateSmartspaceSession(in SmartspaceConfig context, in SmartspaceSessionId sessionId);
+
+    void notifySmartspaceEvent(in SmartspaceSessionId sessionId, in SmartspaceTargetEvent event);
+
+    void requestSmartspaceUpdate(in SmartspaceSessionId sessionId);
+
+    void registerSmartspaceUpdates(in SmartspaceSessionId sessionId,
+            in ISmartspaceCallback callback);
+
+    void unregisterSmartspaceUpdates(in SmartspaceSessionId sessionId,
+            in ISmartspaceCallback callback);
+
+    void onDestroySmartspaceSession(in SmartspaceSessionId sessionId);
+}
diff --git a/core/java/android/service/smartspace/OWNERS b/core/java/android/service/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/core/java/android/service/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/core/java/android/service/smartspace/SmartspaceService.java b/core/java/android/service/smartspace/SmartspaceService.java
new file mode 100644
index 0000000..09b7310
--- /dev/null
+++ b/core/java/android/service/smartspace/SmartspaceService.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2021 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 android.service.smartspace;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTarget;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService.Stub;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A service used to share the lifecycle of smartspace UI (open, close, interaction)
+ * and also to return smartspace result on a query.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SmartspaceService extends Service {
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     *
+     * <p>The service must also require the {@link android.permission#MANAGE_SMARTSPACE}
+     * permission.
+     *
+     * @hide
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.smartspace.SmartspaceService";
+    private static final boolean DEBUG = false;
+    private static final String TAG = "SmartspaceService";
+    private final ArrayMap<SmartspaceSessionId, ArrayList<CallbackWrapper>> mSessionCallbacks =
+            new ArrayMap<>();
+    private Handler mHandler;
+
+    private final android.service.smartspace.ISmartspaceService mInterface = new Stub() {
+
+        @Override
+        public void onCreateSmartspaceSession(SmartspaceConfig smartspaceConfig,
+                SmartspaceSessionId sessionId) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doCreateSmartspaceSession,
+                            SmartspaceService.this, smartspaceConfig, sessionId));
+        }
+
+        @Override
+        public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+                SmartspaceTargetEvent event) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::notifySmartspaceEvent,
+                            SmartspaceService.this, sessionId, event));
+        }
+
+        @Override
+        public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doRequestPredictionUpdate,
+                            SmartspaceService.this, sessionId));
+        }
+
+        @Override
+        public void registerSmartspaceUpdates(SmartspaceSessionId sessionId,
+                ISmartspaceCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doRegisterSmartspaceUpdates,
+                            SmartspaceService.this, sessionId, callback));
+        }
+
+        @Override
+        public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+                ISmartspaceCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doUnregisterSmartspaceUpdates,
+                            SmartspaceService.this, sessionId, callback));
+        }
+
+        @Override
+        public void onDestroySmartspaceSession(SmartspaceSessionId sessionId) {
+
+            mHandler.sendMessage(
+                    obtainMessage(SmartspaceService::doDestroy,
+                            SmartspaceService.this, sessionId));
+        }
+    };
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.d(TAG, "onCreate mSessionCallbacks: " + mSessionCallbacks);
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+    }
+
+    @Override
+    @NonNull
+    public final IBinder onBind(@NonNull Intent intent) {
+        Log.d(TAG, "onBind mSessionCallbacks: " + mSessionCallbacks);
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Slog.w(TAG, "Tried to bind to wrong intent (should be "
+                + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+    private void doCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+            @NonNull SmartspaceSessionId sessionId) {
+        Log.d(TAG, "doCreateSmartspaceSession mSessionCallbacks: " + mSessionCallbacks);
+        mSessionCallbacks.put(sessionId, new ArrayList<>());
+        onCreateSmartspaceSession(config, sessionId);
+    }
+
+    /**
+     * Gets called when the client calls <code> SmartspaceManager#createSmartspaceSession </code>.
+     */
+    public abstract void onCreateSmartspaceSession(@NonNull SmartspaceConfig config,
+            @NonNull SmartspaceSessionId sessionId);
+
+    /**
+     * Gets called when the client calls <code> SmartspaceSession#notifySmartspaceEvent </code>.
+     */
+    @MainThread
+    public abstract void notifySmartspaceEvent(@NonNull SmartspaceSessionId sessionId,
+            @NonNull SmartspaceTargetEvent event);
+
+    /**
+     * Gets called when the client calls <code> SmartspaceSession#requestSmartspaceUpdate </code>.
+     */
+    @MainThread
+    public abstract void onRequestSmartspaceUpdate(@NonNull SmartspaceSessionId sessionId);
+
+    private void doRegisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+            @NonNull ISmartspaceCallback callback) {
+        Log.d(TAG, "doRegisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks == null) {
+            Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+            return;
+        }
+
+        final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+        if (wrapper == null) {
+            callbacks.add(new CallbackWrapper(callback,
+                    callbackWrapper ->
+                            mHandler.post(
+                                    () -> removeCallbackWrapper(callbacks, callbackWrapper))));
+        }
+    }
+
+    private void doUnregisterSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+            @NonNull ISmartspaceCallback callback) {
+        Log.d(TAG, "doUnregisterSmartspaceUpdates mSessionCallbacks: " + mSessionCallbacks);
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks == null) {
+            Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+            return;
+        }
+
+        final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+        if (wrapper != null) {
+            removeCallbackWrapper(callbacks, wrapper);
+        }
+    }
+
+    private void doRequestPredictionUpdate(@NonNull SmartspaceSessionId sessionId) {
+        Log.d(TAG, "doRequestPredictionUpdate mSessionCallbacks: " + mSessionCallbacks);
+        // Just an optimization, if there are no callbacks, then don't bother notifying the service
+        final ArrayList<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks != null && !callbacks.isEmpty()) {
+            onRequestSmartspaceUpdate(sessionId);
+        }
+    }
+
+    /**
+     * Finds the callback wrapper for the given callback.
+     */
+    private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+            ISmartspaceCallback callback) {
+        for (int i = callbacks.size() - 1; i >= 0; i--) {
+            if (callbacks.get(i).isCallback(callback)) {
+                return callbacks.get(i);
+            }
+        }
+        return null;
+    }
+
+    private void removeCallbackWrapper(
+            ArrayList<CallbackWrapper> callbacks, CallbackWrapper wrapper) {
+        if (callbacks == null) {
+            return;
+        }
+        callbacks.remove(wrapper);
+    }
+
+    /**
+     * Gets called when the client calls <code> SmartspaceManager#destroy() </code>.
+     */
+    public abstract void onDestroySmartspaceSession(@NonNull SmartspaceSessionId sessionId);
+
+    private void doDestroy(@NonNull SmartspaceSessionId sessionId) {
+        Log.d(TAG, "doDestroy mSessionCallbacks: " + mSessionCallbacks);
+        super.onDestroy();
+        mSessionCallbacks.remove(sessionId);
+        onDestroySmartspaceSession(sessionId);
+    }
+
+    /**
+     * Used by the prediction factory to send back results the client app. The can be called
+     * in response to {@link #onRequestSmartspaceUpdate(SmartspaceSessionId)} or proactively as
+     * a result of changes in predictions.
+     */
+    public final void updateSmartspaceTargets(@NonNull SmartspaceSessionId sessionId,
+            @NonNull List<SmartspaceTarget> targets) {
+        Log.d(TAG, "updateSmartspaceTargets mSessionCallbacks: " + mSessionCallbacks);
+        List<CallbackWrapper> callbacks = mSessionCallbacks.get(sessionId);
+        if (callbacks != null) {
+            for (CallbackWrapper callback : callbacks) {
+                callback.accept(targets);
+            }
+        }
+    }
+
+    /**
+     * Destroys a smartspace session.
+     */
+    @MainThread
+    public abstract void onDestroy(@NonNull SmartspaceSessionId sessionId);
+
+    private static final class CallbackWrapper implements Consumer<List<SmartspaceTarget>>,
+            IBinder.DeathRecipient {
+
+        private final Consumer<CallbackWrapper> mOnBinderDied;
+        private ISmartspaceCallback mCallback;
+
+        CallbackWrapper(ISmartspaceCallback callback,
+                @Nullable Consumer<CallbackWrapper> onBinderDied) {
+            mCallback = callback;
+            mOnBinderDied = onBinderDied;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to link to death: " + e);
+            }
+        }
+
+        public boolean isCallback(@NonNull ISmartspaceCallback callback) {
+            if (mCallback == null) {
+                Slog.e(TAG, "Callback is null, likely the binder has died.");
+                return false;
+            }
+            return mCallback.equals(callback);
+        }
+
+        @Override
+        public void accept(List<SmartspaceTarget> smartspaceTargets) {
+            try {
+                if (mCallback != null) {
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "CallbackWrapper.accept smartspaceTargets=" + smartspaceTargets);
+                    }
+                    mCallback.onResult(new ParceledListSlice(smartspaceTargets));
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error sending result:" + e);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+            mCallback = null;
+            if (mOnBinderDied != null) {
+                mOnBinderDied.accept(this);
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java
index 0123c36..a750b68 100644
--- a/core/java/android/service/storage/ExternalStorageService.java
+++ b/core/java/android/service/storage/ExternalStorageService.java
@@ -158,7 +158,7 @@
      * @param volumeUuid uuid of the {@link StorageVolume} from which cache needs to be freed
      * @param bytes number of bytes which need to be freed
      */
-    public void onFreeCacheRequested(@NonNull UUID volumeUuid, @BytesLong long bytes) {
+    public void onFreeCache(@NonNull UUID volumeUuid, @BytesLong long bytes) throws IOException {
         throw new UnsupportedOperationException("onFreeCacheRequested not implemented");
     }
 
@@ -202,7 +202,7 @@
                 RemoteCallback callback) {
             mHandler.post(() -> {
                 try {
-                    onFreeCacheRequested(StorageManager.convert(volumeUuid), bytes);
+                    onFreeCache(StorageManager.convert(volumeUuid), bytes);
                     sendResult(sessionId, null /* throwable */, callback);
                 } catch (Throwable t) {
                     sendResult(sessionId, t, callback);
diff --git a/core/java/android/service/voice/IVoiceInteractionSessionService.aidl b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
index 7f8158f..dc0718a 100644
--- a/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionSessionService.aidl
@@ -18,8 +18,6 @@
 
 import android.os.Bundle;
 
-import android.service.voice.IVoiceInteractionSession;
-
 /**
  * @hide
  */
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 68d6f3f..25f8090 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -345,7 +345,8 @@
      */
     @SystemApi
     @HotwordConfigResult
-    public final int setHotwordDetectionConfig(@Nullable Bundle options) {
+    public final int setHotwordDetectionConfig(
+            @SuppressLint("NullableCollection") @Nullable Bundle options) {
         if (mSystemService == null) {
             throw new IllegalStateException("Not available until onReady() is called");
         }
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index f7710e6..ff03cc1 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
@@ -44,6 +45,7 @@
     private ServiceInfo mServiceInfo;
     private String mSessionService;
     private String mRecognitionService;
+    private String mHotwordDetectionService;
     private String mSettingsActivity;
     private boolean mSupportsAssist;
     private boolean mSupportsLaunchFromKeyguard;
@@ -133,6 +135,8 @@
                     false);
             mSupportsLocalInteraction = array.getBoolean(com.android.internal.
                     R.styleable.VoiceInteractionService_supportsLocalInteraction, false);
+            mHotwordDetectionService = array.getString(com.android.internal.R.styleable
+                    .VoiceInteractionService_hotwordDetectionService);
             array.recycle();
             if (mSessionService == null) {
                 mParseError = "No sessionService specified";
@@ -181,4 +185,9 @@
     public boolean getSupportsLocalInteraction() {
         return mSupportsLocalInteraction;
     }
+
+    @Nullable
+    public String getHotwordDetectionService() {
+        return mHotwordDetectionService;
+    }
 }
diff --git a/core/java/android/service/voice/VoiceInteractionSessionService.java b/core/java/android/service/voice/VoiceInteractionSessionService.java
index 424ff9d..8300343 100644
--- a/core/java/android/service/voice/VoiceInteractionSessionService.java
+++ b/core/java/android/service/voice/VoiceInteractionSessionService.java
@@ -21,11 +21,14 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.Log;
+
 import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
@@ -38,6 +41,8 @@
  */
 public abstract class VoiceInteractionSessionService extends Service {
 
+    private static final String TAG = "VoiceInteractionSession";
+
     static final int MSG_NEW_SESSION = 1;
 
     IVoiceInteractionManagerService mSystemService;
@@ -120,10 +125,22 @@
             mSession = null;
         }
         mSession = onNewSession(args);
-        try {
-            mSystemService.deliverNewSession(token, mSession.mSession, mSession.mInteractor);
+        if (deliverSession(token)) {
             mSession.doCreate(mSystemService, token);
-        } catch (RemoteException e) {
+        } else {
+            // TODO(b/178777121): Add an onError() method to let the application know what happened.
+            mSession.doDestroy();
+            mSession = null;
         }
     }
+
+    private boolean deliverSession(IBinder token) {
+        try {
+            return mSystemService.deliverNewSession(token, mSession.mSession, mSession.mInteractor);
+        } catch (DeadObjectException ignored) {
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to deliver session: " + e);
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 507dc7a..82e0b4a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -880,8 +880,8 @@
                         InputChannel inputChannel = new InputChannel();
 
                         if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
-                                mDisplay.getDisplayId(), mInsetsState, mWinFrames.frame,
-                                inputChannel, mInsetsState, mTempControls) < 0) {
+                                mDisplay.getDisplayId(), mInsetsState, inputChannel, mInsetsState,
+                                mTempControls) < 0) {
                             Log.w(TAG, "Failed to add window while updating wallpaper surface.");
                             return;
                         }
@@ -1494,6 +1494,16 @@
         private void doDetachEngine() {
             mActiveEngines.remove(mEngine);
             mEngine.detach();
+            // Some wallpapers will not trigger the rendering threads of the remaining engines even
+            // if they are visible, so we need to toggle the state to get their attention.
+            if (!mDetached.get()) {
+                for (Engine eng : mActiveEngines) {
+                    if (eng.mVisible) {
+                        eng.doVisibilityChanged(false);
+                        eng.doVisibilityChanged(true);
+                    }
+                }
+            }
         }
 
         @Override
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/core/java/android/speech/IRecognitionServiceManager.aidl
similarity index 68%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to core/java/android/speech/IRecognitionServiceManager.aidl
index 8b5b251..7158ba2 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/core/java/android/speech/IRecognitionServiceManager.aidl
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
+package android.speech;
+
+import android.speech.IRecognitionServiceManagerCallback;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Binder service allowing speech recognition proxied by the system.
+ *
+ * {@hide}
  */
-@android.annotation.Hide
-package com.android.role;
+interface IRecognitionServiceManager {
+    void createSession(in IRecognitionServiceManagerCallback callback);
+}
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
similarity index 67%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to core/java/android/speech/IRecognitionServiceManagerCallback.aidl
index 8b5b251..d760810 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/core/java/android/speech/IRecognitionServiceManagerCallback.aidl
@@ -14,9 +14,16 @@
  * limitations under the License.
  */
 
+package android.speech;
+
+import android.speech.IRecognitionService;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Callback for the service allowing speech recognition proxied by the system.
+ *
+ * {@hide}
  */
-@android.annotation.Hide
-package com.android.role;
+oneway interface IRecognitionServiceManagerCallback {
+    void onSuccess(in IRecognitionService service);
+    void onError();
+}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 5fd192a..c97dbfe 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -50,7 +51,9 @@
     /**
      * Name under which a RecognitionService component publishes information about itself.
      * This meta-data should reference an XML resource containing a
-     * <code>&lt;{@link android.R.styleable#RecognitionService recognition-service}&gt;</code> tag.
+     * <code>&lt;{@link android.R.styleable#RecognitionService recognition-service}&gt;</code> or
+     * <code>&lt;{@link android.R.styleable#RecognitionService on-device-recognition-service}
+     * &gt;</code> tag.
      */
     public static final String SERVICE_META_DATA = "android.speech";
 
@@ -182,6 +185,13 @@
     private boolean checkPermissions(IRecognitionListener listener, boolean forDataDelivery,
             @NonNull String packageName, @Nullable String featureId) {
         if (DBG) Log.d(TAG, "checkPermissions");
+
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid == Process.SYSTEM_UID) {
+            // Assuming system has verified permissions of the caller.
+            return true;
+        }
+
         if (forDataDelivery) {
             if (PermissionChecker.checkCallingOrSelfPermissionForDataDelivery(this,
                     android.Manifest.permission.RECORD_AUDIO, packageName, featureId,
@@ -342,6 +352,7 @@
          * Return the Linux uid assigned to the process that sent you the current transaction that
          * is being processed. This is obtained from {@link Binder#getCallingUid()}.
          */
+        // TODO(b/176578753): need to make sure this is fixed when proxied through system.
         public int getCallingUid() {
             return mCallingUid;
         }
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index aea94bf..de879c6 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -16,6 +16,7 @@
 
 package android.speech;
 
+import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -27,6 +28,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -38,8 +40,9 @@
 /**
  * This class provides access to the speech recognition service. This service allows access to the
  * speech recognizer. Do not instantiate this class directly, instead, call
- * {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be
- * invoked only from the main application thread. 
+ * {@link SpeechRecognizer#createSpeechRecognizer(Context)}, or
+ * {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)}. This class's methods must be
+ * invoked only from the main application thread.
  *
  * <p>The implementation of this API is likely to stream audio to remote servers to perform speech
  * recognition. As such this API is not intended to be used for continuous recognition, which would
@@ -122,8 +125,13 @@
     /** Component to direct service intent to */
     private final ComponentName mServiceComponent;
 
+    /** Whether to use on-device speech recognizer. */
+    private final boolean mOnDevice;
+
+    private IRecognitionServiceManager mManagerService;
+
     /** Handler that will execute the main tasks */
-    private Handler mHandler = new Handler() {
+    private Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -159,6 +167,17 @@
     private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
         mContext = context;
         mServiceComponent = serviceComponent;
+        mOnDevice = false;
+    }
+
+    /**
+     * The right way to create a {@code SpeechRecognizer} is by using
+     * {@link #createOnDeviceSpeechRecognizer} static factory method
+     */
+    private SpeechRecognizer(final Context context, boolean onDevice) {
+        mContext = context;
+        mServiceComponent = null;
+        mOnDevice = onDevice;
     }
 
     /**
@@ -194,6 +213,7 @@
      * @return {@code true} if recognition is available, {@code false} otherwise
      */
     public static boolean isRecognitionAvailable(final Context context) {
+        // TODO(b/176578753): make sure this works well with system speech recognizers.
         final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
                 new Intent(RecognitionService.SERVICE_INTERFACE), 0);
         return list != null && list.size() != 0;
@@ -231,13 +251,32 @@
     public static SpeechRecognizer createSpeechRecognizer(final Context context,
             final ComponentName serviceComponent) {
         if (context == null) {
-            throw new IllegalArgumentException("Context cannot be null)");
+            throw new IllegalArgumentException("Context cannot be null");
         }
         checkIsCalledFromMainThread();
         return new SpeechRecognizer(context, serviceComponent);
     }
 
     /**
+     * Factory method to create a new {@code SpeechRecognizer}.
+     *
+     * <p>Please note that {@link #setRecognitionListener(RecognitionListener)} should be called
+     * before dispatching any command to the created {@code SpeechRecognizer}, otherwise no
+     * notifications will be received.
+     *
+     * @param context in which to create {@code SpeechRecognizer}
+     * @return a new on-device {@code SpeechRecognizer}.
+     */
+    @NonNull
+    public static SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull final Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("Context cannot be null");
+        }
+        checkIsCalledFromMainThread();
+        return new SpeechRecognizer(context, /* onDevice */ true);
+    }
+
+    /**
      * Sets the listener that will receive all the callbacks. The previous unfinished commands will
      * be executed with the old listener, while any following command will be executed with the new
      * listener.
@@ -265,36 +304,74 @@
         }
         checkIsCalledFromMainThread();
         if (mConnection == null) { // first time connection
-            mConnection = new Connection();
-            
-            Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
-            
-            if (mServiceComponent == null) {
-                String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
-                        Settings.Secure.VOICE_RECOGNITION_SERVICE);
-                
-                if (TextUtils.isEmpty(serviceComponent)) {
-                    Log.e(TAG, "no selected voice recognition service");
-                    mListener.onError(ERROR_CLIENT);
-                    return;
-                }
-                
-                serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent));                
+            // TODO(b/176578753): both flows should go through system service.
+            if (mOnDevice) {
+                connectToSystemService();
             } else {
-                serviceIntent.setComponent(mServiceComponent);
-            }
-            if (!mContext.bindService(serviceIntent, mConnection,
-                    Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
-                Log.e(TAG, "bind to recognition service failed");
-                mConnection = null;
-                mService = null;
-                mListener.onError(ERROR_CLIENT);
-                return;
+                connectToService();
             }
         }
         putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
     }
 
+    private void connectToSystemService() {
+        mManagerService = IRecognitionServiceManager.Stub.asInterface(
+                ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
+
+        if (mManagerService == null) {
+            mListener.onError(ERROR_CLIENT);
+            return;
+        }
+
+        try {
+            // TODO(b/176578753): this has to supply information on whether to use on-device impl.
+            mManagerService.createSession(new IRecognitionServiceManagerCallback.Stub(){
+                @Override
+                public void onSuccess(IRecognitionService service) throws RemoteException {
+                    mService = service;
+                }
+
+                @Override
+                public void onError() throws RemoteException {
+                    Log.e(TAG, "Bind to system recognition service failed");
+                    mListener.onError(ERROR_CLIENT);
+                }
+            });
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    private void connectToService() {
+        mConnection = new Connection();
+
+        Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
+
+        if (mServiceComponent == null) {
+            String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
+                    Settings.Secure.VOICE_RECOGNITION_SERVICE);
+
+            if (TextUtils.isEmpty(serviceComponent)) {
+                Log.e(TAG, "no selected voice recognition service");
+                mListener.onError(ERROR_CLIENT);
+                return;
+            }
+
+            serviceIntent.setComponent(
+                    ComponentName.unflattenFromString(serviceComponent));
+        } else {
+            serviceIntent.setComponent(mServiceComponent);
+        }
+        if (!mContext.bindService(serviceIntent, mConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
+            Log.e(TAG, "bind to recognition service failed");
+            mConnection = null;
+            mService = null;
+            mListener.onError(ERROR_CLIENT);
+            return;
+        }
+    }
+
     /**
      * Stops listening for speech. Speech captured so far will be recognized as if the user had
      * stopped speaking at this point. Note that in the default case, this does not need to be
@@ -378,7 +455,7 @@
             mListener.onError(ERROR_CLIENT);
         }
     }
-    
+
     private boolean checkOpenConnection() {
         if (mService != null) {
             return true;
@@ -433,7 +510,7 @@
         private final static int MSG_RMS_CHANGED = 8;
         private final static int MSG_ON_EVENT = 9;
 
-        private final Handler mInternalHandler = new Handler() {
+        private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) {
             @Override
             public void handleMessage(Message msg) {
                 if (mInternalListener == null) {
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 82d7399..2de7558 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -53,6 +53,8 @@
 public final class FontConfig implements Parcelable {
     private final @NonNull List<FontFamily> mFamilies;
     private final @NonNull List<Alias> mAliases;
+    private final long mLastModifiedTimeMillis;
+    private final int mConfigVersion;
 
     /**
      * Construct a FontConfig instance.
@@ -62,9 +64,12 @@
      *
      * @hide Only system server can create this instance and passed via IPC.
      */
-    public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases) {
+    public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
+            long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
         mFamilies = families;
         mAliases = aliases;
+        mLastModifiedTimeMillis = lastModifiedTimeMillis;
+        mConfigVersion = configVersion;
     }
 
     /**
@@ -88,6 +93,27 @@
     }
 
     /**
+     * Returns the last modified time in milliseconds.
+     *
+     * This is a value of {@link System#currentTimeMillis()} when the system font configuration was
+     * modified last time.
+     *
+     * If there is no update, this return 0.
+     */
+    public long getLastModifiedTimeMillis() {
+        return mLastModifiedTimeMillis;
+    }
+
+    /**
+     * Returns the monotonically increasing config version value.
+     *
+     * The config version is reset to 0 when the system is restarted.
+     */
+    public @IntRange(from = 0) int getConfigVersion() {
+        return mConfigVersion;
+    }
+
+    /**
      * Returns the ordered list of families included in the system fonts.
      * @deprecated Use getFontFamilies instead.
      * @hide
@@ -107,6 +133,8 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelableList(mFamilies, flags);
         dest.writeParcelableList(mAliases, flags);
+        dest.writeLong(mLastModifiedTimeMillis);
+        dest.writeInt(mConfigVersion);
     }
 
     public static final @NonNull Creator<FontConfig> CREATOR = new Creator<FontConfig>() {
@@ -116,7 +144,9 @@
                     FontFamily.class.getClassLoader());
             List<Alias> aliases = source.readParcelableList(new ArrayList<>(),
                     Alias.class.getClassLoader());
-            return new FontConfig(families, aliases);
+            long lastModifiedDate = source.readLong();
+            int configVersion = source.readInt();
+            return new FontConfig(families, aliases, lastModifiedDate, configVersion);
         }
 
         @Override
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index b229212..790773f 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -65,7 +65,7 @@
         DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true");
 
         DEFAULT_FLAGS.put("settings_tether_all_in_one", "false");
-        DEFAULT_FLAGS.put("settings_silky_home", "false");
+        DEFAULT_FLAGS.put("settings_silky_home", "true");
         DEFAULT_FLAGS.put("settings_contextual_home", "false");
         DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false");
     }
diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS
index 8f3d9f6..14aa386 100644
--- a/core/java/android/util/OWNERS
+++ b/core/java/android/util/OWNERS
@@ -1,3 +1,6 @@
 per-file FeatureFlagUtils.java = sbasi@google.com
 per-file FeatureFlagUtils.java = tmfang@google.com
 per-file FeatureFlagUtils.java = asapperstein@google.com
+
+per-file TypedValue.java = file:/core/java/android/content/res/OWNERS
+per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index a44ed59..698cb77 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -21,7 +21,9 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import android.annotation.Dimension;
 import android.graphics.Insets;
+import android.graphics.Matrix;
 import android.view.Surface.Rotation;
 
 /**
@@ -69,4 +71,34 @@
         }
         return rotated;
     }
+
+    /**
+     * Sets a matrix such that given a rotation, it transforms physical display
+     * coordinates to that rotation's logical coordinates.
+     *
+     * @param rotation the rotation to which the matrix should transform
+     * @param out the matrix to be set
+     */
+    public static void transformPhysicalToLogicalCoordinates(@Rotation int rotation,
+            @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) {
+        switch (rotation) {
+            case ROTATION_0:
+                out.reset();
+                break;
+            case ROTATION_90:
+                out.setRotate(270);
+                out.postTranslate(0, physicalWidth);
+                break;
+            case ROTATION_180:
+                out.setRotate(180);
+                out.postTranslate(physicalWidth, physicalHeight);
+                break;
+            case ROTATION_270:
+                out.setRotate(90);
+                out.postTranslate(physicalHeight, 0);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown rotation: " + rotation);
+        }
+    }
 }
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 86120d1..6718e93 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -16,6 +16,7 @@
 
 package android.util;
 
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 
 import com.android.internal.util.ArrayUtils;
@@ -23,6 +24,8 @@
 
 import libcore.util.EmptyArray;
 
+import java.util.Objects;
+
 /**
  * <code>SparseArray</code> maps integers to Objects and, unlike a normal array of Objects,
  * its indices can contain gaps. <code>SparseArray</code> is intended to be more memory-efficient
@@ -505,4 +508,44 @@
         buffer.append('}');
         return buffer.toString();
     }
+
+    /**
+     * For backwards compatibility reasons, {@link Object#equals(Object)} cannot be implemented,
+     * so this serves as a manually invoked alternative.
+     */
+    public boolean contentEquals(@Nullable SparseArray<E> other) {
+        if (other == null) {
+            return false;
+        }
+
+        int size = size();
+        if (size != other.size()) {
+            return false;
+        }
+
+        for (int index = 0; index < size; index++) {
+            int key = keyAt(index);
+            if (!Objects.equals(valueAt(index), other.get(key))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * For backwards compatibility, {@link Object#hashCode()} cannot be implemented, so this serves
+     * as a manually invoked alternative.
+     */
+    public int contentHashCode() {
+        int hash = 0;
+        int size = size();
+        for (int index = 0; index < size; index++) {
+            int key = keyAt(index);
+            E value = valueAt(index);
+            hash = 31 * hash + Objects.hashCode(key);
+            hash = 31 * hash + Objects.hashCode(value);
+        }
+        return hash;
+    }
 }
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 02edb7e..696271c 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.Signature;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Build;
 import android.os.Trace;
 import android.util.jar.StrictJarFile;
@@ -361,7 +362,7 @@
             // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization
             // to not need to verify the whole APK when verifyFUll == false.
             final ZipEntry manifestEntry = jarFile.findEntry(
-                    PackageParser.ANDROID_MANIFEST_FILENAME);
+                    ParsingPackageUtils.ANDROID_MANIFEST_FILENAME);
             if (manifestEntry == null) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                         "Package " + apkPath + " has no manifest");
@@ -370,7 +371,7 @@
             if (ArrayUtils.isEmpty(lastCerts)) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package "
                         + apkPath + " has no certificates at entry "
-                        + PackageParser.ANDROID_MANIFEST_FILENAME);
+                        + ParsingPackageUtils.ANDROID_MANIFEST_FILENAME);
             }
             lastSigs = convertToSignatures(lastCerts);
 
@@ -383,7 +384,7 @@
 
                     final String entryName = entry.getName();
                     if (entryName.startsWith("META-INF/")) continue;
-                    if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue;
+                    if (entryName.equals(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME)) continue;
 
                     toVerify.add(entry);
                 }
diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java
index 723f1dd..49ff237 100644
--- a/core/java/android/util/imetracing/ImeTracing.java
+++ b/core/java/android/util/imetracing/ImeTracing.java
@@ -28,6 +28,8 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.inputmethod.Completable;
+import com.android.internal.inputmethod.ResultCallbacks;
 import com.android.internal.view.IInputMethodManager;
 
 import java.io.PrintWriter;
@@ -91,7 +93,9 @@
      * @param where
      */
     public void sendToService(byte[] protoDump, int source, String where) throws RemoteException {
-        mService.startProtoDump(protoDump, source, where);
+        final Completable.Void value = Completable.createVoid();
+        mService.startProtoDump(protoDump, source, where, ResultCallbacks.of(value));
+        Completable.getResult(value);
     }
 
     /**
diff --git a/core/java/android/util/imetracing/OWNERS b/core/java/android/util/imetracing/OWNERS
new file mode 100644
index 0000000..885fd0a
--- /dev/null
+++ b/core/java/android/util/imetracing/OWNERS
@@ -0,0 +1,3 @@
+set noparent
+
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/core/java/android/view/ContentInfo.java b/core/java/android/view/ContentInfo.java
index bc66ea1..547bc9d 100644
--- a/core/java/android/view/ContentInfo.java
+++ b/core/java/android/view/ContentInfo.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -204,6 +205,7 @@
      * the IME.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public Bundle getExtras() {
         return mExtras;
     }
@@ -347,7 +349,7 @@
          * @return this builder
          */
         @NonNull
-        public Builder setExtras(@Nullable Bundle extras) {
+        public Builder setExtras(@SuppressLint("NullableCollection") @Nullable Bundle extras) {
             mExtras = extras;
             return this;
         }
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 0e5fb2c..0ba1dfe 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -121,6 +121,19 @@
     public static final int INVALID_DISPLAY = -1;
 
     /**
+     * The default display group id, which is the display group id of the primary display assuming
+     * there is one.
+     * @hide
+     */
+    public static final int DEFAULT_DISPLAY_GROUP = 0;
+
+    /**
+     * Invalid display group id.
+     * @hide
+     */
+    public static final int INVALID_DISPLAY_GROUP = -1;
+
+    /**
      * Display flag: Indicates that the display supports compositing content
      * that is stored in protected graphics buffers.
      * <p>
@@ -887,6 +900,32 @@
     }
 
     /**
+     * Returns the {@link RoundedCorner} of the given position if there is one.
+     *
+     * @param position the position of the rounded corner on the display.
+     *
+     * @return the rounded corner of the given position. Returns {@code null} if there is none.
+     */
+    @SuppressLint("VisiblySynchronized")
+    @Nullable
+    public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            RoundedCorners roundedCorners;
+            if (mMayAdjustByFixedRotation) {
+                roundedCorners = getDisplayAdjustments().adjustRoundedCorner(
+                        mDisplayInfo.roundedCorners,
+                        mDisplayInfo.rotation,
+                        mDisplayInfo.logicalWidth,
+                        mDisplayInfo.logicalHeight);
+            } else {
+                roundedCorners = mDisplayInfo.roundedCorners;
+            }
+            return roundedCorners == null ? null : roundedCorners.getRoundedCorner(position);
+        }
+    }
+
+    /**
      * Gets the pixel format of the display.
      * @return One of the constants defined in {@link android.graphics.PixelFormat}.
      *
diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java
index 5d5771c..e307eff 100644
--- a/core/java/android/view/DisplayAdjustments.java
+++ b/core/java/android/view/DisplayAdjustments.java
@@ -151,6 +151,23 @@
                 : realCutout;
     }
 
+    /**
+     * Returns the adjusted {@link RoundedCorners} if available. Otherwise the original
+     * {@link RoundedCorners} is returned.
+     */
+    @Nullable
+    public RoundedCorners adjustRoundedCorner(@Nullable RoundedCorners realRoundedCorners,
+            @Surface.Rotation int realRotation, int displayWidth, int displayHeight) {
+        final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments;
+        if (realRoundedCorners == null || rotationAdjustments == null
+                || rotationAdjustments.mRotation == realRotation) {
+            return realRoundedCorners;
+        }
+
+        return realRoundedCorners.rotate(
+                rotationAdjustments.mRotation, displayWidth, displayHeight);
+    }
+
     /** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */
     @Surface.Rotation
     public int getRotation(@Surface.Rotation int realRotation) {
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 525ac53..e1a4402 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -23,6 +23,7 @@
 import static android.view.DisplayCutoutProto.BOUND_RIGHT;
 import static android.view.DisplayCutoutProto.BOUND_TOP;
 import static android.view.DisplayCutoutProto.INSETS;
+import static android.view.Surface.ROTATION_0;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
@@ -31,13 +32,16 @@
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.graphics.Insets;
+import android.graphics.Matrix;
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Pair;
+import android.util.RotationUtils;
 import android.util.proto.ProtoOutputStream;
+import android.view.Surface.Rotation;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -69,6 +73,9 @@
             "com.android.internal.display_cutout_emulation";
 
     private static final Rect ZERO_RECT = new Rect();
+    private static final CutoutPathParserInfo EMPTY_PARSER_INFO = new CutoutPathParserInfo(
+            0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */,
+            0 /* rotation */, 0f /* scale */);
 
     /**
      * An instance where {@link #isEmpty()} returns {@code true}.
@@ -76,7 +83,7 @@
      * @hide
      */
     public static final DisplayCutout NO_CUTOUT = new DisplayCutout(
-            ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT,
+            ZERO_RECT, Insets.NONE, ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT, EMPTY_PARSER_INFO,
             false /* copyArguments */);
 
 
@@ -96,11 +103,15 @@
     @GuardedBy("CACHE_LOCK")
     private static Insets sCachedWaterfallInsets;
 
+    @GuardedBy("CACHE_LOCK")
+    private static CutoutPathParserInfo sCachedCutoutPathParserInfo;
+    @GuardedBy("CACHE_LOCK")
+    private static Path sCachedCutoutPath;
+
     private final Rect mSafeInsets;
     @NonNull
     private final Insets mWaterfallInsets;
 
-
     /**
      * The bound is at the left of the screen.
      * @hide
@@ -210,6 +221,7 @@
             }
             return result;
         }
+
         @Override
         public boolean equals(@Nullable Object o) {
             if (o == this) {
@@ -232,6 +244,106 @@
     private final Bounds mBounds;
 
     /**
+     * Stores all the needed info to create the cutout paths.
+     *
+     * @hide
+     */
+    public static class CutoutPathParserInfo {
+        private final int mDisplayWidth;
+        private final int mDisplayHeight;
+        private final float mDensity;
+        private final String mCutoutSpec;
+        private final @Rotation int mRotation;
+        private final float mScale;
+
+        public CutoutPathParserInfo(int displayWidth, int displayHeight, float density,
+                String cutoutSpec, @Rotation int rotation, float scale) {
+            mDisplayWidth = displayWidth;
+            mDisplayHeight = displayHeight;
+            mDensity = density;
+            mCutoutSpec = cutoutSpec == null ? "" : cutoutSpec;
+            mRotation = rotation;
+            mScale = scale;
+        }
+
+        public CutoutPathParserInfo(CutoutPathParserInfo cutoutPathParserInfo) {
+            mDisplayWidth = cutoutPathParserInfo.mDisplayWidth;
+            mDisplayHeight = cutoutPathParserInfo.mDisplayHeight;
+            mDensity = cutoutPathParserInfo.mDensity;
+            mCutoutSpec = cutoutPathParserInfo.mCutoutSpec;
+            mRotation = cutoutPathParserInfo.mRotation;
+            mScale = cutoutPathParserInfo.mScale;
+        }
+
+        public int getDisplayWidth() {
+            return mDisplayWidth;
+        }
+
+        public int getDisplayHeight() {
+            return mDisplayHeight;
+        }
+
+        public float getDensity() {
+            return mDensity;
+        }
+
+        public @NonNull String getCutoutSpec() {
+            return mCutoutSpec;
+        }
+
+        public int getRotation() {
+            return mRotation;
+        }
+
+        public float getScale() {
+            return mScale;
+        }
+
+        private boolean hasCutout() {
+            return !mCutoutSpec.isEmpty();
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            result = result * 48271 + Integer.hashCode(mDisplayWidth);
+            result = result * 48271 + Integer.hashCode(mDisplayHeight);
+            result = result * 48271 + Float.hashCode(mDensity);
+            result = result * 48271 + mCutoutSpec.hashCode();
+            result = result * 48271 + Integer.hashCode(mRotation);
+            result = result * 48271 + Float.hashCode(mScale);
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o instanceof CutoutPathParserInfo) {
+                CutoutPathParserInfo c = (CutoutPathParserInfo) o;
+                return mDisplayWidth == c.mDisplayWidth && mDisplayHeight == c.mDisplayHeight
+                        && mDensity == c.mDensity && mCutoutSpec.equals(c.mCutoutSpec)
+                        && mRotation == c.mRotation && mScale == c.mScale;
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "CutoutPathParserInfo{displayWidth=" + mDisplayWidth
+                    + " displayHeight=" + mDisplayHeight
+                    + " density={" + mDensity + "}"
+                    + " cutoutSpec={" + mCutoutSpec + "}"
+                    + " rotation={" + mRotation + "}"
+                    + " scale={" + mScale + "}"
+                    + "}";
+        }
+    }
+
+    private final @NonNull CutoutPathParserInfo mCutoutPathParserInfo;
+
+    /**
      * Creates a DisplayCutout instance.
      *
      * <p>Note that this is only useful for tests. For production code, developers should always
@@ -251,7 +363,8 @@
     // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
     public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
             @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom) {
-        this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, true);
+        this(safeInsets.toRect(), Insets.NONE, boundLeft, boundTop, boundRight, boundBottom, null,
+                true);
     }
 
     /**
@@ -276,7 +389,7 @@
             @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
             @NonNull Insets waterfallInsets) {
         this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
-                true);
+                null, true);
     }
 
     /**
@@ -294,7 +407,7 @@
     // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
     @Deprecated
     public DisplayCutout(@Nullable Rect safeInsets, @Nullable List<Rect> boundingRects) {
-        this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects),
+        this(safeInsets, Insets.NONE, extractBoundsFromList(safeInsets, boundingRects), null,
                 true /* copyArguments */);
     }
 
@@ -303,28 +416,42 @@
      *
      * @param safeInsets the insets from each edge which avoid the display cutout as returned by
      *                   {@link #getSafeInsetTop()} etc.
+     * @param waterfallInsets the insets for the curved areas in waterfall display.
+     * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
+     *                  it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundTop the top bounding rect of the display cutout in pixels.  If null is passed,
+     *                 it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundRight the right bounding rect of the display cutout in pixels.  If null is
+     *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
+     *                    passed, it's treated as an empty rectangle (0,0)-(0,0).
+     * @param info the cutout path parser info.
      * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments
      *                      are not copied and MUST remain unchanged forever.
      */
-    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft,
-                        Rect boundTop, Rect boundRight, Rect boundBottom, boolean copyArguments) {
+    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect boundLeft, Rect boundTop,
+            Rect boundRight, Rect boundBottom, CutoutPathParserInfo info,
+            boolean copyArguments) {
         mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
         mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
         mBounds = new Bounds(boundLeft, boundTop, boundRight, boundBottom, copyArguments);
+        mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
     }
 
     private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Rect[] bounds,
-                        boolean copyArguments) {
+            CutoutPathParserInfo info, boolean copyArguments) {
         mSafeInsets = getCopyOrRef(safeInsets, copyArguments);
         mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
         mBounds = new Bounds(bounds, copyArguments);
+        mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
     }
 
-    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds) {
+    private DisplayCutout(Rect safeInsets, Insets waterfallInsets, Bounds bounds,
+            CutoutPathParserInfo info) {
         mSafeInsets = safeInsets;
         mWaterfallInsets = waterfallInsets == null ? Insets.NONE : waterfallInsets;
         mBounds = bounds;
-
+        mCutoutPathParserInfo = info == null ? EMPTY_PARSER_INFO : info;
     }
 
     private static Rect getCopyOrRef(Rect r, boolean copyArguments) {
@@ -534,10 +661,70 @@
         return mBounds.getRect(BOUNDS_POSITION_BOTTOM);
     }
 
+    /**
+     * Returns a {@link Path} that contains the cutout paths of all sides on the display.
+     *
+     * To get a cutout path for one specific side, apps can intersect the {@link Path} with the
+     * {@link Rect} obtained from {@link #getBoundingRectLeft()}, {@link #getBoundingRectTop()},
+     * {@link #getBoundingRectRight()} or {@link #getBoundingRectBottom()}.
+     *
+     * @return a {@link Path} contains all the cutout paths based on display coordinate. Returns
+     * null if there is no cutout on the display.
+     */
+    public @Nullable Path getCutoutPath() {
+        if (!mCutoutPathParserInfo.hasCutout()) {
+            return null;
+        }
+        synchronized (CACHE_LOCK) {
+            if (mCutoutPathParserInfo.equals(sCachedCutoutPathParserInfo)) {
+                return sCachedCutoutPath;
+            }
+        }
+        final CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(
+                mCutoutPathParserInfo.getDensity(), mCutoutPathParserInfo.getDisplayWidth(),
+                mCutoutPathParserInfo.getDisplayHeight())
+                .parse(mCutoutPathParserInfo.getCutoutSpec());
+
+        final Path cutoutPath = cutoutSpec.getPath();
+        if (cutoutPath == null || cutoutPath.isEmpty()) {
+            return null;
+        }
+        final Matrix matrix = new Matrix();
+        if (mCutoutPathParserInfo.getRotation() != ROTATION_0) {
+            RotationUtils.transformPhysicalToLogicalCoordinates(
+                    mCutoutPathParserInfo.getRotation(),
+                    mCutoutPathParserInfo.getDisplayWidth(),
+                    mCutoutPathParserInfo.getDisplayHeight(),
+                    matrix
+            );
+        }
+        matrix.postScale(mCutoutPathParserInfo.getScale(), mCutoutPathParserInfo.getScale());
+        cutoutPath.transform(matrix);
+
+        synchronized (CACHE_LOCK) {
+            sCachedCutoutPathParserInfo = new CutoutPathParserInfo(mCutoutPathParserInfo);
+            sCachedCutoutPath = cutoutPath;
+        }
+        return cutoutPath;
+    }
+
+    /**
+     * @return the {@link CutoutPathParserInfo};
+     *
+     * @hide
+     */
+    public CutoutPathParserInfo getCutoutPathParserInfo() {
+        return mCutoutPathParserInfo;
+    }
+
     @Override
     public int hashCode() {
-        return (mSafeInsets.hashCode() * 48271 + mBounds.hashCode()) * 48271
-                + mWaterfallInsets.hashCode();
+        int result = 0;
+        result = 48271 * result + mSafeInsets.hashCode();
+        result = 48271 * result + mBounds.hashCode();
+        result = 48271 * result + mWaterfallInsets.hashCode();
+        result = 48271 * result + mCutoutPathParserInfo.hashCode();
+        return result;
     }
 
     @Override
@@ -548,7 +735,8 @@
         if (o instanceof DisplayCutout) {
             DisplayCutout c = (DisplayCutout) o;
             return mSafeInsets.equals(c.mSafeInsets) && mBounds.equals(c.mBounds)
-                    && mWaterfallInsets.equals(c.mWaterfallInsets);
+                    && mWaterfallInsets.equals(c.mWaterfallInsets)
+                    && mCutoutPathParserInfo.equals(c.mCutoutPathParserInfo);
         }
         return false;
     }
@@ -558,6 +746,7 @@
         return "DisplayCutout{insets=" + mSafeInsets
                 + " waterfall=" + mWaterfallInsets
                 + " boundingRect={" + mBounds + "}"
+                + " cutoutPathParserInfo={" + mCutoutPathParserInfo + "}"
                 + "}";
     }
 
@@ -607,7 +796,7 @@
         }
 
         return new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds,
-                false /* copyArguments */);
+                mCutoutPathParserInfo, false /* copyArguments */);
     }
 
     private Rect insetInsets(int insetLeft, int insetTop, int insetRight, int insetBottom,
@@ -638,7 +827,8 @@
      * @hide
      */
     public DisplayCutout replaceSafeInsets(Rect safeInsets) {
-        return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds);
+        return new DisplayCutout(new Rect(safeInsets), mWaterfallInsets, mBounds,
+                mCutoutPathParserInfo);
     }
 
     private static int atLeastZero(int value) {
@@ -658,16 +848,18 @@
         for (int i = 0; i < BOUNDS_POSITION_LENGTH; ++i) {
             bounds[i] = (pos == i) ? new Rect(left, top, right, bottom) : new Rect();
         }
-        return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */);
+        return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null, false /* copyArguments */);
     }
 
     /**
-     * Creates an instance from a bounding and waterfall insets.
+     * Creates an instance from bounds, waterfall insets and CutoutPathParserInfo.
      *
      * @hide
      */
-    public static DisplayCutout fromBoundsAndWaterfall(Rect[] bounds, Insets waterfallInsets) {
-        return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, false /* copyArguments */);
+    public static DisplayCutout constructDisplayCutout(Rect[] bounds, Insets waterfallInsets,
+            CutoutPathParserInfo info) {
+        return new DisplayCutout(ZERO_RECT, waterfallInsets, bounds, info,
+                false /* copyArguments */);
     }
 
     /**
@@ -676,7 +868,8 @@
      * @hide
      */
     public static DisplayCutout fromBounds(Rect[] bounds) {
-        return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, false /* copyArguments */);
+        return new DisplayCutout(ZERO_RECT, Insets.NONE, bounds, null /* cutoutPathParserInfo */,
+                false /* copyArguments */);
     }
 
     /**
@@ -686,10 +879,12 @@
      *
      * @hide
      */
-    public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, int displayHeight) {
-        return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
+    public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth,
+            int displayHeight) {
+        return pathAndDisplayCutoutFromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
+                res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
                 displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
-                loadWaterfallInset(res));
+                loadWaterfallInset(res)).second;
     }
 
     /**
@@ -699,7 +894,7 @@
      */
     public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
         return pathAndDisplayCutoutFromSpec(
-                res.getString(R.string.config_mainBuiltInDisplayCutout),
+                res.getString(R.string.config_mainBuiltInDisplayCutout), null,
                 displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
                 loadWaterfallInset(res)).first;
     }
@@ -710,14 +905,30 @@
      * @hide
      */
     @VisibleForTesting(visibility = PRIVATE)
-    public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
-            float density, Insets waterfallInsets) {
+    public static DisplayCutout fromSpec(String pathSpec, int displayWidth,
+            int displayHeight, float density, Insets waterfallInsets) {
         return pathAndDisplayCutoutFromSpec(
-                spec, displayWidth, displayHeight, density, waterfallInsets).second;
+                pathSpec, null, displayWidth, displayHeight, density, waterfallInsets)
+                .second;
     }
 
-    private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec,
-            int displayWidth, int displayHeight, float density, Insets waterfallInsets) {
+    /**
+     * Gets the cutout path and the corresponding DisplayCutout instance from the spec string.
+     *
+     * @param pathSpec the spec string read from config_mainBuiltInDisplayCutout.
+     * @param rectSpec the spec string read from config_mainBuiltInDisplayCutoutRectApproximation.
+     * @param displayWidth the display width.
+     * @param displayHeight the display height.
+     * @param density the display density.
+     * @param waterfallInsets the waterfall insets of the display.
+     * @return a Pair contains the cutout path and the corresponding DisplayCutout instance.
+     */
+    private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(
+            String pathSpec, String rectSpec, int displayWidth, int displayHeight, float density,
+            Insets waterfallInsets) {
+        // Always use the rect approximation spec to create the cutout if it's not null because
+        // transforming and sending a Region constructed from a path is very costly.
+        String spec = rectSpec != null ? rectSpec : pathSpec;
         if (TextUtils.isEmpty(spec) && waterfallInsets.equals(Insets.NONE)) {
             return NULL_PAIR;
         }
@@ -750,9 +961,12 @@
                     Math.max(waterfallInsets.bottom, safeInset.bottom));
         }
 
+        final CutoutPathParserInfo cutoutPathParserInfo = new CutoutPathParserInfo(displayWidth,
+                displayHeight, density, pathSpec.trim(), ROTATION_0, 1f /* scale */);
+
         final DisplayCutout cutout = new DisplayCutout(
-                safeInset, waterfallInsets, boundLeft, boundTop,
-                boundRight, boundBottom, false /* copyArguments */);
+                safeInset, waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
+                cutoutPathParserInfo , false /* copyArguments */);
         final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout);
         synchronized (CACHE_LOCK) {
             sCachedSpec = spec;
@@ -817,6 +1031,12 @@
                 out.writeTypedObject(cutout.mSafeInsets, flags);
                 out.writeTypedArray(cutout.mBounds.getRects(), flags);
                 out.writeTypedObject(cutout.mWaterfallInsets, flags);
+                out.writeInt(cutout.mCutoutPathParserInfo.getDisplayWidth());
+                out.writeInt(cutout.mCutoutPathParserInfo.getDisplayHeight());
+                out.writeFloat(cutout.mCutoutPathParserInfo.getDensity());
+                out.writeString(cutout.mCutoutPathParserInfo.getCutoutSpec());
+                out.writeInt(cutout.mCutoutPathParserInfo.getRotation());
+                out.writeFloat(cutout.mCutoutPathParserInfo.getScale());
             }
         }
 
@@ -860,9 +1080,17 @@
             Rect[] bounds = new Rect[BOUNDS_POSITION_LENGTH];
             in.readTypedArray(bounds, Rect.CREATOR);
             Insets waterfallInsets = in.readTypedObject(Insets.CREATOR);
+            int displayWidth = in.readInt();
+            int displayHeight = in.readInt();
+            float density = in.readFloat();
+            String cutoutSpec = in.readString();
+            int rotation = in.readInt();
+            float scale = in.readFloat();
+            final CutoutPathParserInfo info = new CutoutPathParserInfo(
+                    displayWidth, displayHeight, density, cutoutSpec, rotation, scale);
 
             return new DisplayCutout(
-                    safeInsets, waterfallInsets, bounds, false /* copyArguments */);
+                    safeInsets, waterfallInsets, bounds, info, false /* copyArguments */);
         }
 
         public DisplayCutout get() {
@@ -884,7 +1112,15 @@
             bounds.scale(scale);
             final Rect waterfallInsets = mInner.mWaterfallInsets.toRect();
             waterfallInsets.scale(scale);
-            mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds);
+            final CutoutPathParserInfo info = new CutoutPathParserInfo(
+                    mInner.mCutoutPathParserInfo.getDisplayWidth(),
+                    mInner.mCutoutPathParserInfo.getDisplayHeight(),
+                    mInner.mCutoutPathParserInfo.getDensity(),
+                    mInner.mCutoutPathParserInfo.getCutoutSpec(),
+                    mInner.mCutoutPathParserInfo.getRotation(),
+                    scale);
+
+            mInner = new DisplayCutout(safeInsets, Insets.of(waterfallInsets), bounds, info);
         }
 
         @Override
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index fc42cd0..2a00b5a 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -66,6 +66,11 @@
     public int displayId;
 
     /**
+     * Display Group identifier.
+     */
+    public int displayGroupId;
+
+    /**
      * Display address, or null if none.
      * Interpretation varies by display type.
      */
@@ -296,6 +301,12 @@
      */
     public float brightnessDefault;
 
+    /**
+     * The {@link RoundedCorners} if present, otherwise {@code null}.
+     */
+    @Nullable
+    public RoundedCorners roundedCorners;
+
     public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
         @Override
         public DisplayInfo createFromParcel(Parcel source) {
@@ -331,6 +342,7 @@
                 && flags == other.flags
                 && type == other.type
                 && displayId == other.displayId
+                && displayGroupId == other.displayGroupId
                 && Objects.equals(address, other.address)
                 && Objects.equals(deviceProductInfo, other.deviceProductInfo)
                 && Objects.equals(uniqueId, other.uniqueId)
@@ -363,7 +375,8 @@
                 && refreshRateOverride == other.refreshRateOverride
                 && brightnessMinimum == other.brightnessMinimum
                 && brightnessMaximum == other.brightnessMaximum
-                && brightnessDefault == other.brightnessDefault;
+                && brightnessDefault == other.brightnessDefault
+                && Objects.equals(roundedCorners, other.roundedCorners);
     }
 
     @Override
@@ -376,6 +389,7 @@
         flags = other.flags;
         type = other.type;
         displayId = other.displayId;
+        displayGroupId = other.displayGroupId;
         address = other.address;
         deviceProductInfo = other.deviceProductInfo;
         name = other.name;
@@ -411,6 +425,7 @@
         brightnessMinimum = other.brightnessMinimum;
         brightnessMaximum = other.brightnessMaximum;
         brightnessDefault = other.brightnessDefault;
+        roundedCorners = other.roundedCorners;
     }
 
     public void readFromParcel(Parcel source) {
@@ -418,6 +433,7 @@
         flags = source.readInt();
         type = source.readInt();
         displayId = source.readInt();
+        displayGroupId = source.readInt();
         address = source.readParcelable(null);
         deviceProductInfo = source.readParcelable(null);
         name = source.readString8();
@@ -460,6 +476,7 @@
         brightnessMinimum = source.readFloat();
         brightnessMaximum = source.readFloat();
         brightnessDefault = source.readFloat();
+        roundedCorners = source.readTypedObject(RoundedCorners.CREATOR);
     }
 
     @Override
@@ -468,6 +485,7 @@
         dest.writeInt(this.flags);
         dest.writeInt(type);
         dest.writeInt(displayId);
+        dest.writeInt(displayGroupId);
         dest.writeParcelable(address, flags);
         dest.writeParcelable(deviceProductInfo, flags);
         dest.writeString8(name);
@@ -508,6 +526,7 @@
         dest.writeFloat(brightnessMinimum);
         dest.writeFloat(brightnessMaximum);
         dest.writeFloat(brightnessDefault);
+        dest.writeTypedObject(roundedCorners, flags);
     }
 
     @Override
@@ -547,16 +566,17 @@
      * Returns the id of the "default" mode with the given refresh rate, or {@code 0} if no suitable
      * mode could be found.
      */
-    public int findDefaultModeByRefreshRate(float refreshRate) {
+    @Nullable
+    public Display.Mode findDefaultModeByRefreshRate(float refreshRate) {
         Display.Mode[] modes = supportedModes;
         Display.Mode defaultMode = getDefaultMode();
         for (int i = 0; i < modes.length; i++) {
             if (modes[i].matches(
                     defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), refreshRate)) {
-                return modes[i].getModeId();
+                return modes[i];
             }
         }
-        return 0;
+        return null;
     }
 
     /**
@@ -661,6 +681,8 @@
         sb.append(name);
         sb.append("\", displayId ");
         sb.append(displayId);
+        sb.append("\", displayGroupId ");
+        sb.append(displayGroupId);
         sb.append(flagsToString(flags));
         sb.append(", real ");
         sb.append(logicalWidth);
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index c2566cc..5937499 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -149,7 +149,7 @@
      * <p>
      * The time value that was used in all the vsync listeners and drawing for
      * the frame (Choreographer frame callbacks, animations,
-     * {@link View#getDrawingTime()}, etc…)
+     * {@link View#getDrawingTime()}, etc.)
      * </p>
      */
     public static final int VSYNC_TIMESTAMP = 11;
diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl
index 423e23d..1f64fb8 100644
--- a/core/java/android/view/IRemoteAnimationRunner.aidl
+++ b/core/java/android/view/IRemoteAnimationRunner.aidl
@@ -30,11 +30,15 @@
     /**
      * Called when the process needs to start the remote animation.
      *
+     * @param transition The old transition type. Must be one of WindowManager.TRANSIT_OLD_* values.
      * @param apps The list of apps to animate.
+     * @param wallpapers The list of wallpapers to animate.
+     * @param nonApps The list of non-app windows such as Bubbles to animate.
      * @param finishedCallback The callback to invoke when the animation is finished.
      */
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-    void onAnimationStart(in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
+    void onAnimationStart(int transit, in RemoteAnimationTarget[] apps,
+            in RemoteAnimationTarget[] wallpapers, in RemoteAnimationTarget[] nonApps,
             in IRemoteAnimationFinishedCallback finishedCallback);
 
     /**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 68a6de8..62f4b86 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -32,7 +32,7 @@
 import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
 import android.view.DisplayCutout;
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
@@ -117,23 +117,17 @@
     // These can only be called when holding the MANAGE_APP_TOKENS permission.
     void setEventDispatching(boolean enabled);
 
-    /** @return {@code true} if this binder is a registered window token. */
+    /** Returns {@code true} if this binder is a registered window token. */
     boolean isWindowToken(in IBinder binder);
     /**
      * Adds window token for a given type.
      *
      * @param token Token to be registered.
      * @param type Window type to be used with this token.
-     * @param options A bundle used to pass window-related options.
      * @param displayId The ID of the display where this token should be added.
-     * @param packageName The name of package to request to add window token. Could be {@code null}
-     *                    if callers holds the MANAGE_APP_TOKENS permission.
-     * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code
-     *         otherwise.
+     * @param options A bundle used to pass window-related options.
      */
-    int addWindowTokenWithOptions(IBinder token, int type, int displayId, in Bundle options,
-            String packageName);
-    void addWindowToken(IBinder token, int type, int displayId);
+    void addWindowToken(IBinder token, int type, int displayId, in Bundle options);
     /**
      * Remove window token on a specific display.
      *
@@ -765,25 +759,29 @@
     /**
      * Gets an array of support hashing algorithms that can be used to generate the hash of the
      * screenshot. The String value of one algorithm should be used when requesting to generate
-     * the impression attestation token.
+     * the ScreenshotHash.
      *
      * @return a String array of supported hashing algorithms.
      */
-    String[] getSupportedImpressionAlgorithms();
+    String[] getSupportedScreenshotHashingAlgorithms();
 
     /**
-     * Validate the impression token was generated by the system. The impression token passed in
-     * should be the token generated when calling {@link IWindowSession#generateImpressionToken}
+     * Validate the ScreenshotHash was generated by the system. The ScreenshotHash passed in
+     * should be the token generated when calling {@link IWindowSession#generateScreenshotHash}
      *
-     * @param impressionToken The token to verify that it was generated by the system.
+     * @param ScreenshotHash The token to verify that it was generated by the system.
      * @return true if the token was generated by the system or false if the token cannot be
      *         verified.
      */
-    boolean verifyImpressionToken(in ImpressionToken impressionToken);
+    boolean verifyScreenshotHash(in ScreenshotHash screenshotHash);
 
     /**
      * Registers a listener for a {@link android.app.WindowContext} to handle configuration changes
      * from the server side.
+     * <p>
+     * Note that this API should be invoked after calling
+     * {@link android.app.WindowTokenClient#attachContext(WindowContext)}
+     * </p>
      *
      * @param clientToken the window context's token
      * @param type Window type of the window context
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 85498cb..990b7bd 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -22,7 +22,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Bundle;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
 import android.util.MergedConfiguration;
 import android.view.DisplayCutout;
 import android.view.InputChannel;
@@ -47,11 +47,11 @@
 interface IWindowSession {
     int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, in InsetsState requestedVisibility,
-            out Rect outFrame, out InputChannel outInputChannel, out InsetsState insetsState,
+            out InputChannel outInputChannel, out InsetsState insetsState,
             out InsetsSourceControl[] activeControls);
     int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, in int userId,
-            in InsetsState requestedVisibility, out Rect outFrame, out InputChannel outInputChannel,
+            in InsetsState requestedVisibility, out InputChannel outInputChannel,
             out InsetsState insetsState, out InsetsSourceControl[] activeControls);
     int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, out InsetsState insetsState);
@@ -345,14 +345,14 @@
     void grantEmbeddedWindowFocus(IWindow window, in IBinder inputToken, boolean grantFocus);
 
     /**
-     * Generates an impression token that can be used to validate whether specific content was on
+     * Generates an ScreenshotHash that can be used to validate whether specific content was on
      * screen.
      *
-     * @param window The token for the window where the view to attest is shown.
+     * @param window The token for the window where the view to screenshot is shown.
      * @param boundsInWindow The size and position of the ads view in the window
      * @param hashAlgorithm The String for the hashing algorithm to use based on values returned
-     *                      from {@link IWindowManager#getSupportedImpressionAlgorithms()}
+     *                      from {@link IWindowManager#getSupportedHashingAlgorithms()}
      */
-    ImpressionToken generateImpressionToken(IWindow window, in Rect boundsInWindow,
+    ScreenshotHash generateScreenshotHash(IWindow window, in Rect boundsInWindow,
             in String hashAlgorithm);
 }
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f4b90e1..59e4931 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.hardware.Battery;
 import android.hardware.SensorManager;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
@@ -73,6 +74,7 @@
     private final boolean mHasMicrophone;
     private final boolean mHasButtonUnderPad;
     private final boolean mHasSensor;
+    private final boolean mHasBattery;
     private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
 
     @GuardedBy("mMotionRanges")
@@ -84,6 +86,9 @@
     @GuardedBy("mMotionRanges")
     private SensorManager mSensorManager;
 
+    @GuardedBy("mMotionRanges")
+    private Battery mBattery;
+
     /**
      * A mask for input source classes.
      *
@@ -323,6 +328,13 @@
     public static final int SOURCE_HDMI = 0x02000000 | SOURCE_CLASS_BUTTON;
 
     /**
+     * The input source is a sensor associated with the input device.
+     *
+     * @see #SOURCE_CLASS_NONE
+     */
+    public static final int SOURCE_SENSOR = 0x04000000 | SOURCE_CLASS_NONE;
+
+    /**
      * A special input source constant that is used when filtering input devices
      * to match devices that provide any type of input source.
      */
@@ -443,12 +455,11 @@
      * Called by native code
      * @hide
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @VisibleForTesting
     public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
             int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
             KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone,
-            boolean hasButtonUnderPad, boolean hasSensor) {
+            boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery) {
         mId = id;
         mGeneration = generation;
         mControllerNumber = controllerNumber;
@@ -464,6 +475,7 @@
         mHasMicrophone = hasMicrophone;
         mHasButtonUnderPad = hasButtonUnderPad;
         mHasSensor = hasSensor;
+        mHasBattery = hasBattery;
         mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
     }
 
@@ -483,6 +495,7 @@
         mHasMicrophone = in.readInt() != 0;
         mHasButtonUnderPad = in.readInt() != 0;
         mHasSensor = in.readInt() != 0;
+        mHasBattery = in.readInt() != 0;
         mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
 
         int numRanges = in.readInt();
@@ -830,6 +843,22 @@
     }
 
     /**
+     * Gets the battery object associated with the device, if there is one.
+     * Even if the device does not have a battery, the result is never null.
+     * Use {@link Battery#hasBattery} to determine whether a battery is
+     * present.
+     *
+     * @return The battery object associated with the device, never null.
+     */
+    @NonNull
+    public Battery getBattery() {
+        if (mBattery == null) {
+            mBattery = InputManager.getInstance().getInputDeviceBattery(mId, mHasBattery);
+        }
+        return mBattery;
+    }
+
+    /**
      * Gets the sensor manager service associated with the input device.
      * Even if the device does not have a sensor, the result is never null.
      * Use {@link SensorManager#getSensorList} to get a full list of all supported sensors.
@@ -1051,6 +1080,7 @@
         out.writeInt(mHasMicrophone ? 1 : 0);
         out.writeInt(mHasButtonUnderPad ? 1 : 0);
         out.writeInt(mHasSensor ? 1 : 0);
+        out.writeInt(mHasBattery ? 1 : 0);
 
         final int numRanges = mMotionRanges.size();
         out.writeInt(numRanges);
@@ -1097,6 +1127,8 @@
 
         description.append("  Has Sensor: ").append(mHasSensor).append("\n");
 
+        description.append("  Has battery: ").append(mHasBattery).append("\n");
+
         description.append("  Has mic: ").append(mHasMicrophone).append("\n");
 
         description.append("  Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 0939336..6a34a15 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -111,6 +111,9 @@
         mControl = new InsetsAnimationControlImpl(controls, frame, state, listener,
                 types, mCallbacks, durationMs, interpolator, animationType, translator);
         InsetsAnimationThread.getHandler().post(() -> {
+            if (mControl.isCancelled()) {
+                return;
+            }
             Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW,
                     "InsetsAsyncAnimation: " + WindowInsets.Type.toString(types), types);
             listener.onReady(mControl, types);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index b4e1172..e681c0e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -653,6 +653,7 @@
     private void updateState(InsetsState newState) {
         mState.setDisplayFrame(newState.getDisplayFrame());
         mState.setDisplayCutout(newState.getDisplayCutout());
+        mState.setRoundedCorners(newState.getRoundedCorners());
         @InsetsType int disabledUserAnimationTypes = 0;
         @InsetsType int[] cancelledUserAnimationTypes = {0};
         for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java
index a334907..3355252 100644
--- a/core/java/android/view/InsetsFlags.java
+++ b/core/java/android/view/InsetsFlags.java
@@ -21,6 +21,8 @@
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 
@@ -54,7 +56,15 @@
             @ViewDebug.FlagToString(
                     mask = APPEARANCE_LIGHT_NAVIGATION_BARS,
                     equals = APPEARANCE_LIGHT_NAVIGATION_BARS,
-                    name = "LIGHT_NAVIGATION_BARS")
+                    name = "LIGHT_NAVIGATION_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+                    equals = APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+                    name = "SEMI_TRANSPARENT_STATUS_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
+                    equals = APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS,
+                    name = "SEMI_TRANSPARENT_NAVIGATION_BARS")
     })
     public @Appearance int appearance;
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index d68e903..219190f 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -173,6 +173,9 @@
     private final DisplayCutout.ParcelableWrapper mDisplayCutout =
             new DisplayCutout.ParcelableWrapper();
 
+    /** The rounded corners on the display */
+    private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+
     public InsetsState() {
     }
 
@@ -256,7 +259,8 @@
         }
 
         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
-                alwaysConsumeSystemBars, calculateRelativeCutout(frame), compatInsetsTypes,
+                alwaysConsumeSystemBars, calculateRelativeCutout(frame),
+                calculateRelativeRoundedCorners(frame), compatInsetsTypes,
                 (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
     }
 
@@ -281,6 +285,20 @@
         return raw.inset(insetLeft, insetTop, insetRight, insetBottom);
     }
 
+    private RoundedCorners calculateRelativeRoundedCorners(Rect frame) {
+        if (mDisplayFrame.equals(frame)) {
+            return mRoundedCorners;
+        }
+        if (frame == null) {
+            return RoundedCorners.NO_ROUNDED_CORNERS;
+        }
+        final int insetLeft = frame.left - mDisplayFrame.left;
+        final int insetTop = frame.top - mDisplayFrame.top;
+        final int insetRight = mDisplayFrame.right - frame.right;
+        final int insetBottom = mDisplayFrame.bottom - frame.bottom;
+        return mRoundedCorners.inset(insetLeft, insetTop, insetRight, insetBottom);
+    }
+
     public Rect calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
         Insets insets = Insets.NONE;
         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -462,6 +480,14 @@
         return mDisplayCutout.get();
     }
 
+    public void setRoundedCorners(RoundedCorners roundedCorners) {
+        mRoundedCorners = roundedCorners;
+    }
+
+    public RoundedCorners getRoundedCorners() {
+        return mRoundedCorners;
+    }
+
     /**
      * Modifies the state of this class to exclude a certain type to make it ready for dispatching
      * to the client.
@@ -493,6 +519,7 @@
     public void scale(float scale) {
         mDisplayFrame.scale(scale);
         mDisplayCutout.scale(scale);
+        mRoundedCorners = mRoundedCorners.scale(scale);
         for (int i = 0; i < SIZE; i++) {
             final InsetsSource source = mSources[i];
             if (source != null) {
@@ -512,6 +539,7 @@
     public void set(InsetsState other, boolean copySources) {
         mDisplayFrame.set(other.mDisplayFrame);
         mDisplayCutout.set(other.mDisplayCutout);
+        mRoundedCorners = other.getRoundedCorners();
         if (copySources) {
             for (int i = 0; i < SIZE; i++) {
                 InsetsSource source = other.mSources[i];
@@ -576,13 +604,13 @@
                 return Type.CAPTION_BAR;
             case ITYPE_IME:
                 return Type.IME;
-            case ITYPE_TOP_GESTURES:
-            case ITYPE_BOTTOM_GESTURES:
             case ITYPE_TOP_MANDATORY_GESTURES:
             case ITYPE_BOTTOM_MANDATORY_GESTURES:
             case ITYPE_LEFT_MANDATORY_GESTURES:
             case ITYPE_RIGHT_MANDATORY_GESTURES:
                 return Type.MANDATORY_SYSTEM_GESTURES;
+            case ITYPE_TOP_GESTURES:
+            case ITYPE_BOTTOM_GESTURES:
             case ITYPE_LEFT_GESTURES:
             case ITYPE_RIGHT_GESTURES:
                 return Type.SYSTEM_GESTURES;
@@ -619,11 +647,15 @@
     }
 
     public void dump(String prefix, PrintWriter pw) {
+        final String newPrefix = prefix + "  ";
         pw.println(prefix + "InsetsState");
+        pw.println(newPrefix + "mDisplayFrame=" + mDisplayFrame);
+        pw.println(newPrefix + "mDisplayCutout=" + mDisplayCutout.get());
+        pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
         for (int i = 0; i < SIZE; i++) {
             InsetsSource source = mSources[i];
             if (source == null) continue;
-            source.dump(prefix + "  ", pw);
+            source.dump(newPrefix + "  ", pw);
         }
     }
 
@@ -713,7 +745,8 @@
         InsetsState state = (InsetsState) o;
 
         if (!mDisplayFrame.equals(state.mDisplayFrame)
-                || !mDisplayCutout.equals(state.mDisplayCutout)) {
+                || !mDisplayCutout.equals(state.mDisplayCutout)
+                || !mRoundedCorners.equals(state.mRoundedCorners)) {
             return false;
         }
         for (int i = 0; i < SIZE; i++) {
@@ -737,7 +770,8 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources));
+        return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
+                mRoundedCorners);
     }
 
     public InsetsState(Parcel in) {
@@ -754,6 +788,7 @@
         mDisplayFrame.writeToParcel(dest, flags);
         mDisplayCutout.writeToParcel(dest, flags);
         dest.writeParcelableArray(mSources, 0);
+        dest.writeTypedObject(mRoundedCorners, flags);
     }
 
     public static final @android.annotation.NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
@@ -771,6 +806,7 @@
         mDisplayFrame.set(Rect.CREATOR.createFromParcel(in));
         mDisplayCutout.set(DisplayCutout.ParcelableWrapper.CREATOR.createFromParcel(in));
         mSources = in.readParcelableArray(null, InsetsSource.class);
+        mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
     }
 
     @Override
@@ -785,6 +821,7 @@
         return "InsetsState: {"
                 + "mDisplayFrame=" + mDisplayFrame
                 + ", mDisplayCutout=" + mDisplayCutout
+                + ", mRoundedCorners=" + mRoundedCorners
                 + ", mSources= { " + joiner
                 + " }";
     }
diff --git a/core/java/android/view/RoundedCorner.java b/core/java/android/view/RoundedCorner.java
new file mode 100644
index 0000000..cc7525b
--- /dev/null
+++ b/core/java/android/view/RoundedCorner.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2021 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 android.view;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a rounded corner of the display.
+ *
+ * <code>
+ *       ________
+ *     /        ^
+ *    /         | Radius
+ *    |         v
+ *    |       X <- Center point
+ *    |<----->
+ *     Radius
+ * </code>
+ *
+ * <p>Note: The rounded corner formed by the radius and the center is an approximation.</p>
+ *
+ * <p>{@link RoundedCorner} is immutable.</p>
+ */
+public final class RoundedCorner implements Parcelable {
+
+    /**
+     * The rounded corner is at the top-left of the screen.
+     */
+    public static final int POSITION_TOP_LEFT = 0;
+    /**
+     * The rounded corner is at the top-right of the screen.
+     */
+    public static final int POSITION_TOP_RIGHT = 1;
+    /**
+     * The rounded corner is at the bottom-right of the screen.
+     */
+    public static final int POSITION_BOTTOM_RIGHT = 2;
+    /**
+     * The rounded corner is at the bottom-left of the screen.
+     */
+    public static final int POSITION_BOTTOM_LEFT = 3;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "POSITION_" }, value = {
+            POSITION_TOP_LEFT,
+            POSITION_TOP_RIGHT,
+            POSITION_BOTTOM_RIGHT,
+            POSITION_BOTTOM_LEFT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Position {}
+
+    private final @Position int mPosition;
+    private final int mRadius;
+    @NonNull
+    private final Point mCenter;
+
+    /**
+     * Creates an empty {@link RoundedCorner} on the given position.
+     * @hide
+     */
+    @VisibleForTesting
+    public RoundedCorner(@Position int position) {
+        mPosition = position;
+        mRadius = 0;
+        mCenter = new Point(0, 0);
+    }
+
+    /**
+     * Creates a {@link RoundedCorner}.
+     *
+     * <p>Note that this is only useful for tests. For production code, developers should always
+     * use a {@link RoundedCorner} obtained from the system via
+     * {@link WindowInsets#getRoundedCorner} or {@link Display#getRoundedCorner}.</p>
+     *
+     * @param position the position of the rounded corner.
+     * @param radius the radius of the rounded corner.
+     * @param centerX the x of center point of the rounded corner.
+     * @param centerY the y of center point of the rounded corner.
+     *
+     */
+    public RoundedCorner(@Position int position, int radius, int centerX,
+            int centerY) {
+        mPosition = position;
+        mRadius = radius;
+        mCenter = new Point(centerX, centerY);
+    }
+
+    /**
+     * Creates a {@link RoundedCorner} from a passed in {@link RoundedCorner}.
+     *
+     * @hide
+     */
+    RoundedCorner(RoundedCorner rc) {
+        mPosition = rc.getPosition();
+        mRadius = rc.getRadius();
+        mCenter = new Point(rc.getCenter());
+    }
+
+    /**
+     * Get the position of this {@link RoundedCorner}.
+     *
+     * @see #POSITION_TOP_LEFT
+     * @see #POSITION_TOP_RIGHT
+     * @see #POSITION_BOTTOM_RIGHT
+     * @see #POSITION_BOTTOM_LEFT
+     */
+    public @Position int getPosition() {
+        return mPosition;
+    }
+
+    /**
+     * Returns the radius of a quarter circle approximation of this {@link RoundedCorner}.
+     *
+     * @return the rounded corner radius of this {@link RoundedCorner}. Returns 0 if there is no
+     * rounded corner.
+     */
+    public int getRadius() {
+        return mRadius;
+    }
+
+    /**
+     * Returns the circle center of a quarter circle approximation of this {@link RoundedCorner}.
+     *
+     * @return the center point of this {@link RoundedCorner} in the application's coordinate.
+     */
+    @NonNull
+    public Point getCenter() {
+        return new Point(mCenter);
+    }
+
+    /**
+     * Checks whether this {@link RoundedCorner} exists and is inside the application's bounds.
+     *
+     * @return {@code false} if there is a rounded corner and is contained in the application's
+     *                       bounds. Otherwise return {@code true}.
+     *
+     * @hide
+     */
+    public boolean isEmpty() {
+        return mRadius == 0 || mCenter.x == 0 || mCenter.y == 0;
+    }
+
+    private String getPositionString(@Position int position) {
+        switch (position) {
+            case POSITION_TOP_LEFT:
+                return "TopLeft";
+            case POSITION_TOP_RIGHT:
+                return "TopRight";
+            case POSITION_BOTTOM_RIGHT:
+                return "BottomRight";
+            case POSITION_BOTTOM_LEFT:
+                return "BottomLeft";
+            default:
+                return "Invalid";
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof RoundedCorner) {
+            RoundedCorner r = (RoundedCorner) o;
+            return mPosition == r.mPosition && mRadius == r.mRadius
+                    && mCenter.equals(r.mCenter);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        result = 31 * result + mPosition;
+        result = 31 * result + mRadius;
+        result = 31 * result + mCenter.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "RoundedCorner{"
+                + "position=" + getPositionString(mPosition)
+                + ", radius=" + mRadius
+                + ", center=" + mCenter
+                + '}';
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mPosition);
+        out.writeInt(mRadius);
+        out.writeInt(mCenter.x);
+        out.writeInt(mCenter.y);
+    }
+
+    public static final @NonNull Creator<RoundedCorner> CREATOR = new Creator<RoundedCorner>() {
+        @Override
+        public RoundedCorner createFromParcel(Parcel in) {
+            return new RoundedCorner(in.readInt(), in.readInt(), in.readInt(), in.readInt());
+        }
+
+        @Override
+        public RoundedCorner[] newArray(int size) {
+            return new RoundedCorner[size];
+        }
+    };
+}
diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/view/RoundedCorners.aidl
similarity index 75%
copy from core/java/com/android/internal/net/VpnInfo.aidl
copy to core/java/android/view/RoundedCorners.aidl
index 6fc97be..0a901c0 100644
--- a/core/java/com/android/internal/net/VpnInfo.aidl
+++ b/core/java/android/view/RoundedCorners.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
+/**
+ * Copyright (c) 2021, 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
+ *     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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package android.view;
 
-parcelable VpnInfo;
+parcelable RoundedCorners;
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
new file mode 100644
index 0000000..015e804
--- /dev/null
+++ b/core/java/android/view/RoundedCorners.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2021 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 android.view;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.view.RoundedCorner.Position;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A class to create & manage all the {@link RoundedCorner} on the display.
+ *
+ * @hide
+ */
+public class RoundedCorners implements Parcelable {
+
+    public static final RoundedCorners NO_ROUNDED_CORNERS = new RoundedCorners(
+            new RoundedCorner(POSITION_TOP_LEFT), new RoundedCorner(POSITION_TOP_RIGHT),
+            new RoundedCorner(POSITION_BOTTOM_RIGHT), new RoundedCorner(POSITION_BOTTOM_LEFT));
+
+    /**
+     * The number of possible positions at which rounded corners can be located.
+     */
+    public static final int ROUNDED_CORNER_POSITION_LENGTH = 4;
+
+    private static final Object CACHE_LOCK = new Object();
+
+    @GuardedBy("CACHE_LOCK")
+    private static int sCachedDisplayWidth;
+    @GuardedBy("CACHE_LOCK")
+    private static int sCachedDisplayHeight;
+    @GuardedBy("CACHE_LOCK")
+    private static Pair<Integer, Integer> sCachedRadii;
+    @GuardedBy("CACHE_LOCK")
+    private static RoundedCorners sCachedRoundedCorners;
+
+    @VisibleForTesting
+    public final RoundedCorner[] mRoundedCorners;
+
+    public RoundedCorners(RoundedCorner[] roundedCorners) {
+        mRoundedCorners = roundedCorners;
+    }
+
+    public RoundedCorners(RoundedCorner topLeft, RoundedCorner topRight, RoundedCorner bottomRight,
+            RoundedCorner bottomLeft) {
+        mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        mRoundedCorners[POSITION_TOP_LEFT] = topLeft;
+        mRoundedCorners[POSITION_TOP_RIGHT] = topRight;
+        mRoundedCorners[POSITION_BOTTOM_RIGHT] = bottomRight;
+        mRoundedCorners[POSITION_BOTTOM_LEFT] = bottomLeft;
+    }
+
+    public RoundedCorners(RoundedCorners roundedCorners) {
+        mRoundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+            mRoundedCorners[i] = new RoundedCorner(roundedCorners.mRoundedCorners[i]);
+        }
+    }
+
+    /**
+     * Creates the rounded corners according to @android:dimen/rounded_corner_radius,
+     * @android:dimen/rounded_corner_radius_top and @android:dimen/rounded_corner_radius_bottom
+     */
+    public static RoundedCorners fromResources(
+            Resources res, int displayWidth, int displayHeight) {
+        return fromRadii(loadRoundedCornerRadii(res), displayWidth, displayHeight);
+    }
+
+    /**
+     * Creates the rounded corners from radius
+     */
+    @VisibleForTesting
+    public static RoundedCorners fromRadii(Pair<Integer, Integer> radii, int displayWidth,
+            int displayHeight) {
+        if (radii == null) {
+            return null;
+        }
+
+        synchronized (CACHE_LOCK) {
+            if (radii.equals(sCachedRadii) && sCachedDisplayWidth == displayWidth
+                    && sCachedDisplayHeight == displayHeight) {
+                return sCachedRoundedCorners;
+            }
+        }
+
+        final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        final int topRadius = radii.first > 0 ? radii.first : 0;
+        final int bottomRadius = radii.second > 0 ? radii.second : 0;
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
+            roundedCorners[i] = createRoundedCorner(
+                    i,
+                    i <= POSITION_TOP_RIGHT ? topRadius : bottomRadius,
+                    displayWidth,
+                    displayHeight);
+        }
+
+        final RoundedCorners result = new RoundedCorners(roundedCorners);
+        synchronized (CACHE_LOCK) {
+            sCachedDisplayWidth = displayWidth;
+            sCachedDisplayHeight = displayHeight;
+            sCachedRadii = radii;
+            sCachedRoundedCorners = result;
+        }
+        return result;
+    }
+
+    /**
+     * Loads the rounded corner radii from resources.
+     *
+     * @param res
+     * @return a Pair of radius. The first is the top rounded corner radius and second is the
+     * bottom corner radius.
+     */
+    @Nullable
+    private static Pair<Integer, Integer> loadRoundedCornerRadii(Resources res) {
+        final int radiusDefault = res.getDimensionPixelSize(R.dimen.rounded_corner_radius);
+        final int radiusTop = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_top);
+        final int radiusBottom = res.getDimensionPixelSize(R.dimen.rounded_corner_radius_bottom);
+        if (radiusDefault == 0 && radiusTop == 0 && radiusBottom == 0) {
+            return null;
+        }
+        final Pair<Integer, Integer> radii = new Pair<>(
+                        radiusTop > 0 ? radiusTop : radiusDefault,
+                        radiusBottom > 0 ? radiusBottom : radiusDefault);
+        return radii;
+    }
+
+    /**
+     * Insets the reference frame of the rounded corners.
+     *
+     * @return a copy of this instance which has been inset
+     */
+    public RoundedCorners inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
+        final RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; i++) {
+            roundedCorners[i] = insetRoundedCorner(i, insetLeft, insetTop, insetRight, insetBottom);
+        }
+        return new RoundedCorners(roundedCorners);
+    }
+
+    private RoundedCorner insetRoundedCorner(@Position int position, int insetLeft,
+            int insetTop, int insetRight, int insetBottom) {
+        if (mRoundedCorners[position].isEmpty()) {
+            return new RoundedCorner(position);
+        }
+
+        final int radius = mRoundedCorners[position].getRadius();
+        final Point center = mRoundedCorners[position].getCenter();
+        boolean hasRoundedCorner;
+        switch (position) {
+            case POSITION_TOP_LEFT:
+                hasRoundedCorner = radius > insetTop || radius > insetLeft;
+                break;
+            case POSITION_TOP_RIGHT:
+                hasRoundedCorner = radius > insetTop || radius > insetRight;
+                break;
+            case POSITION_BOTTOM_RIGHT:
+                hasRoundedCorner = radius > insetBottom || radius > insetRight;
+                break;
+            case POSITION_BOTTOM_LEFT:
+                hasRoundedCorner = radius > insetBottom || radius > insetLeft;
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "The position is not one of the RoundedCornerPosition =" + position);
+        }
+        return new RoundedCorner(
+                position, radius,
+                hasRoundedCorner ? center.x - insetLeft : 0,
+                hasRoundedCorner ? center.y - insetTop : 0);
+    }
+
+    /**
+     * Returns the {@link RoundedCorner} of the given position if there is one.
+     *
+     * @param position the position of the rounded corner on the display.
+     * @return the rounded corner of the given position. Returns {@code null} if
+     * {@link RoundedCorner#isEmpty()} is {@code true}.
+     */
+    @Nullable
+    public RoundedCorner getRoundedCorner(@Position int position) {
+        return mRoundedCorners[position].isEmpty()
+                ? null : new RoundedCorner(mRoundedCorners[position]);
+    }
+
+    /**
+     * Sets the rounded corner of given position.
+     *
+     * @param position the position of this rounded corner
+     * @param roundedCorner the rounded corner or null if there is none
+     */
+    public void setRoundedCorner(@Position int position, @Nullable RoundedCorner roundedCorner) {
+        mRoundedCorners[position] = roundedCorner == null
+                ? new RoundedCorner(position) : roundedCorner;
+    }
+
+    /**
+     * Returns an array of {@link RoundedCorner}s. Ordinal value of RoundedCornerPosition is used
+     * as an index of the array.
+     *
+     * @return an array of {@link RoundedCorner}s, one for each rounded corner area.
+     */
+    public RoundedCorner[] getAllRoundedCorners() {
+        RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+            roundedCorners[i] = new RoundedCorner(roundedCorners[i]);
+        }
+        return roundedCorners;
+    }
+
+    /**
+     * Returns a scaled RoundedCorners.
+     */
+    public RoundedCorners scale(float scale) {
+        if (scale == 1f) {
+            return this;
+        }
+
+        RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        for (int i = 0; i < ROUNDED_CORNER_POSITION_LENGTH; ++i) {
+            final RoundedCorner roundedCorner = mRoundedCorners[i];
+            roundedCorners[i] = new RoundedCorner(
+                    i,
+                    (int) (roundedCorner.getRadius() * scale),
+                    (int) (roundedCorner.getCenter().x * scale),
+                    (int) (roundedCorner.getCenter().y * scale));
+        }
+        return new RoundedCorners(roundedCorners);
+    }
+
+    /**
+     * Returns a rotated RoundedCorners.
+     */
+    public RoundedCorners rotate(@Surface.Rotation int rotation, int initialDisplayWidth,
+            int initialDisplayHeight) {
+        if (rotation == ROTATION_0) {
+            return this;
+        }
+        final boolean isSizeFlipped = rotation == ROTATION_90 || rotation == ROTATION_270;
+        RoundedCorner[] newCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+        int newPosistion;
+        for (int i = 0; i < mRoundedCorners.length; i++) {
+            newPosistion = getRotatedIndex(i, rotation);
+            newCorners[newPosistion] = createRoundedCorner(
+                    newPosistion,
+                    mRoundedCorners[i].getRadius(),
+                    isSizeFlipped ? initialDisplayHeight : initialDisplayWidth,
+                    isSizeFlipped ? initialDisplayWidth : initialDisplayHeight);
+        }
+        return new RoundedCorners(newCorners);
+    }
+
+    private static RoundedCorner createRoundedCorner(@Position int position,
+            int radius, int displayWidth, int displayHeight) {
+        switch (position) {
+            case POSITION_TOP_LEFT:
+                return new RoundedCorner(
+                        POSITION_TOP_LEFT,
+                        radius,
+                        radius > 0 ? radius : 0,
+                        radius > 0 ? radius : 0);
+            case POSITION_TOP_RIGHT:
+                return new RoundedCorner(
+                        POSITION_TOP_RIGHT,
+                        radius,
+                        radius > 0 ? displayWidth - radius : 0,
+                        radius > 0 ? radius : 0);
+            case POSITION_BOTTOM_RIGHT:
+                return new RoundedCorner(
+                        POSITION_BOTTOM_RIGHT,
+                        radius,
+                        radius > 0 ? displayWidth - radius : 0,
+                        radius > 0 ? displayHeight - radius : 0);
+            case POSITION_BOTTOM_LEFT:
+                return new RoundedCorner(
+                        POSITION_BOTTOM_LEFT,
+                        radius,
+                        radius > 0 ? radius : 0,
+                        radius > 0 ? displayHeight - radius  : 0);
+            default:
+                throw new IllegalArgumentException(
+                        "The position is not one of the RoundedCornerPosition =" + position);
+        }
+    }
+
+    private static int getRotatedIndex(int position, int rotation) {
+        return (position - rotation + ROUNDED_CORNER_POSITION_LENGTH) % 4;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        for (RoundedCorner roundedCorner : mRoundedCorners) {
+            result = result * 31 + roundedCorner.hashCode();
+        }
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof RoundedCorners) {
+            RoundedCorners r = (RoundedCorners) o;
+            return Arrays.deepEquals(mRoundedCorners, ((RoundedCorners) o).mRoundedCorners);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "RoundedCorners{" + Arrays.toString(mRoundedCorners) + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (equals(NO_ROUNDED_CORNERS)) {
+            dest.writeInt(0);
+        } else {
+            dest.writeInt(1);
+            dest.writeTypedArray(mRoundedCorners, flags);
+        }
+    }
+
+    public static final @NonNull Creator<RoundedCorners> CREATOR = new Creator<RoundedCorners>() {
+        @Override
+        public RoundedCorners createFromParcel(Parcel in) {
+            int variant = in.readInt();
+            if (variant == 0) {
+                return NO_ROUNDED_CORNERS;
+            }
+            RoundedCorner[] roundedCorners = new RoundedCorner[ROUNDED_CORNER_POSITION_LENGTH];
+            in.readTypedArray(roundedCorners, RoundedCorner.CREATOR);
+            return new RoundedCorners(roundedCorners);
+        }
+
+        @Override
+        public RoundedCorners[] newArray(int size) {
+            return new RoundedCorners[size];
+        }
+    };
+}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index a2777fe..24bc308 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -218,6 +218,15 @@
     public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1;
 
     /**
+     * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display
+     * to operate at the exact frame rate.
+     *
+     * This is used internally by the platform and should not be used by apps.
+     * @hide
+     */
+    public static final int FRAME_RATE_COMPATIBILITY_EXACT = 100;
+
+    /**
      * Create an empty surface, which will later be filled in by readFromParcel().
      * @hide
      */
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 9932b2a..98b4acd 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -188,8 +188,6 @@
             IBinder displayToken, int mode);
     private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject,
             long barrierObject, long frame);
-    private static native void nativeReparentChildren(long transactionObj, long nativeObject,
-            long newParentObject);
     private static native void nativeReparent(long transactionObj, long nativeObject,
             long newParentNativeObject);
 
@@ -339,8 +337,6 @@
      */
     public long mNativeObject;
     private long mNativeHandle;
-    private boolean mDebugRelease = false;
-    private Throwable mReleaseStack = null;
 
     // TODO: Move width/height to native and fix locking through out.
     private final Object mLock = new Object();
@@ -416,6 +412,15 @@
      */
     public static final int SECURE = 0x00000080;
 
+
+    /**
+     * Queue up BufferStateLayer buffers instead of dropping the oldest buffer when this flag is
+     * set. This blocks the client until all the buffers have been presented. If the buffers
+     * have presentation timestamps, then we may drop buffers.
+     * @hide
+     */
+    public static final int ENABLE_BACKPRESSURE = 0x00000100;
+
     /**
      * Surface creation flag: Creates a surface where color components are interpreted
      * as "non pre-multiplied" by their alpha channel. Of course this flag is
@@ -588,13 +593,6 @@
         }
         mNativeObject = nativeObject;
         mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;
-        if (mNativeObject == 0) {
-            if (mDebugRelease) {
-                mReleaseStack = new Throwable("assigned zero nativeObject here");
-            }
-        } else {
-            mReleaseStack = null;
-        }
     }
 
     /**
@@ -605,7 +603,6 @@
         mWidth = other.mWidth;
         mHeight = other.mHeight;
         mLocalOwnerView = other.mLocalOwnerView;
-        mDebugRelease = other.mDebugRelease;
         assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject), callsite);
     }
 
@@ -751,18 +748,22 @@
     private abstract static class CaptureArgs {
         private final int mPixelFormat;
         private final Rect mSourceCrop = new Rect();
-        private final float mFrameScale;
+        private final float mFrameScaleX;
+        private final float mFrameScaleY;
         private final boolean mCaptureSecureLayers;
         private final boolean mAllowProtected;
         private final long mUid;
+        private final boolean mGrayscale;
 
         private CaptureArgs(Builder<? extends Builder<?>> builder) {
             mPixelFormat = builder.mPixelFormat;
             mSourceCrop.set(builder.mSourceCrop);
-            mFrameScale = builder.mFrameScale;
+            mFrameScaleX = builder.mFrameScaleX;
+            mFrameScaleY = builder.mFrameScaleY;
             mCaptureSecureLayers = builder.mCaptureSecureLayers;
             mAllowProtected = builder.mAllowProtected;
             mUid = builder.mUid;
+            mGrayscale = builder.mGrayscale;
         }
 
         /**
@@ -773,10 +774,12 @@
         abstract static class Builder<T extends Builder<T>> {
             private int mPixelFormat = PixelFormat.RGBA_8888;
             private final Rect mSourceCrop = new Rect();
-            private float mFrameScale = 1;
+            private float mFrameScaleX = 1;
+            private float mFrameScaleY = 1;
             private boolean mCaptureSecureLayers;
             private boolean mAllowProtected;
             private long mUid = -1;
+            private boolean mGrayscale;
 
             /**
              * The desired pixel format of the returned buffer.
@@ -799,7 +802,18 @@
              * The desired scale of the returned buffer. The raw screen will be scaled up/down.
              */
             public T setFrameScale(float frameScale) {
-                mFrameScale = frameScale;
+                mFrameScaleX = frameScale;
+                mFrameScaleY = frameScale;
+                return getThis();
+            }
+
+            /**
+             * The desired scale of the returned buffer, allowing separate values for x and y scale.
+             * The raw screen will be scaled up/down.
+             */
+            public T setFrameScale(float frameScaleX, float frameScaleY) {
+                mFrameScaleX = frameScaleX;
+                mFrameScaleY = frameScaleY;
                 return getThis();
             }
 
@@ -835,6 +849,14 @@
             }
 
             /**
+             * Set whether the screenshot should use grayscale or not.
+             */
+            public T setGrayscale(boolean grayscale) {
+                mGrayscale = grayscale;
+                return getThis();
+            }
+
+            /**
              * Each sub class should return itself to allow the builder to chain properly
              */
             abstract T getThis();
@@ -930,7 +952,7 @@
     /**
      * The arguments class used to make layer capture requests.
      *
-     * @see #nativeCaptureLayers(LayerCaptureArgs)
+     * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
      * @hide
      */
     public static class LayerCaptureArgs extends CaptureArgs {
@@ -1435,7 +1457,6 @@
         mName = in.readString8();
         mWidth = in.readInt();
         mHeight = in.readInt();
-        mDebugRelease = in.readBoolean();
 
         long object = 0;
         if (in.readInt() != 0) {
@@ -1454,12 +1475,8 @@
         dest.writeString8(mName);
         dest.writeInt(mWidth);
         dest.writeInt(mHeight);
-        dest.writeBoolean(mDebugRelease);
         if (mNativeObject == 0) {
             dest.writeInt(0);
-            if (mReleaseStack != null) {
-                Log.w(TAG, "Sending invalid " + this + " caused by:", mReleaseStack);
-            }
         } else {
             dest.writeInt(1);
         }
@@ -1471,13 +1488,6 @@
     }
 
     /**
-     * @hide
-     */
-    public void setDebugRelease(boolean debug) {
-        mDebugRelease = debug;
-    }
-
-    /**
      * Checks whether two {@link SurfaceControl} objects represent the same surface.
      *
      * @param other The other object to check
@@ -1547,9 +1557,6 @@
             nativeRelease(mNativeObject);
             mNativeObject = 0;
             mNativeHandle = 0;
-            if (mDebugRelease) {
-                mReleaseStack = new Throwable("released here");
-            }
             mCloseGuard.close();
         }
     }
@@ -1565,11 +1572,8 @@
     }
 
     private void checkNotReleased() {
-        if (mNativeObject == 0) {
-            Log.wtf(TAG, "Invalid " + this + " caused by:", mReleaseStack);
-            throw new NullPointerException(
-                "mNativeObject of " + this + " is null. Have you called release() already?");
-        }
+        if (mNativeObject == 0) throw new NullPointerException(
+                "Invalid " + this + ", mNativeObject is null. Have you called release() already?");
     }
 
     /**
@@ -2417,7 +2421,6 @@
     public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) {
         long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject);
         SurfaceControl sc = new SurfaceControl();
-        sc.mDebugRelease = mirrorOf.mDebugRelease;
         sc.assignNativeObject(nativeObj, "mirrorSurface");
         return sc;
     }
@@ -2965,15 +2968,6 @@
         }
 
         /**
-         * @hide
-         */
-        public Transaction reparentChildren(SurfaceControl sc, SurfaceControl newParent) {
-            checkPreconditions(sc);
-            nativeReparentChildren(mNativeObject, sc.mNativeObject, newParent.mNativeObject);
-            return this;
-        }
-
-        /**
          * Re-parents a given layer to a new parent. Children inherit transform (position, scaling)
          * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the
          * parent Surface.
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f603ef7..70ec2d4 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -877,6 +877,10 @@
 
         synchronized (mSurfaceControlLock) {
             mSurface.release();
+            if (mBlastBufferQueue != null) {
+                mBlastBufferQueue.destroy();
+                mBlastBufferQueue = null;
+            }
 
             if (mRtHandlingPositionUpdates) {
                 mRtReleaseSurfaces = true;
@@ -901,10 +905,6 @@
             transaction.remove(mBlastSurfaceControl);
             mBlastSurfaceControl = null;
         }
-        if (mBlastBufferQueue != null) {
-            mBlastBufferQueue.destroy();
-            mBlastBufferQueue = null;
-        }
     }
 
     private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5c0e156..e1ccc51 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -41,6 +41,7 @@
 import android.annotation.Nullable;
 import android.annotation.Size;
 import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.annotation.UiThread;
@@ -8739,14 +8740,17 @@
     /**
      * Populates a {@link ViewStructure} for content capture.
      *
-     * <p>This method is called after a view is that is eligible for content capture
-     * (for example, if it {@link #isImportantForAutofill()}, an intelligence service is enabled for
-     * the user, and the activity rendering the view is enabled for content capture) is laid out and
-     * is visible.
-     *
-     * <p>The populated structure is then passed to the service through
+     * <p>This method is called after a view that is eligible for content capture
+     * (for example, if it {@link #isImportantForContentCapture()}, an intelligence service is
+     * enabled for the user, and the activity rendering the view is enabled for content capture)
+     * is laid out and is visible. The populated structure is then passed to the service through
      * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)}.
      *
+     * <p>The default implementation of this method sets the most relevant properties based on
+     * related {@link View} methods, and views in the standard Android widgets library also
+     * override it to set their relevant properties. Therefore, if overriding this method, it
+     * is recommended to call {@code super.onProvideContentCaptureStructure()}.
+     *
      * <p><b>Note: </b>views that manage a virtual structure under this view must populate just
      * the node representing this view and return right away, then asynchronously report (not
      * necessarily in the UI thread) when the children nodes appear, disappear or have their text
@@ -8754,7 +8758,7 @@
      * {@link ContentCaptureSession#notifyViewAppeared(ViewStructure)},
      * {@link ContentCaptureSession#notifyViewDisappeared(AutofillId)}, and
      * {@link ContentCaptureSession#notifyViewTextChanged(AutofillId, CharSequence)}
-     * respectively. The structure for the a child must be created using
+     * respectively. The structure for a child must be created using
      * {@link ContentCaptureSession#newVirtualViewStructure(AutofillId, long)}, and the
      * {@code autofillId} for a child can be obtained either through
      * {@code childStructure.getAutofillId()} or
@@ -8899,7 +8903,7 @@
     /**
      * Called when assist structure is being retrieved from a view as part of
      * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} to
-     * generate additional virtual structure under this view.  The defaullt implementation
+     * generate additional virtual structure under this view.  The default implementation
      * uses {@link #getAccessibilityNodeProvider()} to try to generate this from the
      * view's virtual accessibility nodes, if any.  You can override this for a more
      * optimal implementation providing this data.
@@ -9030,7 +9034,8 @@
      *                  not be null or empty if a non-null listener is passed in.
      * @param listener The listener to use. This can be null to reset to the default behavior.
      */
-    public void setOnReceiveContentListener(@Nullable String[] mimeTypes,
+    public void setOnReceiveContentListener(
+            @SuppressLint("NullableCollection") @Nullable String[] mimeTypes,
             @Nullable OnReceiveContentListener listener) {
         if (listener != null) {
             Preconditions.checkArgument(mimeTypes != null && mimeTypes.length > 0,
@@ -9106,6 +9111,7 @@
      * @return The MIME types accepted by {@link #performReceiveContent} for this view (may
      * include patterns such as "image/*").
      */
+    @SuppressLint("NullableCollection")
     @Nullable
     public String[] getOnReceiveContentMimeTypes() {
         return mOnReceiveContentMimeTypes;
@@ -9803,20 +9809,6 @@
         }
     }
 
-    private void notifyAttachForDrawables() {
-        if (mBackground != null) mBackground.onAttached(this);
-        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
-            mForegroundInfo.mDrawable.onAttached(this);
-        }
-    }
-
-    private void notifyDetachForDrawables() {
-        if (mBackground != null) mBackground.onDetached(this);
-        if (mForegroundInfo != null && mForegroundInfo.mDrawable != null) {
-            mForegroundInfo.mDrawable.onDetached(this);
-        }
-    }
-
     private void setNotifiedContentCaptureAppeared() {
         mPrivateFlags4 |= PFLAG4_NOTIFIED_CONTENT_CAPTURE_APPEARED;
         mPrivateFlags4 &= ~PFLAG4_NOTIFIED_CONTENT_CAPTURE_DISAPPEARED;
@@ -10364,6 +10356,7 @@
                         for (int i = 0; i < childDrawIndex; i++) {
                             drawingOrderInParent += numViewsForAccessibility(preorderedList.get(i));
                         }
+                        preorderedList.clear();
                     } else {
                         final int childIndex = parentGroup.indexOfChild(viewAtDrawingLevel);
                         final boolean customOrder = parentGroup.isChildrenDrawingOrderEnabled();
@@ -20668,7 +20661,6 @@
 
         notifyEnterOrExitForAutoFillIfNeeded(true);
         notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
-        notifyAttachForDrawables();
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
@@ -20718,7 +20710,6 @@
 
         notifyEnterOrExitForAutoFillIfNeeded(false);
         notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
-        notifyDetachForDrawables();
     }
 
     /**
@@ -21367,6 +21358,10 @@
             int height = mBottom - mTop;
             int layerType = getLayerType();
 
+            // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+            // instead of being "stateful" like other RenderNode properties
+            renderNode.clearStretch();
+
             final RecordingCanvas canvas = renderNode.beginRecording(width, height);
 
             try {
@@ -22793,6 +22788,11 @@
         final Rect bounds = drawable.getBounds();
         final int width = bounds.width();
         final int height = bounds.height();
+
+        // Hacky hack: Reset any stretch effects as those are applied during the draw pass
+        // instead of being "stateful" like other RenderNode properties
+        renderNode.clearStretch();
+
         final RecordingCanvas canvas = renderNode.beginRecording(width, height);
 
         // Reverse left/top translation done by drawable canvas, which will
@@ -23847,7 +23847,6 @@
         if (mBackground != null) {
             if (isAttachedToWindow()) {
                 mBackground.setVisible(false, false);
-                mBackground.onDetached(this);
             }
             mBackground.setCallback(null);
             unscheduleDrawable(mBackground);
@@ -23897,7 +23896,6 @@
                 background.setState(getDrawableState());
             }
             if (isAttachedToWindow()) {
-                background.onAttached(this);
                 background.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
             }
 
@@ -24130,7 +24128,6 @@
         if (mForegroundInfo.mDrawable != null) {
             if (isAttachedToWindow()) {
                 mForegroundInfo.mDrawable.setVisible(false, false);
-                mForegroundInfo.mDrawable.onDetached(this);
             }
             mForegroundInfo.mDrawable.setCallback(null);
             unscheduleDrawable(mForegroundInfo.mDrawable);
@@ -24148,7 +24145,6 @@
             }
             applyForegroundTint();
             if (isAttachedToWindow()) {
-                foreground.onAttached(this);
                 foreground.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
             }
             // Set callback last, since the view may still be initializing.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 844fc26..036a703 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -115,7 +115,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.RenderNode;
-import android.graphics.drawable.BackgroundBlurDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.hardware.display.DisplayManager;
@@ -139,6 +138,7 @@
 import android.os.UserHandle;
 import android.sysprop.DisplayProperties;
 import android.util.AndroidRuntimeException;
+import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
@@ -158,6 +158,7 @@
 import android.view.View.FocusDirection;
 import android.view.View.MeasureSpec;
 import android.view.Window.OnContentApplyWindowInsetsListener;
+import android.view.WindowInsets.Side.InsetsSide;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -187,6 +188,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.SomeArgs;
@@ -313,7 +315,7 @@
      * In that case we receive a call back from {@link ActivityThread} and this flag is used to
      * preserve the initial value.
      *
-     * @see #performConfigurationChange(Configuration, Configuration, boolean, int)
+     * @see #performConfigurationChange(MergedConfiguration, boolean, int)
      */
     private boolean mForceNextConfigUpdate;
 
@@ -672,14 +674,6 @@
             new BackgroundBlurDrawable.Aggregator(this);
 
     /**
-     * @return {@link BackgroundBlurDrawable.Aggregator} for this instance.
-     */
-    @NonNull
-    public BackgroundBlurDrawable.Aggregator getBlurRegionAggregator() {
-        return mBlurRegionAggregator;
-    }
-
-    /**
      * @return {@link ImeFocusController} for this instance.
      */
     @NonNull
@@ -934,6 +928,33 @@
         }
     }
 
+    // TODO(b/161810301): Make this private after window layout is moved to the client side.
+    public static void computeWindowBounds(WindowManager.LayoutParams attrs, InsetsState state,
+            Rect displayFrame, Rect outBounds) {
+        final @InsetsType int typesToFit = attrs.getFitInsetsTypes();
+        final @InsetsSide int sidesToFit = attrs.getFitInsetsSides();
+        final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit);
+        final Rect df = displayFrame;
+        Insets insets = Insets.of(0, 0, 0, 0);
+        for (int i = types.size() - 1; i >= 0; i--) {
+            final InsetsSource source = state.peekSource(types.valueAt(i));
+            if (source == null) {
+                continue;
+            }
+            insets = Insets.max(insets, source.calculateInsets(
+                    df, attrs.isFitInsetsIgnoringVisibility()));
+        }
+        final int left = (sidesToFit & WindowInsets.Side.LEFT) != 0 ? insets.left : 0;
+        final int top = (sidesToFit & WindowInsets.Side.TOP) != 0 ? insets.top : 0;
+        final int right = (sidesToFit & WindowInsets.Side.RIGHT) != 0 ? insets.right : 0;
+        final int bottom = (sidesToFit & WindowInsets.Side.BOTTOM) != 0 ? insets.bottom : 0;
+        outBounds.set(df.left + left, df.top + top, df.right - right, df.bottom - bottom);
+    }
+
+    private Configuration getConfiguration() {
+        return mContext.getResources().getConfiguration();
+    }
+
     /**
      * We have one child
      */
@@ -1065,18 +1086,15 @@
                     controlInsetsForCompatibility(mWindowAttributes);
                     res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                             getHostVisibility(), mDisplay.getDisplayId(), userId,
-                            mInsetsController.getRequestedVisibility(), mTmpFrames.frame,
-                            inputChannel, mTempInsets, mTempControls);
+                            mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
+                            mTempControls);
                     if (mTranslator != null) {
-                        mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
                         mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
                     }
-                    setFrame(mTmpFrames.frame);
                 } catch (RemoteException e) {
                     mAdded = false;
                     mView = null;
                     mAttachInfo.mRootView = null;
-                    inputChannel = null;
                     mFallbackEventHandler.setView(null);
                     unscheduleTraversals();
                     setAccessibilityFocus(null, null);
@@ -1092,6 +1110,9 @@
                 mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
                 mInsetsController.onStateChanged(mTempInsets);
                 mInsetsController.onControlsChanged(mTempControls);
+                computeWindowBounds(mWindowAttributes, mInsetsController.getState(),
+                        getConfiguration().windowConfiguration.getBounds(), mTmpFrames.frame);
+                setFrame(mTmpFrames.frame);
                 if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
                 if (res < WindowManagerGlobal.ADD_OKAY) {
                     mAttachInfo.mRootView = null;
@@ -1365,7 +1386,7 @@
     }
 
     private int getNightMode() {
-        return mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        return getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
     }
 
     private void updateForceDarkMode() {
@@ -2341,7 +2362,7 @@
 
     /* package */ WindowInsets getWindowInsets(boolean forceConstruct) {
         if (mLastWindowInsets == null || forceConstruct) {
-            final Configuration config = mContext.getResources().getConfiguration();
+            final Configuration config = getConfiguration();
             mLastWindowInsets = mInsetsController.calculateInsets(
                     config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars,
                     mWindowAttributes.type, config.windowConfiguration.getWindowingMode(),
@@ -2477,7 +2498,7 @@
             mFullRedrawNeeded = true;
             mLayoutRequested = true;
 
-            final Configuration config = mContext.getResources().getConfiguration();
+            final Configuration config = getConfiguration();
             if (shouldUseDisplaySize(lp)) {
                 // NOTE -- system code, won't try to do compat mode.
                 Point size = new Point();
@@ -4770,7 +4791,7 @@
         }
         // TODO: Centralize this sanitization? Why do we let setting bad modes?
         // Alternatively, can we just let HWUI figure it out? Do we need to care here?
-        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+        if (!getConfiguration().isScreenWideColorGamut()) {
             colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
         }
         mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
@@ -10084,6 +10105,13 @@
         }
     }
 
+    /**
+     * Creates a background blur drawable for the backing {@link Surface}.
+     */
+    public BackgroundBlurDrawable createBackgroundBlurDrawable() {
+        return mBlurRegionAggregator.createBackgroundBlurDrawable(mContext);
+    }
+
     @Override
     public void onDescendantUnbufferedRequested() {
         mUnbufferedInputSource = mView.mUnbufferedInputSource;
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index f5aa97a..8b3fb2e 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -377,7 +378,8 @@
      * <p>Should only be set when the node is used for Autofill or Content Capture purposes - it
      * will be ignored when used for Assist.
      */
-    public void setOnReceiveContentMimeTypes(@Nullable String[] mimeTypes) {}
+    public void setOnReceiveContentMimeTypes(
+            @SuppressLint("NullableCollection") @Nullable String[] mimeTypes) {}
 
     /**
      * Sets the {@link android.text.InputType} bits of this node.
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 2168dd0..41c38a1 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -82,6 +82,7 @@
     @Nullable private Rect mTempRect;
     private final boolean mIsRound;
     @Nullable private final DisplayCutout mDisplayCutout;
+    @Nullable private final RoundedCorners mRoundedCorners;
 
     /**
      * In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -125,11 +126,12 @@
      * @hide
      * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)}
      */
-    public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect,
-            boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
+    @Deprecated
+    public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound,
+            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
         this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
                 createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
-                isRound, alwaysConsumeSystemBars, displayCutout, systemBars(),
+                isRound, alwaysConsumeSystemBars, displayCutout, null, systemBars(),
                 false /* compatIgnoreVisibility */);
     }
 
@@ -150,7 +152,8 @@
             boolean[] typeVisibilityMap,
             boolean isRound,
             boolean alwaysConsumeSystemBars, DisplayCutout displayCutout,
-            @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
+            RoundedCorners roundedCorners, @InsetsType int compatInsetsTypes,
+            boolean compatIgnoreVisibility) {
         mSystemWindowInsetsConsumed = typeInsetsMap == null;
         mTypeInsetsMap = mSystemWindowInsetsConsumed
                 ? new Insets[SIZE]
@@ -170,6 +173,8 @@
         mDisplayCutoutConsumed = displayCutout == null;
         mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
                 ? null : displayCutout;
+
+        mRoundedCorners = roundedCorners;
     }
 
     /**
@@ -182,6 +187,7 @@
                 src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
                 src.mTypeVisibilityMap, src.mIsRound,
                 src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
+                src.mRoundedCorners,
                 src.mCompatInsetsTypes,
                 src.mCompatIgnoreVisibility);
     }
@@ -235,12 +241,12 @@
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
         this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
-                systemBars(), false /* compatIgnoreVisibility */);
+                null, systemBars(), false /* compatIgnoreVisibility */);
     }
 
     /**
      * Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to
-     * {@link InsetsType#statusBars()} and {@link InsetsType#navigationBars()}, depending on the
+     * {@link Type#statusBars()} and {@link Type#navigationBars()}, depending on the
      * location of the inset.
      */
     private static Insets[] createCompatTypeMap(@Nullable Rect insets) {
@@ -321,9 +327,9 @@
 
     /**
      * Returns the insets of a specific set of windows causing insets, denoted by the
-     * {@code typeMask} bit mask of {@link InsetsType}s.
+     * {@code typeMask} bit mask of {@link Type}s.
      *
-     * @param typeMask Bit mask of {@link InsetsType}s to query the insets for.
+     * @param typeMask Bit mask of {@link Type}s to query the insets for.
      * @return The insets.
      */
     @NonNull
@@ -333,7 +339,7 @@
 
     /**
      * Returns the insets a specific set of windows can cause, denoted by the
-     * {@code typeMask} bit mask of {@link InsetsType}s, regardless of whether that type is
+     * {@code typeMask} bit mask of {@link Type}s, regardless of whether that type is
      * currently visible or not.
      *
      * <p>The insets represents the area of a a window that that <b>may</b> be partially
@@ -342,7 +348,7 @@
      * normally shown, but temporarily hidden, the inset returned here will still provide the inset
      * associated with the status bar being shown.</p>
      *
-     * @param typeMask Bit mask of {@link InsetsType}s to query the insets for.
+     * @param typeMask Bit mask of {@link Type}s to query the insets for.
      * @return The insets.
      *
      * @throws IllegalArgumentException If the caller tries to query {@link Type#ime()}. Insets are
@@ -362,7 +368,7 @@
      * Returns whether a set of windows that may cause insets is currently visible on screen,
      * regardless of whether it actually overlaps with this window.
      *
-     * @param typeMask Bit mask of {@link Type.InsetsType}s to query visibility status.
+     * @param typeMask Bit mask of {@link Type}s to query visibility status.
      * @return {@code true} if and only if all windows included in {@code typeMask} are currently
      *         visible on screen.
      */
@@ -466,7 +472,7 @@
     public boolean hasInsets() {
         return !getInsets(mTypeInsetsMap, all()).equals(Insets.NONE)
                 || !getInsets(mTypeMaxInsetsMap, all()).equals(Insets.NONE)
-                || mDisplayCutout != null;
+                || mDisplayCutout != null || mRoundedCorners != null;
     }
 
     /**
@@ -487,6 +493,23 @@
     }
 
     /**
+     * Returns the {@link RoundedCorner} of the given position if there is one.
+     *
+     * @param position the position of the rounded corner on the display. The value should be one of
+     *                 the following:
+     *                 {@link RoundedCorner#POSITION_TOP_LEFT},
+     *                 {@link RoundedCorner#POSITION_TOP_RIGHT},
+     *                 {@link RoundedCorner#POSITION_BOTTOM_RIGHT},
+     *                 {@link RoundedCorner#POSITION_BOTTOM_LEFT}.
+     * @return the rounded corner of the given position. Returns {@code null} if there is none or
+     *         the rounded corner area is not inside the application's bounds.
+     */
+    @Nullable
+    public RoundedCorner getRoundedCorner(@RoundedCorner.Position int position) {
+        return mRoundedCorners == null ? null : mRoundedCorners.getRoundedCorner(position);
+    }
+
+    /**
      * Returns a copy of this WindowInsets with the cutout fully consumed.
      *
      * @return A modified copy of this WindowInsets
@@ -501,7 +524,7 @@
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
-                null /* displayCutout */,
+                null /* displayCutout */, mRoundedCorners,
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
 
@@ -553,7 +576,7 @@
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
                 displayCutoutCopyConstructorArgument(this),
-                mCompatInsetsTypes, mCompatIgnoreVisibility);
+                mRoundedCorners, mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
 
     // TODO(b/119190588): replace @code with @link below
@@ -856,6 +879,8 @@
 
         result.append(mDisplayCutout != null ? "cutout=" + mDisplayCutout : "");
         result.append("\n    ");
+        result.append(mRoundedCorners != null ? "roundedCorners=" + mRoundedCorners : "");
+        result.append("\n    ");
         result.append(isRound() ? "round" : "");
         result.append("}");
         return result.toString();
@@ -947,6 +972,9 @@
                         : mDisplayCutout == null
                                 ? DisplayCutout.NO_CUTOUT
                                 : mDisplayCutout.inset(left, top, right, bottom),
+                mRoundedCorners == null
+                        ? RoundedCorners.NO_ROUNDED_CORNERS
+                        : mRoundedCorners.inset(left, top, right, bottom),
                 mCompatInsetsTypes, mCompatIgnoreVisibility);
     }
 
@@ -964,13 +992,14 @@
                 && Arrays.equals(mTypeInsetsMap, that.mTypeInsetsMap)
                 && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap)
                 && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
-                && Objects.equals(mDisplayCutout, that.mDisplayCutout);
+                && Objects.equals(mDisplayCutout, that.mDisplayCutout)
+                && Objects.equals(mRoundedCorners, that.mRoundedCorners);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
-                Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout,
+                Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
                 mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed,
                 mDisplayCutoutConsumed);
     }
@@ -1032,6 +1061,7 @@
         private boolean mStableInsetsConsumed = true;
 
         private DisplayCutout mDisplayCutout;
+        private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
 
         private boolean mIsRound;
         private boolean mAlwaysConsumeSystemBars;
@@ -1057,6 +1087,7 @@
             mSystemInsetsConsumed = insets.mSystemWindowInsetsConsumed;
             mStableInsetsConsumed = insets.mStableInsetsConsumed;
             mDisplayCutout = displayCutoutCopyConstructorArgument(insets);
+            mRoundedCorners = insets.mRoundedCorners;
             mIsRound = insets.mIsRound;
             mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
         }
@@ -1148,7 +1179,7 @@
          *
          * @see #getInsets(int)
          *
-         * @param typeMask The bitmask of {@link InsetsType} to set the insets for.
+         * @param typeMask The bitmask of {@link Type} to set the insets for.
          * @param insets The insets to set.
          *
          * @return itself
@@ -1172,7 +1203,7 @@
          *
          * @see #getInsetsIgnoringVisibility(int)
          *
-         * @param typeMask The bitmask of {@link InsetsType} to set the insets for.
+         * @param typeMask The bitmask of {@link Type} to set the insets for.
          * @param insets The insets to set.
          *
          * @return itself
@@ -1201,7 +1232,7 @@
          *
          * @see #isVisible(int)
          *
-         * @param typeMask The bitmask of {@link InsetsType} to set the visibility for.
+         * @param typeMask The bitmask of {@link Type} to set the visibility for.
          * @param visible Whether to mark the windows as visible or not.
          *
          * @return itself
@@ -1262,6 +1293,29 @@
 
         /** @hide */
         @NonNull
+        public Builder setRoundedCorners(RoundedCorners roundedCorners) {
+            mRoundedCorners = roundedCorners != null
+                    ? roundedCorners : RoundedCorners.NO_ROUNDED_CORNERS;
+            return this;
+        }
+
+        /**
+         * Sets the rounded corner of given position.
+         *
+         * @see #getRoundedCorner(int)
+         * @param position the position of this rounded corner
+         * @param roundedCorner the rounded corner or null if there is none
+         * @return itself
+         */
+        @NonNull
+        public Builder setRoundedCorner(@RoundedCorner.Position int position,
+                @Nullable RoundedCorner roundedCorner) {
+            mRoundedCorners.setRoundedCorner(position, roundedCorner);
+            return this;
+        }
+
+        /** @hide */
+        @NonNull
         public Builder setRound(boolean round) {
             mIsRound = round;
             return this;
@@ -1283,7 +1337,7 @@
         public WindowInsets build() {
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
-                    mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout,
+                    mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners,
                     systemBars(), false /* compatIgnoreVisibility */);
         }
     }
diff --git a/core/java/android/view/WindowInsetsAnimation.java b/core/java/android/view/WindowInsetsAnimation.java
index cf5e7e3..ab5b5ba 100644
--- a/core/java/android/view/WindowInsetsAnimation.java
+++ b/core/java/android/view/WindowInsetsAnimation.java
@@ -61,7 +61,7 @@
     }
 
     /**
-     * @return The bitmask of {@link WindowInsets.Type.InsetsType}s that are animating.
+     * @return The bitmask of {@link WindowInsets.Type}s that are animating.
      */
     @WindowInsets.Type.InsetsType
     public int getTypeMask() {
@@ -140,7 +140,7 @@
     }
 
     /**
-     * Set fraction of the progress if {@link WindowInsets.Type.InsetsType} animation is
+     * Set fraction of the progress if {@link WindowInsets.Type} animation is
      * controlled by the app.
      * <p>
      * Note: This should only be used for testing, as the system fills in the fraction for the
@@ -159,7 +159,7 @@
     /**
      * Retrieves the translucency of the windows that are animating.
      *
-     * @return Alpha of windows that cause insets of type {@link WindowInsets.Type.InsetsType}.
+     * @return Alpha of windows that cause insets of type {@link WindowInsets.Type}.
      */
     @FloatRange(from = 0f, to = 1f)
     public float getAlpha() {
@@ -174,8 +174,7 @@
      * {@link WindowInsetsAnimationController#setInsetsAndAlpha(Insets, float, float)} is being
      * used.
      * </p>
-     * @param alpha Alpha of windows that cause insets of type
-     *              {@link WindowInsets.Type.InsetsType}.
+     * @param alpha Alpha of windows that cause insets of type {@link WindowInsets.Type}.
      * @see #getAlpha()
      */
     public void setAlpha(@FloatRange(from = 0f, to = 1f) float alpha) {
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index b61fa36..140a9a8 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -46,8 +46,8 @@
      * to redraw because of an {@link EditorInfo} change, or when the window is starting up.
      *
      * @param controller The controller to control the inset animation.
-     * @param types The {@link InsetsType}s it was able to gain control over. Note that this may be
-     *              different than the types passed into
+     * @param types The {@link WindowInsets.Type}s it was able to gain control over. Note that this
+     *              may be different than the types passed into
      *              {@link WindowInsetsController#controlWindowInsetsAnimation} in case the window
      *              wasn't able to gain the controls because it wasn't the IME target or not
      *              currently the window that's controlling the system bars.
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 792b974..6578e9b 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -99,7 +99,7 @@
     float getCurrentAlpha();
 
     /**
-     * @return The {@link InsetsType}s this object is currently controlling.
+     * @return The {@link WindowInsets.Type}s this object is currently controlling.
      */
     @InsetsType int getTypes();
 
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index fb9bcbd..227b9f4 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -67,13 +67,26 @@
     int APPEARANCE_LIGHT_NAVIGATION_BARS = 1 << 4;
 
     /**
+     * Makes status bars semi-transparent with dark background and light foreground.
+     * @hide
+     */
+    int APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS = 1 << 5;
+
+    /**
+     * Makes navigation bars semi-transparent with dark background and light foreground.
+     * @hide
+     */
+    int APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS = 1 << 6;
+
+    /**
      * Determines the appearance of system bars.
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {APPEARANCE_OPAQUE_STATUS_BARS, APPEARANCE_OPAQUE_NAVIGATION_BARS,
             APPEARANCE_LOW_PROFILE_BARS, APPEARANCE_LIGHT_STATUS_BARS,
-            APPEARANCE_LIGHT_NAVIGATION_BARS})
+            APPEARANCE_LIGHT_NAVIGATION_BARS, APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+            APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS})
     @interface Appearance {
     }
 
@@ -142,7 +155,7 @@
      * change as soon as the window gains control. The app can listen to the event by observing
      * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
      *
-     * @param types A bitmask of {@link InsetsType} specifying what windows the app
+     * @param types A bitmask of {@link WindowInsets.Type} specifying what windows the app
      *              would like to make appear on screen.
      */
     void show(@InsetsType int types);
@@ -154,7 +167,7 @@
      * change as soon as the window gains control. The app can listen to the event by observing
      * {@link View#onApplyWindowInsets} and checking visibility with {@link WindowInsets#isVisible}.
      *
-     * @param types A bitmask of {@link InsetsType} specifying what windows the app
+     * @param types A bitmask of {@link WindowInsets.Type} specifying what windows the app
      *              would like to make disappear.
      */
     void hide(@InsetsType int types);
@@ -163,7 +176,7 @@
      * Lets the application control window inset animations in a frame-by-frame manner by modifying
      * the position of the windows in the system causing insets directly.
      *
-     * @param types The {@link InsetsType}s the application has requested to control.
+     * @param types The {@link WindowInsets.Type}s the application has requested to control.
      * @param durationMillis Duration of animation in
      *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
      *                       animation doesn't have a predetermined duration. This value will be
@@ -199,7 +212,7 @@
      * setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS)
      * </pre>
      *
-     * @param appearance Bitmask of {@link Appearance} flags.
+     * @param appearance Bitmask of appearance flags.
      * @param mask Specifies which flags of appearance should be changed.
      * @see #getSystemBarsAppearance
      */
@@ -280,11 +293,11 @@
             @NonNull OnControllableInsetsChangedListener listener);
 
     /**
-     * Listener to be notified when the set of controllable {@link InsetsType} controlled by a
-     * {@link WindowInsetsController} changes.
+     * Listener to be notified when the set of controllable {@link WindowInsets.Type} controlled by
+     * a {@link WindowInsetsController} changes.
      * <p>
-     * Once a {@link InsetsType} becomes controllable, the app will be able to control the window
-     * that is causing this type of insets by calling {@link #controlWindowInsetsAnimation}.
+     * Once a {@link WindowInsets.Type} becomes controllable, the app will be able to control the
+     * window that is causing this type of insets by calling {@link #controlWindowInsetsAnimation}.
      * <p>
      * Note: When listening to controllability of the {@link Type#ime},
      * {@link #controlWindowInsetsAnimation} may still fail in case the {@link InputMethodService}
@@ -297,12 +310,12 @@
     interface OnControllableInsetsChangedListener {
 
         /**
-         * Called when the set of controllable {@link InsetsType} changes.
+         * Called when the set of controllable {@link WindowInsets.Type} changes.
          *
-         * @param controller The controller for which the set of controllable {@link InsetsType}s
-         *                   are changing.
-         * @param typeMask Bitwise type-mask of the {@link InsetsType}s the controller is currently
-         *                 able to control.
+         * @param controller The controller for which the set of controllable
+         *                   {@link WindowInsets.Type}s are changing.
+         * @param typeMask Bitwise type-mask of the {@link WindowInsets.Type}s the controller is
+         *                 currently able to control.
          */
         void onControllableInsetsChanged(@NonNull WindowInsetsController controller,
                 @InsetsType int typeMask);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1327f9c..9e87c95 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -83,6 +83,7 @@
 import android.Manifest.permission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -99,6 +100,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1509,13 +1511,7 @@
          *  Use {@link #dimAmount} to control the amount of dim. */
         public static final int FLAG_DIM_BEHIND        = 0x00000002;
 
-        /** Window flag: enable blurring behind this window.
-         * To set the amount of blur, use {@link #backgroundBlurRadius}
-         *
-         * @hide
-         */
-        @RequiresPermission(permission.USE_BACKGROUND_BLUR)
-        @SystemApi
+        /** Window flag: enable blur behind for this window. */
         public static final int FLAG_BLUR_BEHIND        = 0x00000004;
 
         /** Window flag: this window won't ever get key input focus, so the
@@ -1555,17 +1551,26 @@
          *   <li><b>Fully transparent windows</b>: This window has {@link LayoutParams#alpha} equal
          *   to 0.
          *   <li><b>One SAW window with enough transparency</b>: This window is of type {@link
-         *   #TYPE_APPLICATION_OVERLAY}, has {@link LayoutParams#alpha} below or equal to <b>0.8</b>
-         *   and it's the <b>only</b> window of type {@link #TYPE_APPLICATION_OVERLAY} from this UID
-         *   in the touch path.
+         *   #TYPE_APPLICATION_OVERLAY}, has {@link LayoutParams#alpha} below or equal to the
+         *   <a href="#MaximumOpacity">maximum obscuring opacity</a> (see below) and it's the
+         *   <b>only</b> window of type {@link #TYPE_APPLICATION_OVERLAY} from this UID in the touch
+         *   path.
          *   <li><b>Multiple SAW windows with enough transparency</b>: The multiple overlapping
          *   {@link #TYPE_APPLICATION_OVERLAY} windows in the
          *   touch path from this UID have a <b>combined obscuring opacity</b> below or equal to
-         *   <b>0.8</b>. See section below on how to compute this value.
+         *   the <a href="#MaximumOpacity">maximum obscuring opacity</a>. See section
+         *   <a href="#ObscuringOpacity">Combined obscuring opacity</a> below on how to compute this
+         *   value.
          * </ol>
          * <p>If none of these cases hold, the touch will not be delivered and a message will be
          * logged to logcat.</p>
          *
+         * <a name="MaximumOpacity"></a>
+         * <h3>Maximum obscuring opacity</h3>
+         * <p>This value is <b>0.8</b>. Apps that want to gather this value from the system rather
+         * than hard-coding it might want to use {@link
+         * android.hardware.input.InputManager#getMaximumObscuringOpacityForTouch()}.</p>
+         *
          * <a name="ObscuringOpacity"></a>
          * <h3>Combined obscuring opacity</h3>
          *
@@ -2846,12 +2851,14 @@
 
         /**
          * The token of {@link android.app.WindowContext}. It is usually a
-         * {@link android.app.WindowTokenClient} and is used for updating
-         * {@link android.content.res.Resources} from {@link Configuration} propagated from the
-         * server side.
+         * {@link android.app.WindowTokenClient} and is used for associating the params with an
+         * existing node in the WindowManager hierarchy and getting the corresponding
+         * {@link Configuration} and {@link android.content.res.Resources} values with updates
+         * propagated from the server side.
          *
          * @hide
          */
+        @Nullable
         public IBinder mWindowContextToken = null;
 
         /**
@@ -3224,15 +3231,16 @@
         public boolean preferMinimalPostProcessing = false;
 
         /**
-         * When {@link FLAG_BLUR_BEHIND} is set, this is the amount of blur in pixels that this
-         * window will use to blur behind itself.
-         * The range is from 0, which means no blur, to 150.
+         * Specifies the amount of blur to be used to blur everything behind the window.
+         * The effect is similar to the dimAmount, but instead of dimming, the content behind
+         * will be blurred.
          *
-         * @hide
+         * The blur behind radius range starts at 0, which means no blur, and increases until 150
+         * for the densest blur.
+         *
+         * @see #FLAG_BLUR_BEHIND
          */
-        @SystemApi
-        @RequiresPermission(permission.USE_BACKGROUND_BLUR)
-        public int backgroundBlurRadius = 0;
+        public int blurBehindRadius = 0;
 
         /**
          * The color mode requested by this window. The target display may
@@ -3326,8 +3334,8 @@
         /**
          * Specifies types of insets that this window should avoid overlapping during layout.
          *
-         * @param types which types of insets that this window should avoid. The initial value of
-         *              this object includes all system bars.
+         * @param types which {@link WindowInsets.Type}s of insets that this window should avoid.
+         *              The initial value of this object includes all system bars.
          */
         public void setFitInsetsTypes(@InsetsType int types) {
             mFitInsetsTypes = types;
@@ -3401,7 +3409,7 @@
         }
 
         /**
-         * @return the insets types that this window is avoiding overlapping.
+         * @return the {@link WindowInsets.Type}s that this window is avoiding overlapping.
          */
         public @InsetsType int getFitInsetsTypes() {
             return mFitInsetsTypes;
@@ -3538,6 +3546,37 @@
             return userActivityTimeout;
         }
 
+        /**
+         * Sets the {@link android.app.WindowContext} token.
+         *
+         * @see #getWindowContextToken()
+         *
+         * @hide
+         */
+        @TestApi
+        public final void setWindowContextToken(@NonNull IBinder token) {
+            mWindowContextToken = token;
+        }
+
+        /**
+         * Gets the {@link android.app.WindowContext} token.
+         *
+         * The token is usually a {@link android.app.WindowTokenClient} and is used for associating
+         * the params with an existing node in the WindowManager hierarchy and getting the
+         * corresponding {@link Configuration} and {@link android.content.res.Resources} values with
+         * updates propagated from the server side.
+         *
+         * @see android.app.WindowTokenClient
+         * @see Context#createWindowContext(Display, int, Bundle)
+         *
+         * @hide
+         */
+        @TestApi
+        @Nullable
+        public final IBinder getWindowContextToken() {
+            return mWindowContextToken;
+        }
+
         public int describeContents() {
             return 0;
         }
@@ -3590,7 +3629,7 @@
             out.writeInt(mFitInsetsSides);
             out.writeBoolean(mFitInsetsIgnoringVisibility);
             out.writeBoolean(preferMinimalPostProcessing);
-            out.writeInt(backgroundBlurRadius);
+            out.writeInt(blurBehindRadius);
             if (providesInsetsTypes != null) {
                 out.writeInt(providesInsetsTypes.length);
                 out.writeIntArray(providesInsetsTypes);
@@ -3659,7 +3698,7 @@
             mFitInsetsSides = in.readInt();
             mFitInsetsIgnoringVisibility = in.readBoolean();
             preferMinimalPostProcessing = in.readBoolean();
-            backgroundBlurRadius = in.readInt();
+            blurBehindRadius = in.readInt();
             int insetsTypesLength = in.readInt();
             if (insetsTypesLength > 0) {
                 providesInsetsTypes = new int[insetsTypesLength];
@@ -3904,8 +3943,8 @@
                 changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED;
             }
 
-            if (backgroundBlurRadius != o.backgroundBlurRadius) {
-                backgroundBlurRadius = o.backgroundBlurRadius;
+            if (blurBehindRadius != o.blurBehindRadius) {
+                blurBehindRadius = o.blurBehindRadius;
                 changes |= BACKGROUND_BLUR_RADIUS_CHANGED;
             }
 
@@ -4072,9 +4111,9 @@
                 sb.append(" preferMinimalPostProcessing=");
                 sb.append(preferMinimalPostProcessing);
             }
-            if (backgroundBlurRadius != 0) {
-                sb.append(" backgroundBlurRadius=");
-                sb.append(backgroundBlurRadius);
+            if (blurBehindRadius != 0) {
+                sb.append(" blurBehindRadius=");
+                sb.append(blurBehindRadius);
             }
             sb.append(System.lineSeparator());
             sb.append(prefix).append("  fl=").append(
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index dd0ab65..47ac1ee 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -116,14 +116,6 @@
      */
     public static final int RELAYOUT_INSETS_PENDING = 0x1;
 
-    /**
-     * Flag for relayout: the client may be currently using the current surface,
-     * so if it is to be destroyed as a part of the relayout the destroy must
-     * be deferred until later.  The client will call performDeferredDestroy()
-     * when it is okay.
-     */
-    public static final int RELAYOUT_DEFER_SURFACE_DESTROY = 0x2;
-
     public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
     public static final int ADD_FLAG_APP_VISIBLE = 0x2;
     public static final int ADD_FLAG_USE_TRIPLE_BUFFERING = 0x4;
@@ -147,7 +139,6 @@
     public static final int ADD_INVALID_DISPLAY = -9;
     public static final int ADD_INVALID_TYPE = -10;
     public static final int ADD_INVALID_USER = -11;
-    public static final int ADD_TOO_MANY_TOKENS = -12;
 
     @UnsupportedAppUsage
     private static WindowManagerGlobal sDefaultWindowManager;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index dd56c15..b85f1079 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -24,7 +24,7 @@
 import android.graphics.Region;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.window.ClientWindowFrames;
@@ -135,7 +135,7 @@
      */
     @Override
     public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
-            int viewVisibility, int displayId, InsetsState requestedVisibility, Rect outFrame,
+            int viewVisibility, int displayId, InsetsState requestedVisibility,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
@@ -171,10 +171,10 @@
     @Override
     public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
             int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
-            Rect outFrame, InputChannel outInputChannel, InsetsState outInsetsState,
+            InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibility,
-                outFrame, outInputChannel, outInsetsState, outActiveControls);
+                outInputChannel, outInsetsState, outActiveControls);
     }
 
     @Override
@@ -466,7 +466,7 @@
     }
 
     @Override
-    public ImpressionToken generateImpressionToken(IWindow window, Rect boundsInWindow,
+    public ScreenshotHash generateScreenshotHash(IWindow window, Rect boundsInWindow,
             String hashAlgorithm) {
         return null;
     }
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 415b3a7..37220fe 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -164,12 +164,12 @@
 
     /**
      * Default implementation calls {@link #finishComposingText()} and
-     * {@code setImeTemporarilyConsumesInput(false)}.
+     * {@code setImeConsumesInput(false)}.
      */
     @CallSuper
     public void closeConnection() {
         finishComposingText();
-        setImeTemporarilyConsumesInput(false);
+        setImeConsumesInput(false);
     }
 
     /**
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 2df75f6..bde4cb7 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -299,7 +299,7 @@
      * {@link EditorInfo} is using {@link Configuration#ORIENTATION_PORTRAIT} mode.
      * @hide
      */
-    public static final int IME_FLAG_APP_WINDOW_PORTRAIT = 0x80000000;
+    public static final int IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT = 0x00000001;
 
     /**
      * Generic unspecified type for {@link #imeOptions}.
@@ -321,7 +321,6 @@
      *                               1 1 IME_ACTION_NEXT
      *                               11  IME_ACTION_DONE
      *                               111 IME_ACTION_PREVIOUS
-     *          1                        IME_FLAG_APP_WINDOW_PORTRAIT
      *         1                         IME_FLAG_NO_PERSONALIZED_LEARNING
      *        1                          IME_FLAG_NO_FULLSCREEN
      *       1                           IME_FLAG_NAVIGATE_PREVIOUS
@@ -356,7 +355,7 @@
      * Masks for {@link internalImeOptions}
      *
      * <pre>
-     *  1                                IME_FLAG_APP_WINDOW_PORTRAIT
+     *                                 1 IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT
      * |-------|-------|-------|-------|</pre>
      */
 
@@ -984,6 +983,7 @@
         dest.writeInt(inputType);
         dest.writeInt(imeOptions);
         dest.writeString(privateImeOptions);
+        dest.writeInt(internalImeOptions);
         TextUtils.writeToParcel(actionLabel, dest, flags);
         dest.writeInt(actionId);
         dest.writeInt(initialSelStart);
@@ -1019,6 +1019,7 @@
                     res.inputType = source.readInt();
                     res.imeOptions = source.readInt();
                     res.privateImeOptions = source.readString();
+                    res.internalImeOptions = source.readInt();
                     res.actionLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.actionId = source.readInt();
                     res.initialSelStart = source.readInt();
diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
index 73962d7..10fd0e0 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -82,6 +82,7 @@
     public static InlineSuggestionInfo newInlineSuggestionInfo(
             @NonNull InlinePresentationSpec presentationSpec,
             @NonNull @Source String source,
+            @SuppressLint("NullableCollection")
             @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned) {
         return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned);
     }
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index a76d46d1..34a60bb 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -858,7 +858,7 @@
     boolean reportFullscreenMode(boolean enabled);
 
     /**
-     * Have the editor perform spell checking around the current selection.
+     * Have the editor perform spell checking for the full content.
      *
      * <p>The editor can ignore this method call if it does not support spell checking.
      *
@@ -1004,20 +1004,19 @@
             @Nullable Bundle opts);
 
     /**
-     * Called by the input method to indicate that it temporarily consumes all input for itself,
-     * or no longer does so.
+     * Called by the input method to indicate that it consumes all input for itself, or no longer
+     * does so.
      *
-     * <p>Editors should reflect that they are temporarily not receiving input by hiding the
-     * cursor if {@code imeTemporarilyConsumesInput} is {@code true}, and resume showing the
-     * cursor if it is {@code false}.
+     * <p>Editors should reflect that they are not receiving input by hiding the cursor if
+     * {@code imeConsumesInput} is {@code true}, and resume showing the cursor if it is
+     * {@code false}.
      *
-     * @param imeTemporarilyConsumesInput {@code true} when the IME is temporarily consuming input
-     * and the cursor should be hidden, {@code false} when input to the editor resumes and the
-     * cursor should be shown again.
+     * @param imeConsumesInput {@code true} when the IME is consuming input and the cursor should be
+     * hidden, {@code false} when input to the editor resumes and the cursor should be shown again.
      * @return {@code true} on success, {@code false} if the input connection is no longer valid, or
      * the protocol is not supported.
      */
-    default boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
+    default boolean setImeConsumesInput(boolean imeConsumesInput) {
         return false;
     }
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index b29149f..b1501a4 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -341,7 +341,7 @@
      * @throws NullPointerException if the target is {@code null}.
      */
     @Override
-    public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
-        return mTarget.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput);
+    public boolean setImeConsumesInput(boolean imeConsumesInput) {
+        return mTarget.setImeConsumesInput(imeConsumesInput);
     }
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 5140c09..90c8e17 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2307,7 +2307,9 @@
     public void removeImeSurface(IBinder windowToken) {
         synchronized (mH) {
             try {
-                mService.removeImeSurfaceFromWindow(windowToken);
+                final Completable.Void value = Completable.createVoid();
+                mService.removeImeSurfaceFromWindow(windowToken, ResultCallbacks.of(value));
+                Completable.getResult(value);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -3239,7 +3241,9 @@
     @Deprecated
     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
         try {
-            mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
+            final Completable.Void value = Completable.createVoid();
+            mService.setAdditionalInputMethodSubtypes(imiId, subtypes, ResultCallbacks.of(value));
+            Completable.getResult(value);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/OWNERS b/core/java/android/view/inputmethod/OWNERS
index e6a04da..d7db7c7 100644
--- a/core/java/android/view/inputmethod/OWNERS
+++ b/core/java/android/view/inputmethod/OWNERS
@@ -2,3 +2,5 @@
 set noparent
 
 include /services/core/java/com/android/server/inputmethod/OWNERS
+
+per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
index 0a1aea3..5980cb6 100644
--- a/core/java/android/view/textservice/TextServicesManager.java
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -187,7 +187,8 @@
      * @return The spell checker session of the spell checker.
      */
     @Nullable
-    public SpellCheckerSession newSpellCheckerSession(@Nullable Bundle bundle,
+    public SpellCheckerSession newSpellCheckerSession(
+            @SuppressLint("NullableCollection") @Nullable Bundle bundle,
             @SuppressLint("UseIcu") @Nullable Locale locale,
             @NonNull SpellCheckerSessionListener listener,
             @SuppressLint("ListenerLast") boolean referToSpellCheckerLanguageSettings,
@@ -277,6 +278,7 @@
      * @return The list of currently enabled spell checkers.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<SpellCheckerInfo> getEnabledSpellCheckersList() {
         final SpellCheckerInfo[] enabledSpellCheckers = getEnabledSpellCheckers();
         return enabledSpellCheckers != null ? Arrays.asList(enabledSpellCheckers) : null;
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
index 675f32b..22c3e57 100644
--- a/core/java/android/view/translation/Translator.java
+++ b/core/java/android/view/translation/Translator.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.service.translation.ITranslationCallback;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -36,9 +37,11 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
 /**
  * The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}.
@@ -295,4 +298,49 @@
     }
 
     // TODO: add methods for UI-toolkit case.
+    /** @hide */
+    public void requestUiTranslate(@NonNull List<TranslationRequest> requests,
+            @NonNull Consumer<TranslationResponse> responseCallback) {
+        if (mDirectServiceBinder == null) {
+            Log.wtf(TAG, "Translator created without proper initialization.");
+            return;
+        }
+        final android.service.translation.TranslationRequest request =
+                new android.service.translation.TranslationRequest
+                        .Builder(getNextRequestId(), mSourceSpec, mDestSpec, requests)
+                        .build();
+        final ITranslationCallback callback =
+                new TranslationResponseCallbackImpl(responseCallback);
+        try {
+            mDirectServiceBinder.onTranslationRequest(request, mId, callback, null);
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException calling flushRequest");
+        }
+    }
+
+    private static class TranslationResponseCallbackImpl extends ITranslationCallback.Stub {
+
+        private final WeakReference<Consumer<TranslationResponse>> mResponseCallback;
+
+        TranslationResponseCallbackImpl(Consumer<TranslationResponse> responseCallback) {
+            mResponseCallback = new WeakReference<>(responseCallback);
+        }
+
+        @Override
+        public void onTranslationComplete(TranslationResponse response) throws RemoteException {
+            provideTranslationResponse(response);
+        }
+
+        @Override
+        public void onError() throws RemoteException {
+            provideTranslationResponse(null);
+        }
+
+        private void provideTranslationResponse(TranslationResponse response) {
+            final Consumer<TranslationResponse> responseCallback = mResponseCallback.get();
+            if (responseCallback != null) {
+                responseCallback.accept(response);
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index a810c2e..b49d3c0 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -16,35 +16,269 @@
 
 package android.view.translation;
 
+import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_FINISHED;
+import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_PAUSED;
+import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_RESUMED;
+import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_STARTED;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
 import android.app.Activity;
 import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.view.View;
 import android.view.autofill.AutofillId;
+import android.view.translation.UiTranslationManager.UiTranslationState;
 
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
- * A controller to manage the ui translation requests.
+ * A controller to manage the ui translation requests for the {@link Activity}.
  *
  * @hide
  */
 public class UiTranslationController {
 
     private static final String TAG = "UiTranslationController";
-
+    @NonNull
     private final Activity mActivity;
-
+    @NonNull
     private final Context mContext;
+    @NonNull
+    private final Object mLock = new Object();
+
+    // Each Translator is distinguished by sourceSpec and desSepc.
+    @NonNull
+    private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Translator> mTranslators;
+    @NonNull
+    private final ArrayMap<AutofillId, WeakReference<View>> mViews;
+    @NonNull
+    private final HandlerThread mWorkerThread;
+    @NonNull
+    private final Handler mWorkerHandler;
 
     public UiTranslationController(Activity activity, Context context) {
         mActivity = activity;
         mContext = context;
+        mViews = new ArrayMap<>();
+        mTranslators = new ArrayMap<>();
+
+        mWorkerThread =
+                new HandlerThread("UiTranslationController_" + mActivity.getComponentName(),
+                        Process.THREAD_PRIORITY_FOREGROUND);
+        mWorkerThread.start();
+        mWorkerHandler = mWorkerThread.getThreadHandler();
     }
 
     /**
      * Update the Ui translation state.
      */
-    public void updateUiTranslationState(int state, TranslationSpec sourceSpec,
+    public void updateUiTranslationState(@UiTranslationState int state, TranslationSpec sourceSpec,
             TranslationSpec destSpec, List<AutofillId> views) {
-        // Implement it. Deal with the each states
+        if (!mActivity.isResumed()) {
+            return;
+        }
+        switch (state) {
+            case STATE_UI_TRANSLATION_STARTED:
+                final Pair<TranslationSpec, TranslationSpec> specs =
+                        new Pair<>(sourceSpec, destSpec);
+                if (!mTranslators.containsKey(specs)) {
+                    mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
+                            UiTranslationController::createTranslatorAndStart,
+                            UiTranslationController.this, sourceSpec, destSpec, views));
+                } else {
+                    onUiTranslationStarted(mTranslators.get(specs), views);
+                }
+                break;
+            case STATE_UI_TRANSLATION_PAUSED:
+                runForEachView(View::onPauseUiTranslation);
+                break;
+            case STATE_UI_TRANSLATION_RESUMED:
+                runForEachView(View::onRestoreUiTranslation);
+                break;
+            case STATE_UI_TRANSLATION_FINISHED:
+                destroyTranslators();
+                runForEachView(View::onFinishUiTranslation);
+                synchronized (mLock) {
+                    mViews.clear();
+                }
+                break;
+            default:
+                Log.w(TAG, "onAutoTranslationStateChange(): unknown state: " + state);
+        }
+    }
+
+    /**
+     * Called when the Activity is destroyed.
+     */
+    public void onActivityDestroyed() {
+        synchronized (mLock) {
+            mViews.clear();
+            destroyTranslators();
+            mWorkerThread.quitSafely();
+        }
+    }
+
+    /**
+     * The method is used by {@link Translator}, it will be called when the translation is done. The
+     * translation result can be get from here.
+     */
+    public void onTranslationCompleted(TranslationResponse response) {
+        if (response == null || response.getTranslationStatus()
+                != TranslationResponse.TRANSLATION_STATUS_SUCCESS) {
+            Log.w(TAG, "Fail result from TranslationService, response: " + response);
+            return;
+        }
+        final List<TranslationRequest> translatedResult = response.getTranslations();
+        onTranslationCompleted(translatedResult);
+    }
+
+    private void onTranslationCompleted(List<TranslationRequest> translatedResult) {
+        if (!mActivity.isResumed()) {
+            return;
+        }
+        final int resultCount = translatedResult.size();
+        synchronized (mLock) {
+            for (int i = 0; i < resultCount; i++) {
+                final TranslationRequest request = translatedResult.get(i);
+                final AutofillId autofillId = request.getAutofillId();
+                if (autofillId == null) {
+                    continue;
+                }
+                final View view = mViews.get(autofillId).get();
+                if (view == null) {
+                    Log.w(TAG, "onTranslationCompleted: the Veiew for autofill id " + autofillId
+                            + " may be gone.");
+                    continue;
+                }
+                mActivity.runOnUiThread(() -> view.onTranslationComplete(request));
+            }
+        }
+    }
+
+    /**
+     * Called when there is an ui translation request comes to request view translation.
+     */
+    @WorkerThread
+    private void createTranslatorAndStart(TranslationSpec sourceSpec, TranslationSpec destSpec,
+            List<AutofillId> views) {
+        // Create Translator
+        final Translator translator = createTranslatorIfNeeded(sourceSpec, destSpec);
+        if (translator == null) {
+            Log.w(TAG, "Can not create Translator for sourceSpec:" + sourceSpec + " destSpec:"
+                    + destSpec);
+            return;
+        }
+        onUiTranslationStarted(translator, views);
+    }
+
+    @WorkerThread
+    private void sendTranslationRequest(Translator translator,
+            ArrayList<TranslationRequest> requests) {
+        translator.requestUiTranslate(requests, this::onTranslationCompleted);
+    }
+
+    /**
+     * Called when there is an ui translation request comes to request view translation.
+     */
+    private void onUiTranslationStarted(Translator translator, List<AutofillId> views) {
+        synchronized (mLock) {
+            // Find Views collect the translation data
+            // TODO(b/178084101): try to optimize, e.g. to this in a single traversal
+            final int viewCounts = views.size();
+            final ArrayList<TranslationRequest> requests = new ArrayList<>();
+            for (int i = 0; i < viewCounts; i++) {
+                final AutofillId viewAutofillId = views.get(i);
+                final View view = mActivity.findViewByAutofillIdTraversal(viewAutofillId);
+                if (view == null) {
+                    Log.w(TAG, "Can not find the View for autofill id= " + viewAutofillId);
+                    continue;
+                }
+                mViews.put(viewAutofillId, new WeakReference<>(view));
+                mActivity.runOnUiThread(() -> {
+                    final TranslationRequest translationRequest = view.onCreateTranslationRequest();
+                    if (translationRequest != null
+                            && translationRequest.getTranslationText().length() > 0) {
+                        requests.add(translationRequest);
+                    }
+                    if (requests.size() == viewCounts) {
+                        Log.v(TAG, "onUiTranslationStarted: send " + requests.size() + " request.");
+                        mWorkerHandler.sendMessage(PooledLambda.obtainMessage(
+                                UiTranslationController::sendTranslationRequest,
+                                UiTranslationController.this, translator, requests));
+                    }
+                });
+            }
+        }
+    }
+
+    private void runForEachView(Consumer<View> action) {
+        synchronized (mLock) {
+            final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
+            mActivity.runOnUiThread(() -> {
+                final int viewCounts = views.size();
+                for (int i = 0; i < viewCounts; i++) {
+                    final View view = views.valueAt(i).get();
+                    if (view == null) {
+                        continue;
+                    }
+                    action.accept(view);
+                }
+            });
+        }
+    }
+
+    private Translator createTranslatorIfNeeded(
+            TranslationSpec sourceSpec, TranslationSpec destSpec) {
+        final TranslationManager tm = mContext.getSystemService(TranslationManager.class);
+        if (tm == null) {
+            Log.e(TAG, "Can not find TranslationManager when trying to create translator.");
+            return null;
+        }
+        final Translator translator = tm.createTranslator(sourceSpec, destSpec);
+        if (translator != null) {
+            final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec);
+            mTranslators.put(specs, translator);
+        }
+        return translator;
+    }
+
+    private void destroyTranslators() {
+        synchronized (mLock) {
+            final int count = mTranslators.size();
+            for (int i = 0; i < count; i++) {
+                Translator translator = mTranslators.valueAt(i);
+                translator.destroy();
+            }
+            mTranslators.clear();
+        }
+    }
+
+    /**
+     * Returns a string representation of the state.
+     */
+    public static String stateToString(@UiTranslationState int state) {
+        switch (state) {
+            case STATE_UI_TRANSLATION_STARTED:
+                return "UI_TRANSLATION_STARTED";
+            case STATE_UI_TRANSLATION_PAUSED:
+                return "UI_TRANSLATION_PAUSED";
+            case STATE_UI_TRANSLATION_RESUMED:
+                return "UI_TRANSLATION_RESUMED";
+            case STATE_UI_TRANSLATION_FINISHED:
+                return "UI_TRANSLATION_FINISHED";
+            default:
+                return "Unknown state (" + state + ")";
+        }
     }
 }
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 023d9ff2..20230e7 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -98,9 +98,17 @@
     public abstract boolean acceptThirdPartyCookies(WebView webview);
 
     /**
-     * Sets a cookie for the given URL. Any existing cookie with the same host,
-     * path and name will be replaced with the new cookie. The cookie being set
-     * will be ignored if it is expired.
+     * Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
+     * host, path and name will be replaced with the new cookie. The cookie being set
+     * will be ignored if it is expired. To set multiple cookies, your application should invoke
+     * this method multiple times.
+     *
+     * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
+     * response header defined by
+     * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
+     * This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
+     * cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
+     * consult the RFC specification for a list of valid attributes.
      *
      * <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
      * attribute, {@code url} must use the {@code "https://"} scheme.
@@ -112,13 +120,20 @@
     public abstract void setCookie(String url, String value);
 
     /**
-     * Sets a cookie for the given URL. Any existing cookie with the same host,
-     * path and name will be replaced with the new cookie. The cookie being set
-     * will be ignored if it is expired.
-     * <p>
-     * This method is asynchronous.
-     * If a {@link ValueCallback} is provided,
-     * {@link ValueCallback#onReceiveValue(T) onReceiveValue()} will be called on the current
+     * Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
+     * host, path and name will be replaced with the new cookie. The cookie being set
+     * will be ignored if it is expired. To set multiple cookies, your application should invoke
+     * this method multiple times.
+     *
+     * <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
+     * response header defined by
+     * <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
+     * This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
+     * cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
+     * consult the RFC specification for a list of valid attributes.
+     *
+     * <p>This method is asynchronous. If a {@link ValueCallback} is provided,
+     * {@link ValueCallback#onReceiveValue} will be called on the current
      * thread's {@link android.os.Looper} once the operation is complete.
      * The value provided to the callback indicates whether the cookie was set successfully.
      * You can pass {@code null} as the callback if you don't need to know when the operation
@@ -137,7 +152,10 @@
             callback);
 
     /**
-     * Gets the cookies for the given URL.
+     * Gets all the cookies for the given URL. This may return multiple key-value pairs if multiple
+     * cookies are associated with this URL, in which case each cookie will be delimited by {@code
+     * "; "} characters (semicolon followed by a space). Each key-value pair will be of the form
+     * {@code "key=value"}.
      *
      * @param url the URL for which the cookies are requested
      * @return value the cookies as a string, using the format of the 'Cookie'
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 950dc73..c7eac6c 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -218,4 +218,15 @@
     public String getDataDirectorySuffix() {
         return WebViewFactory.getDataDirectorySuffix();
     }
+
+    /**
+     * Returns an array of startup timestamps. For the specification of array
+     * see {@link WebViewFactory.Timestamp}.
+     * This method must be called on the same thread where the
+     * WebViewChromiumFactoryProvider#create method was invoked.
+     */
+    @NonNull
+    public long[] getTimestamps() {
+        return WebViewFactory.getTimestamps();
+    }
 }
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index b91e7d3..2e75834 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -35,6 +37,8 @@
 import android.util.Log;
 
 import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Method;
 
 /**
@@ -67,6 +71,33 @@
     private static boolean sWebViewDisabled;
     private static String sDataDirectorySuffix; // stored here so it can be set without loading WV
 
+    // Indices in sTimestamps array.
+    /** @hide */
+    @IntDef(value = {
+            WEBVIEW_LOAD_START, CREATE_CONTEXT_START, CREATE_CONTEXT_END,
+            ADD_ASSETS_START, ADD_ASSETS_END, GET_CLASS_LOADER_START, GET_CLASS_LOADER_END,
+            NATIVE_LOAD_START, NATIVE_LOAD_END,
+            PROVIDER_CLASS_FOR_NAME_START, PROVIDER_CLASS_FOR_NAME_END})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Timestamp {
+    }
+
+    public static final int WEBVIEW_LOAD_START = 0;
+    public static final int CREATE_CONTEXT_START = 1;
+    public static final int CREATE_CONTEXT_END = 2;
+    public static final int ADD_ASSETS_START = 3;
+    public static final int ADD_ASSETS_END = 4;
+    public static final int GET_CLASS_LOADER_START = 5;
+    public static final int GET_CLASS_LOADER_END = 6;
+    public static final int NATIVE_LOAD_START = 7;
+    public static final int NATIVE_LOAD_END = 8;
+    public static final int PROVIDER_CLASS_FOR_NAME_START = 9;
+    public static final int PROVIDER_CLASS_FOR_NAME_END = 10;
+    private static final int TIMESTAMPS_SIZE = 11;
+
+    // WebView startup timestamps. To access elements use {@link Timestamp}.
+    private static long[] sTimestamps = new long[TIMESTAMPS_SIZE];
+
     // Error codes for loadWebViewNativeLibraryFromPackage
     public static final int LIBLOAD_SUCCESS = 0;
     public static final int LIBLOAD_WRONG_PACKAGE_NAME = 1;
@@ -230,6 +261,7 @@
             // us honest and minimize usage of WebView internals when binding the proxy.
             if (sProviderInstance != null) return sProviderInstance;
 
+            sTimestamps[WEBVIEW_LOAD_START] = System.currentTimeMillis();
             final int uid = android.os.Process.myUid();
             if (uid == android.os.Process.ROOT_UID || uid == android.os.Process.SYSTEM_UID
                     || uid == android.os.Process.PHONE_UID || uid == android.os.Process.NFC_UID
@@ -369,6 +401,7 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW,
                     "initialApplication.createApplicationContext");
+            sTimestamps[CREATE_CONTEXT_START] = System.currentTimeMillis();
             try {
                 // Construct an app context to load the Java code into the current app.
                 Context webViewContext = initialApplication.createApplicationContext(
@@ -377,6 +410,7 @@
                 sPackageInfo = newPackageInfo;
                 return webViewContext;
             } finally {
+                sTimestamps[CREATE_CONTEXT_END] = System.currentTimeMillis();
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
             }
         } catch (RemoteException | PackageManager.NameNotFoundException e) {
@@ -402,20 +436,26 @@
 
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
             try {
+                sTimestamps[ADD_ASSETS_START] = System.currentTimeMillis();
                 for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
                     initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
                 }
+                sTimestamps[ADD_ASSETS_END] = sTimestamps[GET_CLASS_LOADER_START] =
+                        System.currentTimeMillis();
                 ClassLoader clazzLoader = webViewContext.getClassLoader();
-
                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.loadNativeLibrary()");
+                sTimestamps[GET_CLASS_LOADER_END] = sTimestamps[NATIVE_LOAD_START] =
+                        System.currentTimeMillis();
                 WebViewLibraryLoader.loadNativeLibrary(clazzLoader,
                         getWebViewLibrary(sPackageInfo.applicationInfo));
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
-
                 Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "Class.forName()");
+                sTimestamps[NATIVE_LOAD_END] = sTimestamps[PROVIDER_CLASS_FOR_NAME_START] =
+                        System.currentTimeMillis();
                 try {
                     return getWebViewProviderClass(clazzLoader);
                 } finally {
+                    sTimestamps[PROVIDER_CLASS_FOR_NAME_END] = System.currentTimeMillis();
                     Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
                 }
             } catch (ClassNotFoundException e) {
@@ -477,4 +517,9 @@
         return IWebViewUpdateService.Stub.asInterface(
                 ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
     }
+
+    @NonNull
+    static long[] getTimestamps() {
+        return sTimestamps;
+    }
 }
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index ffdb89d..93b2d8a 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -25,15 +27,22 @@
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.RemotableViewMethod;
 import android.view.View;
+import android.view.inspector.InspectableProperty;
 import android.widget.RemoteViews.RemoteView;
 
 import java.time.Clock;
+import java.time.DateTimeException;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.util.Formatter;
+import java.util.Locale;
 
 /**
  * This widget display an analogic clock with two hands for hours and
@@ -42,25 +51,36 @@
  * @attr ref android.R.styleable#AnalogClock_dial
  * @attr ref android.R.styleable#AnalogClock_hand_hour
  * @attr ref android.R.styleable#AnalogClock_hand_minute
+ * @attr ref android.R.styleable#AnalogClock_hand_second
+ * @attr ref android.R.styleable#AnalogClock_timeZone
  * @deprecated This widget is no longer supported.
  */
 @RemoteView
 @Deprecated
 public class AnalogClock extends View {
+    private static final String LOG_TAG = "AnalogClock";
+    /** How often the clock should refresh to make the seconds hand advance at ~15 FPS. */
+    private static final long SECONDS_TICK_FREQUENCY_MS = 1000 / 15;
+
     private Clock mClock;
+    @Nullable
+    private ZoneId mTimeZone;
 
     @UnsupportedAppUsage
     private Drawable mHourHand;
     @UnsupportedAppUsage
     private Drawable mMinuteHand;
+    @Nullable
+    private Drawable mSecondHand;
     @UnsupportedAppUsage
     private Drawable mDial;
 
     private int mDialWidth;
     private int mDialHeight;
 
-    private boolean mAttached;
+    private boolean mVisible;
 
+    private float mSeconds;
     private float mMinutes;
     private float mHour;
     private boolean mChanged;
@@ -101,18 +121,111 @@
             mMinuteHand = context.getDrawable(com.android.internal.R.drawable.clock_hand_minute);
         }
 
-        mClock = Clock.systemDefaultZone();
+        mSecondHand = a.getDrawable(com.android.internal.R.styleable.AnalogClock_hand_second);
+
+        mTimeZone = toZoneId(a.getString(com.android.internal.R.styleable.AnalogClock_timeZone));
+        createClock();
 
         mDialWidth = mDial.getIntrinsicWidth();
         mDialHeight = mDial.getIntrinsicHeight();
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
+    /** Sets the dial of the clock to the specified Icon. */
+    @RemotableViewMethod
+    public void setDial(@NonNull Icon icon) {
+        mDial = icon.loadDrawable(getContext());
+        mDialWidth = mDial.getIntrinsicWidth();
+        mDialHeight = mDial.getIntrinsicHeight();
 
-        if (!mAttached) {
-            mAttached = true;
+        mChanged = true;
+        invalidate();
+    }
+
+    /** Sets the hour hand of the clock to the specified Icon. */
+    @RemotableViewMethod
+    public void setHourHand(@NonNull Icon icon) {
+        mHourHand = icon.loadDrawable(getContext());
+
+        mChanged = true;
+        invalidate();
+    }
+
+    /** Sets the minute hand of the clock to the specified Icon. */
+    @RemotableViewMethod
+    public void setMinuteHand(@NonNull Icon icon) {
+        mMinuteHand = icon.loadDrawable(getContext());
+
+        mChanged = true;
+        invalidate();
+    }
+
+    /**
+     * Sets the second hand of the clock to the specified Icon, or hides the second hand if it is
+     * null.
+     */
+    @RemotableViewMethod
+    public void setSecondHand(@Nullable Icon icon) {
+        mSecondHand = icon == null ? null : icon.loadDrawable(getContext());
+        mSecondsTick.run();
+
+        mChanged = true;
+        invalidate();
+    }
+
+    /**
+     * Indicates which time zone is currently used by this view.
+     *
+     * @return The ID of the current time zone or null if the default time zone,
+     *         as set by the user, must be used
+     *
+     * @see java.util.TimeZone
+     * @see java.util.TimeZone#getAvailableIDs()
+     * @see #setTimeZone(String)
+     */
+    @InspectableProperty
+    @Nullable
+    public String getTimeZone() {
+        ZoneId zoneId = mTimeZone;
+        return zoneId == null ? null : zoneId.getId();
+    }
+
+    /**
+     * Sets the specified time zone to use in this clock. When the time zone
+     * is set through this method, system time zone changes (when the user
+     * sets the time zone in settings for instance) will be ignored.
+     *
+     * @param timeZone The desired time zone's ID as specified in {@link java.util.TimeZone}
+     *                 or null to user the time zone specified by the user
+     *                 (system time zone)
+     *
+     * @see #getTimeZone()
+     * @see java.util.TimeZone#getAvailableIDs()
+     * @see java.util.TimeZone#getTimeZone(String)
+     *
+     * @attr ref android.R.styleable#AnalogClock_timeZone
+     */
+    @RemotableViewMethod
+    public void setTimeZone(@Nullable String timeZone) {
+        mTimeZone = toZoneId(timeZone);
+
+        createClock();
+        onTimeChanged();
+    }
+
+    @Override
+    public void onVisibilityAggregated(boolean isVisible) {
+        super.onVisibilityAggregated(isVisible);
+
+        if (isVisible) {
+            onVisible();
+        } else {
+            onInvisible();
+        }
+    }
+
+    private void onVisible() {
+        if (!mVisible) {
+            mVisible = true;
             IntentFilter filter = new IntentFilter();
 
             filter.addAction(Intent.ACTION_TIME_TICK);
@@ -128,24 +241,25 @@
             // user not the one the context is for.
             getContext().registerReceiverAsUser(mIntentReceiver,
                     android.os.Process.myUserHandle(), filter, null, getHandler());
+
+            mSecondsTick.run();
         }
 
         // NOTE: It's safe to do these after registering the receiver since the receiver always runs
         // in the main thread, therefore the receiver can't run before this method returns.
 
-        // The time zone may have changed while the receiver wasn't registered, so update the Time
-        mClock = Clock.systemDefaultZone();
+        // The time zone may have changed while the receiver wasn't registered, so update the clock.
+        createClock();
 
         // Make sure we update to the current time
         onTimeChanged();
     }
 
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        if (mAttached) {
+    private void onInvisible() {
+        if (mVisible) {
             getContext().unregisterReceiver(mIntentReceiver);
-            mAttached = false;
+            removeCallbacks(mSecondsTick);
+            mVisible = false;
         }
     }
 
@@ -237,6 +351,20 @@
         minuteHand.draw(canvas);
         canvas.restore();
 
+        final Drawable secondHand = mSecondHand;
+        if (secondHand != null) {
+            canvas.save();
+            canvas.rotate(mSeconds / 60.0f * 360.0f, x, y);
+
+            if (changed) {
+                w = secondHand.getIntrinsicWidth();
+                h = secondHand.getIntrinsicHeight();
+                secondHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));
+            }
+            secondHand.draw(canvas);
+            canvas.restore();
+        }
+
         if (scaled) {
             canvas.restore();
         }
@@ -250,6 +378,7 @@
         int minute = localDateTime.getMinute();
         int second = localDateTime.getSecond();
 
+        mSeconds = second + localDateTime.getNano() / 1_000_000_000f;
         mMinutes = minute + second / 60.0f;
         mHour = hour + mMinutes / 60.0f;
         mChanged = true;
@@ -261,8 +390,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
-                String tz = intent.getStringExtra(Intent.EXTRA_TIMEZONE);
-                mClock = Clock.system(ZoneId.of(tz));
+                createClock();
             }
 
             onTimeChanged();
@@ -271,9 +399,41 @@
         }
     };
 
+    private final Runnable mSecondsTick = new Runnable() {
+        @Override
+        public void run() {
+            if (!mVisible || mSecondHand == null) {
+                return;
+            }
+
+            onTimeChanged();
+
+            invalidate();
+
+            postDelayed(this, SECONDS_TICK_FREQUENCY_MS);
+        }
+    };
+
+    private void createClock() {
+        ZoneId zoneId = mTimeZone;
+        if (zoneId == null) {
+            mClock = Clock.systemDefaultZone();
+        } else {
+            mClock = Clock.system(zoneId);
+        }
+    }
+
     private void updateContentDescription(long timeMillis) {
         final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
-        String contentDescription = DateUtils.formatDateTime(mContext, timeMillis, flags);
+        String contentDescription =
+                DateUtils.formatDateRange(
+                        mContext,
+                        new Formatter(new StringBuilder(50), Locale.getDefault()),
+                        timeMillis /* startMillis */,
+                        timeMillis /* endMillis */,
+                        flags,
+                        getTimeZone())
+                        .toString();
         setContentDescription(contentDescription);
     }
 
@@ -284,4 +444,22 @@
         Instant instant = Instant.ofEpochMilli(timeMillis);
         return LocalDateTime.ofInstant(instant, zoneId);
     }
+
+    /**
+     * Tries to parse a {@link ZoneId} from {@code timeZone}, returning null if it is null or there
+     * is an error parsing.
+     */
+    @Nullable
+    private static ZoneId toZoneId(@Nullable String timeZone) {
+        if (timeZone == null) {
+            return null;
+        }
+
+        try {
+            return ZoneId.of(timeZone);
+        } catch (DateTimeException e) {
+            Log.w(LOG_TAG, "Failed to parse time zone from " + timeZone, e);
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 046f75f..2452e4c 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
+import android.widget.RemoteViews.RemoteView;
 
 /**
  * <p>
@@ -52,6 +53,7 @@
  * {@link android.R.styleable#View View Attributes}
  * </p>
  */
+@RemoteView
 public class CheckBox extends CompoundButton {
     public CheckBox(Context context) {
         this(context, null);
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 135ff9f..63f8ee7 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -27,11 +27,13 @@
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
+import android.view.RemotableViewMethod;
 import android.view.SoundEffectConstants;
 import android.view.ViewDebug;
 import android.view.ViewHierarchyEncoder;
@@ -275,6 +277,7 @@
      * @param resId the resource identifier of the drawable
      * @attr ref android.R.styleable#CompoundButton_button
      */
+    @RemotableViewMethod(asyncImpl = "setButtonDrawableAsync")
     public void setButtonDrawable(@DrawableRes int resId) {
         final Drawable d;
         if (resId != 0) {
@@ -285,6 +288,12 @@
         setButtonDrawable(d);
     }
 
+    /** @hide **/
+    public Runnable setButtonDrawableAsync(@DrawableRes int resId) {
+        Drawable drawable = resId == 0 ? null : getContext().getDrawable(resId);
+        return () -> setButtonDrawable(drawable);
+    }
+
     /**
      * Sets a drawable as the compound button image.
      *
@@ -336,6 +345,23 @@
     }
 
     /**
+     * Sets the button of this CompoundButton to the specified Icon.
+     *
+     * @param icon an Icon holding the desired button, or {@code null} to clear
+     *             the button
+     */
+    @RemotableViewMethod(asyncImpl = "setButtonIconAsync")
+    public void setButtonIcon(@Nullable Icon icon) {
+        setButtonDrawable(icon == null ? null : icon.loadDrawable(getContext()));
+    }
+
+    /** @hide **/
+    public Runnable setButtonIconAsync(@Nullable Icon icon) {
+        Drawable button = icon == null ? null : icon.loadDrawable(getContext());
+        return () -> setButtonDrawable(button);
+    }
+
+    /**
      * Applies a tint to the button drawable. Does not modify the current tint
      * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
      * <p>
@@ -350,6 +376,7 @@
      * @see #setButtonTintList(ColorStateList)
      * @see Drawable#setTintList(ColorStateList)
      */
+    @RemotableViewMethod
     public void setButtonTintList(@Nullable ColorStateList tint) {
         mButtonTintList = tint;
         mHasButtonTint = true;
@@ -394,6 +421,7 @@
      * @see #getButtonTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setButtonTintBlendMode(@Nullable BlendMode tintMode) {
         mButtonBlendMode = tintMode;
         mHasButtonBlendMode = true;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 26dd5e3..012352d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1705,15 +1705,23 @@
     }
 
     private void updateFloatingToolbarVisibility(MotionEvent event) {
-        if (mTextActionMode != null) {
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_MOVE:
-                    hideFloatingToolbar(ActionMode.DEFAULT_HIDE_DURATION);
-                    break;
-                case MotionEvent.ACTION_UP:  // fall through
-                case MotionEvent.ACTION_CANCEL:
+        if (mTextActionMode == null) {
+            return;
+        }
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_MOVE:
+                hideFloatingToolbar(ActionMode.DEFAULT_HIDE_DURATION);
+                break;
+            case MotionEvent.ACTION_UP:  // fall through
+            case MotionEvent.ACTION_CANCEL:
+                final SelectionModifierCursorController selectionController =
+                        getSelectionController();
+                final InsertionPointCursorController insertionController = getInsertionController();
+                if ((selectionController != null && selectionController.isCursorBeingModified())
+                        || (insertionController != null
+                        && insertionController.isCursorBeingModified())) {
                     showFloatingToolbar();
-            }
+                }
         }
     }
 
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index a04d7c3..9b35034 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.RemoteViews.RemoteView;
 
 import com.android.internal.R;
 
@@ -49,6 +50,7 @@
  * {@link android.R.styleable#View View Attributes}
  * </p>
  */
+@RemoteView
 public class RadioButton extends CompoundButton {
 
     public RadioButton(Context context) {
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 4722fdc..d445fdc 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -31,6 +31,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
+import android.widget.RemoteViews.RemoteView;
 
 import com.android.internal.R;
 
@@ -59,6 +60,7 @@
  * @see RadioButton
  *
  */
+@RemoteView
 public class RadioGroup extends LinearLayout {
     private static final String LOG_TAG = RadioGroup.class.getSimpleName();
 
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 8dafc5d..dfef7ca 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import android.annotation.ColorInt;
+import android.annotation.ColorRes;
 import android.annotation.DimenRes;
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
@@ -25,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Px;
+import android.annotation.StringRes;
 import android.annotation.StyleRes;
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -63,6 +65,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.DisplayMetrics;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -129,6 +132,13 @@
  *   <li>{@link android.widget.TextClock}</li>
  *   <li>{@link android.widget.TextView}</li>
  * </ul>
+ * <p>As of API 31, the following widgets and layouts may also be used:</p>
+ * <ul>
+ *     <li>{@link android.widget.CheckBox}</li>
+ *     <li>{@link android.widget.RadioButton}</li>
+ *     <li>{@link android.widget.RadioGroup}</li>
+ *     <li>{@link android.widget.Switch}</li>
+ * </ul>
  * <p>Descendants of these classes are not supported.</p>
  */
 public class RemoteViews implements Parcelable, Filter {
@@ -180,6 +190,10 @@
     private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21;
     private static final int SET_INT_TAG_TAG = 22;
     private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23;
+    private static final int RESOURCE_REFLECTION_ACTION_TAG = 24;
+    private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25;
+    private static final int SET_COMPOUND_BUTTON_CHECKED_TAG = 26;
+    private static final int SET_RADIO_GROUP_CHECKED = 27;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -980,6 +994,45 @@
         return rect;
     }
 
+    private static Class<?> getParameterType(int type) {
+        switch (type) {
+            case BaseReflectionAction.BOOLEAN:
+                return boolean.class;
+            case BaseReflectionAction.BYTE:
+                return byte.class;
+            case BaseReflectionAction.SHORT:
+                return short.class;
+            case BaseReflectionAction.INT:
+                return int.class;
+            case BaseReflectionAction.LONG:
+                return long.class;
+            case BaseReflectionAction.FLOAT:
+                return float.class;
+            case BaseReflectionAction.DOUBLE:
+                return double.class;
+            case BaseReflectionAction.CHAR:
+                return char.class;
+            case BaseReflectionAction.STRING:
+                return String.class;
+            case BaseReflectionAction.CHAR_SEQUENCE:
+                return CharSequence.class;
+            case BaseReflectionAction.URI:
+                return Uri.class;
+            case BaseReflectionAction.BITMAP:
+                return Bitmap.class;
+            case BaseReflectionAction.BUNDLE:
+                return Bundle.class;
+            case BaseReflectionAction.INTENT:
+                return Intent.class;
+            case BaseReflectionAction.COLOR_STATE_LIST:
+                return ColorStateList.class;
+            case BaseReflectionAction.ICON:
+                return Icon.class;
+            default:
+                return null;
+        }
+    }
+
     private MethodHandle getMethod(View view, String methodName, Class<?> paramType,
             boolean async) {
         MethodArgs result;
@@ -1282,7 +1335,8 @@
         @Override
         public void apply(View root, ViewGroup rootParent,
                 OnClickHandler handler) throws ActionException {
-            ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
+            ReflectionAction ra = new ReflectionAction(viewId, methodName,
+                    BaseReflectionAction.BITMAP,
                     bitmap);
             ra.apply(root, rootParent, handler);
         }
@@ -1301,7 +1355,7 @@
     /**
      * Base class for the reflection actions.
      */
-    private final class ReflectionAction extends Action {
+    private abstract class BaseReflectionAction extends Action {
         static final int BOOLEAN = 1;
         static final int BYTE = 2;
         static final int SHORT = 3;
@@ -1324,17 +1378,14 @@
         @UnsupportedAppUsage
         String methodName;
         int type;
-        @UnsupportedAppUsage
-        Object value;
 
-        ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) {
+        BaseReflectionAction(@IdRes int viewId, String methodName, int type) {
             this.viewId = viewId;
             this.methodName = methodName;
             this.type = type;
-            this.value = value;
         }
 
-        ReflectionAction(Parcel in) {
+        BaseReflectionAction(Parcel in) {
             this.viewId = in.readInt();
             this.methodName = in.readString8();
             this.type = in.readInt();
@@ -1343,7 +1394,125 @@
                 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
                         + " methodName=" + this.methodName + " type=" + this.type);
             }
+        }
 
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(this.viewId);
+            out.writeString8(this.methodName);
+            out.writeInt(this.type);
+        }
+
+        /**
+         * Returns the value to use as parameter for the method.
+         *
+         * The view might be passed as {@code null} if the parameter value is requested outside of
+         * inflation. If the parameter cannot be determined at that time, the method should return
+         * {@code null} but not raise any exception.
+         */
+        @Nullable
+        protected abstract Object getParameterValue(@Nullable View view) throws ActionException;
+
+        @Override
+        public final void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+            final View view = root.findViewById(viewId);
+            if (view == null) return;
+
+            Class<?> param = getParameterType(this.type);
+            if (param == null) {
+                throw new ActionException("bad type: " + this.type);
+            }
+            Object value = getParameterValue(view);
+            try {
+                getMethod(view, this.methodName, param, false /* async */).invoke(view, value);
+            } catch (Throwable ex) {
+                throw new ActionException(ex);
+            }
+        }
+
+        @Override
+        public final Action initActionAsync(ViewTree root, ViewGroup rootParent,
+                OnClickHandler handler) {
+            final View view = root.findViewById(viewId);
+            if (view == null) return ACTION_NOOP;
+
+            Class<?> param = getParameterType(this.type);
+            if (param == null) {
+                throw new ActionException("bad type: " + this.type);
+            }
+
+            Object value = getParameterValue(view);
+            try {
+                MethodHandle method = getMethod(view, this.methodName, param, true /* async */);
+
+                if (method != null) {
+                    Runnable endAction = (Runnable) method.invoke(view, value);
+                    if (endAction == null) {
+                        return ACTION_NOOP;
+                    }
+                    // Special case view stub
+                    if (endAction instanceof ViewStub.ViewReplaceRunnable) {
+                        root.createTree();
+                        // Replace child tree
+                        root.findViewTreeById(viewId).replaceView(
+                                ((ViewStub.ViewReplaceRunnable) endAction).view);
+                    }
+                    return new RunnableAction(endAction);
+                }
+            } catch (Throwable ex) {
+                throw new ActionException(ex);
+            }
+
+            return this;
+        }
+
+        public final int mergeBehavior() {
+            // smoothScrollBy is cumulative, everything else overwites.
+            if (methodName.equals("smoothScrollBy")) {
+                return MERGE_APPEND;
+            } else {
+                return MERGE_REPLACE;
+            }
+        }
+
+        @Override
+        public final String getUniqueKey() {
+            // Each type of reflection action corresponds to a setter, so each should be seen as
+            // unique from the standpoint of merging.
+            return super.getUniqueKey() + this.methodName + this.type;
+        }
+
+        @Override
+        public final boolean prefersAsyncApply() {
+            return this.type == URI || this.type == ICON;
+        }
+
+        @Override
+        public final void visitUris(@NonNull Consumer<Uri> visitor) {
+            switch (this.type) {
+                case URI:
+                    final Uri uri = (Uri) getParameterValue(null);
+                    if (uri != null) visitor.accept(uri);
+                    break;
+                case ICON:
+                    final Icon icon = (Icon) getParameterValue(null);
+                    if (icon != null) visitIconUri(icon, visitor);
+                    break;
+            }
+        }
+    }
+
+    /** Class for the reflection actions. */
+    private final class ReflectionAction extends BaseReflectionAction {
+        @UnsupportedAppUsage
+        Object value;
+
+        ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) {
+            super(viewId, methodName, type);
+            this.value = value;
+        }
+
+        ReflectionAction(Parcel in) {
+            super(in);
             // For some values that may have been null, we first check a flag to see if they were
             // written to the parcel.
             switch (this.type) {
@@ -1354,7 +1523,7 @@
                     this.value = in.readByte();
                     break;
                 case SHORT:
-                    this.value = (short)in.readInt();
+                    this.value = (short) in.readInt();
                     break;
                 case INT:
                     this.value = in.readInt();
@@ -1369,7 +1538,7 @@
                     this.value = in.readDouble();
                     break;
                 case CHAR:
-                    this.value = (char)in.readInt();
+                    this.value = (char) in.readInt();
                     break;
                 case STRING:
                     this.value = in.readString8();
@@ -1400,15 +1569,7 @@
         }
 
         public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(this.viewId);
-            out.writeString8(this.methodName);
-            out.writeInt(this.type);
-            //noinspection ConstantIfStatement
-            if (false) {
-                Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId)
-                        + " methodName=" + this.methodName + " type=" + this.type);
-            }
-
+            super.writeToParcel(out, flags);
             // For some values which are null, we record an integer flag to indicate whether
             // we have written a valid value to the parcel.
             switch (this.type) {
@@ -1434,13 +1595,13 @@
                     out.writeDouble((Double) this.value);
                     break;
                 case CHAR:
-                    out.writeInt((int)((Character)this.value).charValue());
+                    out.writeInt((int) ((Character) this.value).charValue());
                     break;
                 case STRING:
-                    out.writeString8((String)this.value);
+                    out.writeString8((String) this.value);
                     break;
                 case CHAR_SEQUENCE:
-                    TextUtils.writeToParcel((CharSequence)this.value, out, flags);
+                    TextUtils.writeToParcel((CharSequence) this.value, out, flags);
                     break;
                 case BUNDLE:
                     out.writeBundle((Bundle) this.value);
@@ -1457,135 +1618,134 @@
             }
         }
 
-        private Class<?> getParameterType() {
-            switch (this.type) {
-                case BOOLEAN:
-                    return boolean.class;
-                case BYTE:
-                    return byte.class;
-                case SHORT:
-                    return short.class;
-                case INT:
-                    return int.class;
-                case LONG:
-                    return long.class;
-                case FLOAT:
-                    return float.class;
-                case DOUBLE:
-                    return double.class;
-                case CHAR:
-                    return char.class;
-                case STRING:
-                    return String.class;
-                case CHAR_SEQUENCE:
-                    return CharSequence.class;
-                case URI:
-                    return Uri.class;
-                case BITMAP:
-                    return Bitmap.class;
-                case BUNDLE:
-                    return Bundle.class;
-                case INTENT:
-                    return Intent.class;
-                case COLOR_STATE_LIST:
-                    return ColorStateList.class;
-                case ICON:
-                    return Icon.class;
-                default:
-                    return null;
-            }
-        }
-
         @Override
-        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
-            final View view = root.findViewById(viewId);
-            if (view == null) return;
-
-            Class<?> param = getParameterType();
-            if (param == null) {
-                throw new ActionException("bad type: " + this.type);
-            }
-            try {
-                getMethod(view, this.methodName, param, false /* async */).invoke(view, this.value);
-            } catch (Throwable ex) {
-                throw new ActionException(ex);
-            }
-        }
-
-        @Override
-        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
-            final View view = root.findViewById(viewId);
-            if (view == null) return ACTION_NOOP;
-
-            Class<?> param = getParameterType();
-            if (param == null) {
-                throw new ActionException("bad type: " + this.type);
-            }
-
-            try {
-                MethodHandle method = getMethod(view, this.methodName, param, true /* async */);
-
-                if (method != null) {
-                    Runnable endAction = (Runnable) method.invoke(view, this.value);
-                    if (endAction == null) {
-                        return ACTION_NOOP;
-                    } else {
-                        // Special case view stub
-                        if (endAction instanceof ViewStub.ViewReplaceRunnable) {
-                            root.createTree();
-                            // Replace child tree
-                            root.findViewTreeById(viewId).replaceView(
-                                    ((ViewStub.ViewReplaceRunnable) endAction).view);
-                        }
-                        return new RunnableAction(endAction);
-                    }
-                }
-            } catch (Throwable ex) {
-                throw new ActionException(ex);
-            }
-
-            return this;
-        }
-
-        public int mergeBehavior() {
-            // smoothScrollBy is cumulative, everything else overwites.
-            if (methodName.equals("smoothScrollBy")) {
-                return MERGE_APPEND;
-            } else {
-                return MERGE_REPLACE;
-            }
+        protected Object getParameterValue(View view) throws ActionException {
+            return this.value;
         }
 
         @Override
         public int getActionTag() {
             return REFLECTION_ACTION_TAG;
         }
+    }
 
-        @Override
-        public String getUniqueKey() {
-            // Each type of reflection action corresponds to a setter, so each should be seen as
-            // unique from the standpoint of merging.
-            return super.getUniqueKey() + this.methodName + this.type;
+    private final class ResourceReflectionAction extends BaseReflectionAction {
+
+        static final int DIMEN_RESOURCE = 1;
+        static final int COLOR_RESOURCE = 2;
+        static final int STRING_RESOURCE = 3;
+
+        private final int mResourceType;
+        private final int mResId;
+
+        ResourceReflectionAction(@IdRes int viewId, String methodName, int parameterType,
+                int resourceType, int resId) {
+            super(viewId, methodName, parameterType);
+            this.mResourceType = resourceType;
+            this.mResId = resId;
+        }
+
+        ResourceReflectionAction(Parcel in) {
+            super(in);
+            this.mResourceType = in.readInt();
+            this.mResId = in.readInt();
         }
 
         @Override
-        public boolean prefersAsyncApply() {
-            return this.type == URI || this.type == ICON;
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeInt(this.mResourceType);
+            dest.writeInt(this.mResId);
         }
 
         @Override
-        public void visitUris(@NonNull Consumer<Uri> visitor) {
-            switch (this.type) {
-                case URI:
-                    final Uri uri = (Uri) this.value;
-                    visitor.accept(uri);
-                    break;
-                case ICON:
-                    final Icon icon = (Icon) this.value;
-                    visitIconUri(icon, visitor);
-                    break;
+        protected @NonNull Object getParameterValue(View view) throws ActionException {
+            Resources resources = view.getContext().getResources();
+            try {
+                switch (this.mResourceType) {
+                    case DIMEN_RESOURCE:
+                        if (this.type == BaseReflectionAction.INT) {
+                            return resources.getDimensionPixelSize(this.mResId);
+                        }
+                        return resources.getDimension(this.mResId);
+                    case COLOR_RESOURCE:
+                        switch(this.type) {
+                            case BaseReflectionAction.INT:
+                                return view.getContext().getColor(this.mResId);
+                            case BaseReflectionAction.COLOR_STATE_LIST:
+                                return view.getContext().getColorStateList(this.mResId);
+                            default:
+                                throw new ActionException(
+                                        "color resources must be used as int or ColorStateList, "
+                                                + "not " + this.type);
+                        }
+                    case STRING_RESOURCE:
+                        return resources.getText(this.mResId);
+                    default:
+                        throw new ActionException("unknown resource type: " + this.mResourceType);
+                }
+            } catch (Throwable t) {
+                throw new ActionException(t);
             }
         }
+
+        @Override
+        public int getActionTag() {
+            return RESOURCE_REFLECTION_ACTION_TAG;
+        }
+    }
+
+    private final class ComplexUnitDimensionReflectionAction extends BaseReflectionAction {
+
+        private final float mValue;
+        @ComplexDimensionUnit
+        private final int mUnit;
+
+        ComplexUnitDimensionReflectionAction(int viewId, String methodName, int parameterType,
+                float value, @ComplexDimensionUnit int unit) {
+            super(viewId, methodName, parameterType);
+            this.mValue = value;
+            this.mUnit = unit;
+        }
+
+        ComplexUnitDimensionReflectionAction(Parcel in) {
+            super(in);
+            this.mValue = in.readFloat();
+            this.mUnit = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeFloat(this.mValue);
+            dest.writeInt(this.mUnit);
+        }
+
+        @Override
+        protected Object getParameterValue(View view) throws ActionException {
+            DisplayMetrics dm = view.getContext().getResources().getDisplayMetrics();
+            try {
+                int data = TypedValue.createComplexDimension(this.mValue, this.mUnit);
+                switch (this.type) {
+                    case ReflectionAction.INT:
+                        return TypedValue.complexToDimensionPixelSize(data, dm);
+                    case ReflectionAction.FLOAT:
+                        return TypedValue.complexToDimension(data, dm);
+                    default:
+                        throw new ActionException(
+                                "parameter type must be INT or FLOAT, not " + this.type);
+                }
+            } catch (ActionException ex) {
+                throw ex;
+            } catch (Throwable t) {
+                throw new ActionException(t);
+            }
+        }
+
+        @Override
+        public int getActionTag() {
+            return COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG;
+        }
     }
 
     /**
@@ -2401,6 +2561,87 @@
         }
     }
 
+    private static class SetCompoundButtonCheckedAction extends Action {
+
+        private final boolean mChecked;
+
+        SetCompoundButtonCheckedAction(@IdRes int viewId, boolean checked) {
+            this.viewId = viewId;
+            mChecked = checked;
+        }
+
+        SetCompoundButtonCheckedAction(Parcel in) {
+            viewId = in.readInt();
+            mChecked = in.readBoolean();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(viewId);
+            dest.writeBoolean(mChecked);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
+                throws ActionException {
+            final View target = root.findViewById(viewId);
+            if (target == null) return;
+
+            if (!(target instanceof CompoundButton)) {
+                Log.w(LOG_TAG, "Cannot set checked to view "
+                        + viewId + " because it is not a CompoundButton");
+                return;
+            }
+
+            ((CompoundButton) target).setChecked(mChecked);
+        }
+
+        @Override
+        public int getActionTag() {
+            return SET_COMPOUND_BUTTON_CHECKED_TAG;
+        }
+    }
+
+    private static class SetRadioGroupCheckedAction extends Action {
+
+        @IdRes private final int mCheckedId;
+
+        SetRadioGroupCheckedAction(@IdRes int viewId, @IdRes int checkedId) {
+            this.viewId = viewId;
+            mCheckedId = checkedId;
+        }
+
+        SetRadioGroupCheckedAction(Parcel in) {
+            viewId = in.readInt();
+            mCheckedId = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(viewId);
+            dest.writeInt(mCheckedId);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
+                throws ActionException {
+            final View target = root.findViewById(viewId);
+            if (target == null) return;
+
+            if (!(target instanceof RadioGroup)) {
+                Log.w(LOG_TAG, "Cannot check " + viewId + " because it's not a RadioGroup");
+                return;
+            }
+
+            ((RadioGroup) target).check(mCheckedId);
+        }
+
+        @Override
+        public int getActionTag() {
+            return SET_RADIO_GROUP_CHECKED;
+        }
+    }
+
     /**
      * Create a new RemoteViews object that will display the views contained
      * in the specified layout file.
@@ -2611,6 +2852,14 @@
                 return new SetIntTagAction(parcel);
             case REMOVE_FROM_PARENT_ACTION_TAG:
                 return new RemoveFromParentAction(parcel);
+            case RESOURCE_REFLECTION_ACTION_TAG:
+                return new ResourceReflectionAction(parcel);
+            case COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG:
+                return new ComplexUnitDimensionReflectionAction(parcel);
+            case SET_COMPOUND_BUTTON_CHECKED_TAG:
+                return new SetCompoundButtonCheckedAction(parcel);
+            case SET_RADIO_GROUP_CHECKED:
+                return new SetRadioGroupCheckedAction(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
@@ -3113,7 +3362,7 @@
      */
     public void setProgressTintList(@IdRes int viewId, ColorStateList tint) {
         addAction(new ReflectionAction(viewId, "setProgressTintList",
-                ReflectionAction.COLOR_STATE_LIST, tint));
+                BaseReflectionAction.COLOR_STATE_LIST, tint));
     }
 
     /**
@@ -3125,7 +3374,7 @@
      */
     public void setProgressBackgroundTintList(@IdRes int viewId, ColorStateList tint) {
         addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList",
-                ReflectionAction.COLOR_STATE_LIST, tint));
+                BaseReflectionAction.COLOR_STATE_LIST, tint));
     }
 
     /**
@@ -3137,7 +3386,7 @@
      */
     public void setProgressIndeterminateTintList(@IdRes int viewId, ColorStateList tint) {
         addAction(new ReflectionAction(viewId, "setIndeterminateTintList",
-                ReflectionAction.COLOR_STATE_LIST, tint));
+                BaseReflectionAction.COLOR_STATE_LIST, tint));
     }
 
     /**
@@ -3159,8 +3408,8 @@
      * @param colors the text colors to set
      */
     public void setTextColor(@IdRes int viewId, ColorStateList colors) {
-        addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST,
-                colors));
+        addAction(new ReflectionAction(viewId, "setTextColor",
+                BaseReflectionAction.COLOR_STATE_LIST, colors));
     }
 
     /**
@@ -3353,7 +3602,7 @@
      * @param value The value to pass to the method.
      */
     public void setBoolean(@IdRes int viewId, String methodName, boolean value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BOOLEAN, value));
     }
 
     /**
@@ -3364,7 +3613,7 @@
      * @param value The value to pass to the method.
      */
     public void setByte(@IdRes int viewId, String methodName, byte value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BYTE, value));
     }
 
     /**
@@ -3375,7 +3624,7 @@
      * @param value The value to pass to the method.
      */
     public void setShort(@IdRes int viewId, String methodName, short value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.SHORT, value));
     }
 
     /**
@@ -3386,10 +3635,59 @@
      * @param value The value to pass to the method.
      */
     public void setInt(@IdRes int viewId, String methodName, int value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INT, value));
     }
 
     /**
+     * Call a method taking one int, a size in pixels, on a view in the layout for this
+     * RemoteViews.
+     *
+     * The dimension will be resolved from the resources at the time of inflation.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param dimenResource The resource to resolve and pass as argument to the method.
+     */
+    public void setIntDimen(@IdRes int viewId, @NonNull String methodName,
+            @DimenRes int dimenResource) {
+        addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT,
+                ResourceReflectionAction.DIMEN_RESOURCE, dimenResource));
+    }
+
+    /**
+     * Call a method taking one int, a size in pixels, on a view in the layout for this
+     * RemoteViews.
+     *
+     * The dimension will be resolved from the specified dimension at the time of inflation.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param value The value of the dimension.
+     * @param unit The unit in which the value is specified.
+     */
+    public void setIntDimen(@IdRes int viewId, @NonNull String methodName,
+            float value, @ComplexDimensionUnit int unit) {
+        addAction(new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.INT,
+                value, unit));
+    }
+
+    /**
+     * Call a method taking one int, a color, on a view in the layout for this RemoteViews.
+     *
+     * The ColorStateList will be resolved from the resources at the time of inflation.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param colorResource The resource to resolve and pass as argument to the method.
+     */
+    public void setColor(@IdRes int viewId, @NonNull String methodName,
+            @ColorRes int colorResource) {
+        addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT,
+                ResourceReflectionAction.COLOR_RESOURCE, colorResource));
+    }
+
+
+    /**
      * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
      *
      * @param viewId The id of the view on which to call the method.
@@ -3399,10 +3697,25 @@
      * @hide
      */
     public void setColorStateList(@IdRes int viewId, String methodName, ColorStateList value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.COLOR_STATE_LIST,
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.COLOR_STATE_LIST,
                 value));
     }
 
+    /**
+     * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
+     *
+     * The ColorStateList will be resolved from the resources at the time of inflation.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param colorResource The resource to resolve and pass as argument to the method.
+     */
+    public void setColorStateList(@IdRes int viewId, @NonNull String methodName,
+            @ColorRes int colorResource) {
+        addAction(new ResourceReflectionAction(viewId, methodName,
+                BaseReflectionAction.COLOR_STATE_LIST, ResourceReflectionAction.COLOR_RESOURCE,
+                colorResource));
+    }
 
     /**
      * Call a method taking one long on a view in the layout for this RemoteViews.
@@ -3412,7 +3725,7 @@
      * @param value The value to pass to the method.
      */
     public void setLong(@IdRes int viewId, String methodName, long value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.LONG, value));
     }
 
     /**
@@ -3423,7 +3736,41 @@
      * @param value The value to pass to the method.
      */
     public void setFloat(@IdRes int viewId, String methodName, float value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, value));
+    }
+
+    /**
+     * Call a method taking one float, a size in pixels, on a view in the layout for this
+     * RemoteViews.
+     *
+     * The dimension will be resolved from the resources at the time of inflation.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param dimenResource The resource to resolve and pass as argument to the method.
+     */
+    public void setFloatDimen(@IdRes int viewId, @NonNull String methodName,
+            @DimenRes int dimenResource) {
+        addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT,
+                ResourceReflectionAction.DIMEN_RESOURCE, dimenResource));
+    }
+
+    /**
+     * Call a method taking one float, a size in pixels, on a view in the layout for this
+     * RemoteViews.
+     *
+     * The dimension will be resolved from the specified dimension at the time of inflation.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param value The value of the dimension.
+     * @param unit The unit in which the value is specified.
+     */
+    public void setFloatDimen(@IdRes int viewId, @NonNull String methodName,
+            float value, @ComplexDimensionUnit int unit) {
+        addAction(
+                new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.FLOAT,
+                        value, unit));
     }
 
     /**
@@ -3434,7 +3781,7 @@
      * @param value The value to pass to the method.
      */
     public void setDouble(@IdRes int viewId, String methodName, double value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.DOUBLE, value));
     }
 
     /**
@@ -3445,7 +3792,7 @@
      * @param value The value to pass to the method.
      */
     public void setChar(@IdRes int viewId, String methodName, char value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR, value));
     }
 
     /**
@@ -3456,7 +3803,7 @@
      * @param value The value to pass to the method.
      */
     public void setString(@IdRes int viewId, String methodName, String value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.STRING, value));
     }
 
     /**
@@ -3467,7 +3814,24 @@
      * @param value The value to pass to the method.
      */
     public void setCharSequence(@IdRes int viewId, String methodName, CharSequence value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE,
+                value));
+    }
+
+    /**
+     * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
+     *
+     * The CharSequence will be resolved from the resources at the time of inflation.
+     *
+     * @param viewId The id of the view on which to call the method.
+     * @param methodName The name of the method to call.
+     * @param stringResource The resource to resolve and pass as argument to the method.
+     */
+    public void setCharSequence(@IdRes int viewId, @NonNull String methodName,
+            @StringRes int stringResource) {
+        addAction(
+                new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE,
+                        ResourceReflectionAction.STRING_RESOURCE, stringResource));
     }
 
     /**
@@ -3485,7 +3849,7 @@
                 value.checkFileUriExposed("RemoteViews.setUri()");
             }
         }
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.URI, value));
     }
 
     /**
@@ -3510,7 +3874,7 @@
      * @param value The value to pass to the method.
      */
     public void setBundle(@IdRes int viewId, String methodName, Bundle value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BUNDLE, value));
     }
 
     /**
@@ -3521,7 +3885,7 @@
      * @param value The {@link android.content.Intent} to pass the method.
      */
     public void setIntent(@IdRes int viewId, String methodName, Intent value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INTENT, value));
     }
 
     /**
@@ -3532,7 +3896,7 @@
      * @param value The {@link android.graphics.drawable.Icon} to pass the method.
      */
     public void setIcon(@IdRes int viewId, String methodName, Icon value) {
-        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value));
+        addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.ICON, value));
     }
 
     /**
@@ -3576,6 +3940,26 @@
     }
 
     /**
+     * Equivalent to calling {@link android.widget.CompoundButton#setChecked(boolean)}.
+     *
+     * @param viewId The id of the view whose property to set.
+     * @param checked true to check the button, false to uncheck it.
+     */
+    public void setCompoundButtonChecked(@IdRes int viewId, boolean checked) {
+        addAction(new SetCompoundButtonCheckedAction(viewId, checked));
+    }
+
+    /**
+     * Equivalent to calling {@link android.widget.RadioGroup#check(int)}.
+     *
+     * @param viewId The id of the view whose property to set.
+     * @param checkedId The unique id of the radio button to select in the group.
+     */
+    public void setRadioGroupChecked(@IdRes int viewId, @IdRes int checkedId) {
+        addAction(new SetRadioGroupCheckedAction(viewId, checkedId));
+    }
+
+    /**
      * Provides an alternate layout ID, which can be used to inflate this view. This layout will be
      * used by the host when the widgets displayed on a light-background where foreground elements
      * and text can safely draw using a dark color without any additional background protection.
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 97d98fd..794b642 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -220,29 +220,26 @@
     }
 
     void onPerformSpellCheck() {
-        final int selectionStart = mTextView.getSelectionStart();
-        final int selectionEnd = mTextView.getSelectionEnd();
-        final int selectionRangeStart;
-        final int selectionRangeEnd;
-        if (selectionStart < selectionEnd) {
-            selectionRangeStart = selectionStart;
-            selectionRangeEnd = selectionEnd;
-        } else {
-            selectionRangeStart = selectionEnd;
-            selectionRangeEnd = selectionStart;
-        }
-        // Expand the range so that it (hopefully) includes the current sentence.
-        final int start = Math.max(0, selectionRangeStart - MIN_SENTENCE_LENGTH);
-        final int end = Math.min(mTextView.length(), selectionRangeEnd + MIN_SENTENCE_LENGTH);
+        // Triggers full content spell check.
+        final int start = 0;
+        final int end = mTextView.length();
         if (DBG) {
             Log.d(TAG, "performSpellCheckAroundSelection: " + start + ", " + end);
         }
-        spellCheck(start, end);
+        spellCheck(start, end, /* forceCheckWhenEditingWord= */ true);
     }
 
     public void spellCheck(int start, int end) {
+        spellCheck(start, end, /* forceCheckWhenEditingWord= */ false);
+    }
+
+    /**
+     * Requests to do spell check for text in the range (start, end).
+     */
+    public void spellCheck(int start, int end, boolean forceCheckWhenEditingWord) {
         if (DBG) {
-            Log.d(TAG, "Start spell-checking: " + start + ", " + end);
+            Log.d(TAG, "Start spell-checking: " + start + ", " + end + ", "
+                    + forceCheckWhenEditingWord);
         }
         final Locale locale = mTextView.getSpellCheckerLocale();
         final boolean isSessionActive = isSessionActive();
@@ -267,7 +264,7 @@
         for (int i = 0; i < length; i++) {
             final SpellParser spellParser = mSpellParsers[i];
             if (spellParser.isFinished()) {
-                spellParser.parse(start, end);
+                spellParser.parse(start, end, forceCheckWhenEditingWord);
                 return;
             }
         }
@@ -282,10 +279,14 @@
 
         SpellParser spellParser = new SpellParser();
         mSpellParsers[length] = spellParser;
-        spellParser.parse(start, end);
+        spellParser.parse(start, end, forceCheckWhenEditingWord);
     }
 
     private void spellCheck() {
+        spellCheck(/* forceCheckWhenEditingWord= */ false);
+    }
+
+    private void spellCheck(boolean forceCheckWhenEditingWord) {
         if (mSpellCheckerSession == null) return;
 
         Editable editable = (Editable) mTextView.getText();
@@ -295,6 +296,12 @@
         TextInfo[] textInfos = new TextInfo[mLength];
         int textInfosCount = 0;
 
+        if (DBG) {
+            Log.d(TAG, "forceCheckWhenEditingWord=" + forceCheckWhenEditingWord
+                    + ", mLength=" + mLength + ", cookie = " + mCookie
+                    + ", sel start = " + selectionStart + ", sel end = " + selectionEnd);
+        }
+
         for (int i = 0; i < mLength; i++) {
             final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i];
             if (mIds[i] < 0 || spellCheckSpan.isSpellCheckInProgress()) continue;
@@ -319,7 +326,7 @@
             } else {
                 isEditing = selectionEnd < start || selectionStart > end;
             }
-            if (start >= 0 && end > start && isEditing) {
+            if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
                 spellCheckSpan.setSpellCheckInProgress(true);
                 final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
                 textInfos[textInfosCount++] = textInfo;
@@ -546,7 +553,11 @@
     private class SpellParser {
         private Object mRange = new Object();
 
-        public void parse(int start, int end) {
+        // Forces to do spell checker even user is editing the word.
+        private boolean mForceCheckWhenEditingWord;
+
+        public void parse(int start, int end, boolean forceCheckWhenEditingWord) {
+            mForceCheckWhenEditingWord = forceCheckWhenEditingWord;
             final int max = mTextView.length();
             final int parseEnd;
             if (end > max) {
@@ -567,6 +578,7 @@
 
         public void stop() {
             removeRangeSpan((Editable) mTextView.getText());
+            mForceCheckWhenEditingWord = false;
         }
 
         private void setRangeSpan(Editable editable, int start, int end) {
@@ -617,7 +629,7 @@
                 if (DBG) {
                     Log.i(TAG, "No more spell check.");
                 }
-                removeRangeSpan(editable);
+                stop();
                 return;
             }
 
@@ -649,7 +661,7 @@
                     if (DBG) {
                         Log.i(TAG, "Incorrect range span.");
                     }
-                    removeRangeSpan(editable);
+                    stop();
                     return;
                 }
                 do {
@@ -778,7 +790,7 @@
                 removeRangeSpan(editable);
             }
 
-            spellCheck();
+            spellCheck(mForceCheckWhenEditingWord);
         }
 
         private <T> void removeSpansAt(Editable editable, int offset, T[] spans) {
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 3295fd2..d3600ef 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -35,6 +35,7 @@
 import android.graphics.Region.Op;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.text.Layout;
@@ -48,12 +49,14 @@
 import android.util.MathUtils;
 import android.view.Gravity;
 import android.view.MotionEvent;
+import android.view.RemotableViewMethod;
 import android.view.SoundEffectConstants;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.ViewStructure;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inspector.InspectableProperty;
+import android.widget.RemoteViews.RemoteView;
 
 import com.android.internal.R;
 
@@ -84,6 +87,7 @@
  * @attr ref android.R.styleable#Switch_thumbTextPadding
  * @attr ref android.R.styleable#Switch_track
  */
+@RemoteView
 public class Switch extends CompoundButton {
     private static final int THUMB_ANIMATION_DURATION = 250;
 
@@ -441,6 +445,7 @@
      *
      * @attr ref android.R.styleable#Switch_switchPadding
      */
+    @RemotableViewMethod
     public void setSwitchPadding(int pixels) {
         mSwitchPadding = pixels;
         requestLayout();
@@ -466,6 +471,7 @@
      *
      * @attr ref android.R.styleable#Switch_switchMinWidth
      */
+    @RemotableViewMethod
     public void setSwitchMinWidth(int pixels) {
         mSwitchMinWidth = pixels;
         requestLayout();
@@ -491,6 +497,7 @@
      *
      * @attr ref android.R.styleable#Switch_thumbTextPadding
      */
+    @RemotableViewMethod
     public void setThumbTextPadding(int pixels) {
         mThumbTextPadding = pixels;
         requestLayout();
@@ -533,10 +540,17 @@
      *
      * @attr ref android.R.styleable#Switch_track
      */
+    @RemotableViewMethod(asyncImpl = "setTrackResourceAsync")
     public void setTrackResource(@DrawableRes int resId) {
         setTrackDrawable(getContext().getDrawable(resId));
     }
 
+    /** @hide **/
+    public Runnable setTrackResourceAsync(@DrawableRes int resId) {
+        Drawable drawable = resId == 0 ? null : getContext().getDrawable(resId);
+        return () -> setTrackDrawable(drawable);
+    }
+
     /**
      * Get the drawable used for the track that the switch slides within.
      *
@@ -550,6 +564,23 @@
     }
 
     /**
+     * Set the drawable used for the track that the switch slides within to the specified Icon.
+     *
+     * @param icon an Icon holding the desired track, or {@code null} to clear
+     *             the track
+     */
+    @RemotableViewMethod(asyncImpl = "setTrackIconAsync")
+    public void setTrackIcon(@Nullable Icon icon) {
+        setTrackDrawable(icon == null ? null : icon.loadDrawable(getContext()));
+    }
+
+    /** @hide **/
+    public Runnable setTrackIconAsync(@Nullable Icon icon) {
+        Drawable track = icon == null ? null : icon.loadDrawable(getContext());
+        return () -> setTrackDrawable(track);
+    }
+
+    /**
      * Applies a tint to the track drawable. Does not modify the current
      * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
      * <p>
@@ -563,6 +594,7 @@
      * @see #getTrackTintList()
      * @see Drawable#setTintList(ColorStateList)
      */
+    @RemotableViewMethod
     public void setTrackTintList(@Nullable ColorStateList tint) {
         mTrackTintList = tint;
         mHasTrackTint = true;
@@ -607,6 +639,7 @@
      * @see #getTrackTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setTrackTintBlendMode(@Nullable BlendMode blendMode) {
         mTrackBlendMode = blendMode;
         mHasTrackTintMode = true;
@@ -686,10 +719,17 @@
      *
      * @attr ref android.R.styleable#Switch_thumb
      */
+    @RemotableViewMethod(asyncImpl = "setThumbResourceAsync")
     public void setThumbResource(@DrawableRes int resId) {
         setThumbDrawable(getContext().getDrawable(resId));
     }
 
+    /** @hide **/
+    public Runnable setThumbResourceAsync(@DrawableRes int resId) {
+        Drawable drawable = resId == 0 ? null : getContext().getDrawable(resId);
+        return () -> setThumbDrawable(drawable);
+    }
+
     /**
      * Get the drawable used for the switch "thumb" - the piece that the user
      * can physically touch and drag along the track.
@@ -704,6 +744,24 @@
     }
 
     /**
+     * Set the drawable used for the switch "thumb" - the piece that the user
+     * can physically touch and drag along the track - to the specified Icon.
+     *
+     * @param icon an Icon holding the desired thumb, or {@code null} to clear
+     *             the thumb
+     */
+    @RemotableViewMethod(asyncImpl = "setThumbIconAsync")
+    public void setThumbIcon(@Nullable Icon icon) {
+        setThumbDrawable(icon == null ? null : icon.loadDrawable(getContext()));
+    }
+
+    /** @hide **/
+    public Runnable setThumbIconAsync(@Nullable Icon icon) {
+        Drawable track = icon == null ? null : icon.loadDrawable(getContext());
+        return () -> setThumbDrawable(track);
+    }
+
+    /**
      * Applies a tint to the thumb drawable. Does not modify the current
      * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
      * <p>
@@ -717,6 +775,7 @@
      * @see #getThumbTintList()
      * @see Drawable#setTintList(ColorStateList)
      */
+    @RemotableViewMethod
     public void setThumbTintList(@Nullable ColorStateList tint) {
         mThumbTintList = tint;
         mHasThumbTint = true;
@@ -761,6 +820,7 @@
      * @see #getThumbTintMode()
      * @see Drawable#setTintBlendMode(BlendMode)
      */
+    @RemotableViewMethod
     public void setThumbTintBlendMode(@Nullable BlendMode blendMode) {
         mThumbBlendMode = blendMode;
         mHasThumbTintMode = true;
@@ -822,6 +882,7 @@
      *
      * @attr ref android.R.styleable#Switch_splitTrack
      */
+    @RemotableViewMethod
     public void setSplitTrack(boolean splitTrack) {
         mSplitTrack = splitTrack;
         invalidate();
@@ -852,6 +913,7 @@
      *
      * @attr ref android.R.styleable#Switch_textOn
      */
+    @RemotableViewMethod
     public void setTextOn(CharSequence textOn) {
         mTextOn = textOn;
         requestLayout();
@@ -875,6 +937,7 @@
      *
      * @attr ref android.R.styleable#Switch_textOff
      */
+    @RemotableViewMethod
     public void setTextOff(CharSequence textOff) {
         mTextOff = textOff;
         requestLayout();
@@ -889,6 +952,7 @@
      * @param showText {@code true} to display on/off text
      * @attr ref android.R.styleable#Switch_showText
      */
+    @RemotableViewMethod
     public void setShowText(boolean showText) {
         if (mShowText != showText) {
             mShowText = showText;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 8cfbca8..fe37c53 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -497,9 +497,9 @@
     private TextUtils.TruncateAt mEllipsize;
 
     // A flag to indicate the cursor was hidden by IME.
-    private boolean mImeTemporarilyConsumesInput;
+    private boolean mImeIsConsumingInput;
 
-    // Whether cursor is visible without regard to {@link mImeTemporarilyConsumesInput}.
+    // Whether cursor is visible without regard to {@link mImeConsumesInput}.
     // {code true} is the default value.
     private boolean mCursorVisibleFromAttr = true;
 
@@ -8750,7 +8750,7 @@
                 }
             }
             if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) {
-                outAttrs.internalImeOptions |= EditorInfo.IME_FLAG_APP_WINDOW_PORTRAIT;
+                outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT;
             }
             if (isMultilineInputType(outAttrs.inputType)) {
                 // Multi-line text editors should always show an enter key.
@@ -10506,8 +10506,8 @@
 
     /**
      * Set whether the cursor is visible. The default is true. Note that this property only
-     * makes sense for editable TextView. If IME is temporarily consuming the input, the cursor will
-     * be always invisible, visibility will be updated as the last state when IME does not consume
+     * makes sense for editable TextView. If IME is consuming the input, the cursor will always be
+     * invisible, visibility will be updated as the last state when IME does not consume
      * the input anymore.
      *
      * @see #isCursorVisible()
@@ -10521,20 +10521,20 @@
     }
 
     /**
-     * Sets the IME is temporarily consuming the input and make the cursor invisible if
-     * {@code imeTemporarilyConsumesInput} is {@code true}. Otherwise, make the cursor visible.
+     * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput}
+     * is {@code true}. Otherwise, make the cursor visible.
      *
-     * @param imeTemporarilyConsumesInput {@code true} if IME is temporarily consuming the input
+     * @param imeConsumesInput {@code true} if IME is consuming the input
      *
      * @hide
      */
-    public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
-        mImeTemporarilyConsumesInput = imeTemporarilyConsumesInput;
+    public void setImeConsumesInput(boolean imeConsumesInput) {
+        mImeIsConsumingInput = imeConsumesInput;
         updateCursorVisibleInternal();
     }
 
     private void updateCursorVisibleInternal()  {
-        boolean visible = mCursorVisibleFromAttr && !mImeTemporarilyConsumesInput;
+        boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput;
         if (visible && mEditor == null) return; // visible is the default value with no edit data
         createEditorIfNeeded();
         if (mEditor.mCursorVisible != visible) {
@@ -10550,7 +10550,7 @@
 
     /**
      * @return whether or not the cursor is visible (assuming this TextView is editable). This
-     * method may return {@code false} when the IME is temporarily consuming the input even if the
+     * method may return {@code false} when the IME is consuming the input even if the
      * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)}
      * is called.
      *
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index f29eb39..cdb4762 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.os.IBinder;
@@ -151,6 +152,7 @@
     /** Gets direct child tasks (ordered from top-to-bottom) */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<ActivityManager.RunningTaskInfo> getChildTasks(
             @NonNull WindowContainerToken parent, @NonNull int[] activityTypes) {
         try {
@@ -163,6 +165,7 @@
     /** Gets all root tasks on a display (ordered from top-to-bottom) */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<ActivityManager.RunningTaskInfo> getRootTasks(
             int displayId, @NonNull int[] activityTypes) {
         try {
diff --git a/core/java/com/android/internal/annotations/CompositeRWLock.java b/core/java/com/android/internal/annotations/CompositeRWLock.java
new file mode 100644
index 0000000..b6ddfc4
--- /dev/null
+++ b/core/java/com/android/internal/annotations/CompositeRWLock.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 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.internal.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies a list of locks which are required for read/write operations on a data field.
+ *
+ * <p>
+ * To annotate methods accessing the data field with the annotation {@link CompositeRWLock},
+ * use {@link GuardedBy#value} to annotate method w/ write and/or read access to the data field,
+ * use {@link GuardedBy#anyOf} to annotate method w/ read only access to the data field.
+ * </p>
+ *
+ * <p>
+ * When its {@link #value()} consists of multiple locks:
+ * <ul>
+ *   <li>To write to the protected data, acquire <b>all</b> of the locks
+ *       in the order of the appearance in the {@link #value}.</li>
+ *   <li>To read from the protected data, acquire any of the locks in the {@link #value}.</li>
+ * </ul>
+ * </p>
+ */
+@Target({FIELD})
+@Retention(RetentionPolicy.CLASS)
+public @interface CompositeRWLock {
+    String[] value() default {};
+}
diff --git a/core/java/com/android/internal/annotations/GuardedBy.java b/core/java/com/android/internal/annotations/GuardedBy.java
index 0e63214..c05c4ab 100644
--- a/core/java/com/android/internal/annotations/GuardedBy.java
+++ b/core/java/com/android/internal/annotations/GuardedBy.java
@@ -16,7 +16,9 @@
 
 package com.android.internal.annotations;
 
-import java.lang.annotation.ElementType;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -25,8 +27,32 @@
  * Annotation type used to mark a method or field that can only be accessed when
  * holding the referenced locks.
  */
-@Target({ ElementType.FIELD, ElementType.METHOD })
+@Target({FIELD, METHOD})
 @Retention(RetentionPolicy.CLASS)
 public @interface GuardedBy {
-    String[] value();
+    /**
+     * Specifies a list of locks to be held in order to access the field/method
+     * annotated with this; when used in conjunction with the {@link CompositeRWLock}, locks
+     * should be acquired in the order of the appearance in the {@link #value} here.
+     *
+     * <p>
+     * If specified, {@link #anyOf()} must be null.
+     * </p>
+     *
+     * @see CompositeRWLock
+     */
+    String[] value() default {};
+
+    /**
+     * Specifies a list of locks where at least one of them must be held in order to access
+     * the field/method annotated with this; it should be <em>only</em> used in the conjunction
+     * with the {@link CompositeRWLock}.
+     *
+     * <p>
+     * If specified, {@link #allOf()} must be null.
+     * </p>
+     *
+     * @see CompositeRWLock
+     */
+    String[] anyOf() default {};
 }
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 7a30ee2..c1952c7 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -52,7 +52,7 @@
 
     // Remaining methods are only used in Java.
 
-    BatteryUsageStats getBatteryUsageStats(in BatteryUsageStatsQuery query);
+    List<BatteryUsageStats> getBatteryUsageStats(in List<BatteryUsageStatsQuery> queries);
 
     @UnsupportedAppUsage
     byte[] getStatistics();
@@ -134,7 +134,7 @@
     void noteWifiBatchedScanStartedFromSource(in WorkSource ws, int csph);
     void noteWifiBatchedScanStoppedFromSource(in WorkSource ws);
     void noteWifiRadioPowerState(int powerState, long timestampNs, int uid);
-    void noteNetworkInterfaceType(String iface, int type);
+    void noteNetworkInterfaceForTransports(String iface, in int[] transportTypes);
     void noteNetworkStatsEnabled();
     void noteDeviceIdleMode(int mode, String activeReason, int activeUid);
     void setBatteryState(int status, int health, int plugType, int level, int temp, int volt,
diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS
index 99692d0..7ade05c 100644
--- a/core/java/com/android/internal/app/OWNERS
+++ b/core/java/com/android/internal/app/OWNERS
@@ -5,3 +5,4 @@
 per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS
 per-file IVoice* = file:/core/java/android/service/voice/OWNERS
 per-file *Hotword* = file:/core/java/android/service/voice/OWNERS
+per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 13358daf..ee98878 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -62,6 +62,11 @@
      */
     public static final String ENABLE_NAS_FEEDBACK = "enable_nas_feedback";
 
+    /**
+     * Whether the Notification Assistant can label a notification not a conversation
+     */
+    public static final String ENABLE_NAS_NOT_CONVERSATION = "enable_nas_not_conversation";
+
     // Flags related to screenshot intelligence
 
     /**
@@ -420,6 +425,12 @@
     public static final String PIP_STASHING = "pip_stashing";
 
     /**
+     * (float) The threshold velocity to cause PiP to be stashed when flinging from one edge to the
+     * other.
+     */
+    public static final String PIP_STASH_MINIMUM_VELOCITY_THRESHOLD = "pip_velocity_threshold";
+
+    /**
      * (float) Bottom height in DP for Back Gesture.
      */
     public static final String BACK_GESTURE_BOTTOM_HEIGHT = "back_gesture_bottom_height";
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index bbfd07b..c74c39a 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -27,9 +27,10 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageLite;
-import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.SELinux;
@@ -86,17 +87,19 @@
         final boolean debuggable;
 
         public static Handle create(File packageFile) throws IOException {
-            try {
-                final PackageLite lite = PackageParser.parsePackageLite(packageFile, 0);
-                return create(lite);
-            } catch (PackageParserException e) {
-                throw new IOException("Failed to parse package: " + packageFile, e);
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<PackageLite> ret = ApkLiteParseUtils.parsePackageLite(input.reset(),
+                    packageFile, /* flags */ 0);
+            if (ret.isError()) {
+                throw new IOException("Failed to parse package: " + packageFile,
+                        ret.getException());
             }
+            return create(ret.getResult());
         }
 
         public static Handle create(PackageLite lite) throws IOException {
-            return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs,
-                    lite.debuggable);
+            return create(lite.getAllApkPaths(), lite.isMultiArch(), lite.isExtractNativeLibs(),
+                    lite.isDebuggable());
         }
 
         public static Handle create(List<String> codePaths, boolean multiArch,
@@ -122,14 +125,14 @@
 
         public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
             final long[] apkHandles = new long[1];
-            final String path = lite.baseCodePath;
+            final String path = lite.getBaseApkPath();
             apkHandles[0] = nativeOpenApkFd(fd, path);
             if (apkHandles[0] == 0) {
                 throw new IOException("Unable to open APK " + path + " from fd " + fd);
             }
 
-            return new Handle(new String[]{path}, apkHandles, lite.multiArch,
-                    lite.extractNativeLibs, lite.debuggable);
+            return new Handle(new String[]{path}, apkHandles, lite.isMultiArch(),
+                    lite.isExtractNativeLibs(), lite.isDebuggable());
         }
 
         Handle(String[] apkPaths, long[] apkHandles, boolean multiArch,
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 2b78be3..c2f2052 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -24,8 +24,8 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.dex.DexMetadataHelper;
+import android.content.pm.parsing.PackageLite;
 import android.os.Environment;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -84,8 +84,8 @@
 
     /**
      * A group of external dependencies used in
-     * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
-     * from the system or mocked ones for testing purposes.
+     * {@link #resolveInstallVolume(Context, String, int, long, TestableInterface)}.
+     * It can be backed by real values from the system or mocked ones for testing purposes.
      */
     public static abstract class TestableInterface {
         abstract public StorageManager getStorageManager(Context context);
@@ -447,7 +447,7 @@
         long sizeBytes = 0;
 
         // Include raw APKs, and possibly unpacked resources
-        for (String codePath : pkg.getAllCodePaths()) {
+        for (String codePath : pkg.getAllApkPaths()) {
             final File codeFile = new File(codePath);
             sizeBytes += codeFile.length();
         }
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index af666d8..9a44c05 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.content;
 
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -234,7 +235,7 @@
     /**
      * Called when an existing package is updated or its disabled state changes.
      */
-    public void onPackageModified(String packageName) {
+    public void onPackageModified(@NonNull String packageName) {
     }
     
     public boolean didSomePackagesChange() {
diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java
index a85cf56..6b5cb8d 100644
--- a/core/java/com/android/internal/content/om/OverlayScanner.java
+++ b/core/java/com/android/internal/content/om/OverlayScanner.java
@@ -20,7 +20,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.PackageParser;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -124,15 +127,17 @@
     /** Extracts information about the overlay from its manifest. */
     @VisibleForTesting
     public ParsedOverlayInfo parseOverlayManifest(File overlayApk) {
-        try {
-            final PackageParser.ApkLite apkLite = PackageParser.parseApkLite(overlayApk, 0);
-            return apkLite.targetPackageName == null ? null :
-                    new ParsedOverlayInfo(apkLite.packageName, apkLite.targetPackageName,
-                            apkLite.targetSdkVersion, apkLite.overlayIsStatic,
-                            apkLite.overlayPriority, new File(apkLite.codePath));
-        } catch (PackageParser.PackageParserException e) {
-            Log.w(TAG, "Got exception loading overlay.", e);
+        final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat();
+        final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(),
+                overlayApk, /* flags */ 0);
+        if (ret.isError()) {
+            Log.w(TAG, "Got exception loading overlay.", ret.getException());
             return null;
         }
+        final ApkLite apkLite = ret.getResult();
+        return apkLite.getTargetPackageName() == null ? null :
+                new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(),
+                        apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(),
+                        apkLite.getOverlayPriority(), new File(apkLite.getPath()));
     }
 }
diff --git a/graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
similarity index 78%
rename from graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java
rename to core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
index 7e75c5b..96dac56 100644
--- a/graphics/java/android/graphics/drawable/BackgroundBlurDrawable.java
+++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-package android.graphics.drawable;
+package com.android.internal.graphics.drawable;
 
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
+import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
@@ -31,71 +30,59 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RenderNode;
+import android.graphics.drawable.Drawable;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.SurfaceControl;
-import android.view.View;
 import android.view.ViewRootImpl;
 
+import com.android.internal.R;
+
 /**
  * A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state
  * to SurfaceFlinger.
- *
- * @hide
  */
-@SystemApi
 public final class BackgroundBlurDrawable extends Drawable {
+
     private static final String TAG = BackgroundBlurDrawable.class.getSimpleName();
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    private final Aggregator mAggregator;
     private final RenderNode mRenderNode;
     private final Paint mPaint = new Paint();
     private final Path mRectPath = new Path();
     private final float[] mTmpRadii = new float[8];
     private final SurfaceControl.BlurRegion mBlurRegion = new SurfaceControl.BlurRegion();
 
-    private Aggregator mAggregator;
-
     // This will be called from a thread pool.
     private final RenderNode.PositionUpdateListener mPositionUpdateListener =
             new RenderNode.PositionUpdateListener() {
             @Override
             public void positionChanged(long frameNumber, int left, int top, int right,
                     int bottom) {
-                if (mAggregator == null) {
+                synchronized (mAggregator) {
                     mBlurRegion.rect.set(left, top, right, bottom);
-                } else {
-                    synchronized (mAggregator) {
-                        mBlurRegion.rect.set(left, top, right, bottom);
-                        mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
-                    }
+                    mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
                 }
             }
 
             @Override
             public void positionLost(long frameNumber) {
-                if (mAggregator == null) {
+                synchronized (mAggregator) {
                     mBlurRegion.rect.setEmpty();
-                } else {
-                    synchronized (mAggregator) {
-                        mBlurRegion.rect.setEmpty();
-                        mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
-                    }
+                    mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
                 }
             }
         };
 
-    @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_BLUR)
-    public BackgroundBlurDrawable() {
+    private BackgroundBlurDrawable(Aggregator aggregator) {
+        mAggregator = aggregator;
         mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
         mPaint.setColor(Color.TRANSPARENT);
         mRenderNode = new RenderNode("BackgroundBlurDrawable");
         mRenderNode.addPositionUpdateListener(mPositionUpdateListener);
     }
 
-    /**
-     * @hide
-     */
     @Override
     public void draw(@NonNull Canvas canvas) {
         if (mRectPath.isEmpty() || !isVisible() || getAlpha() == 0) {
@@ -113,9 +100,6 @@
         mPaint.setColor(color);
     }
 
-    /**
-     * @hide
-     */
     @Override
     public boolean setVisible(boolean visible, boolean restart) {
         boolean changed = super.setVisible(visible, restart);
@@ -125,9 +109,6 @@
         return changed;
     }
 
-    /**
-     * @hide
-     */
     @Override
     public void setAlpha(int alpha) {
         mBlurRegion.alpha = alpha / 255f;
@@ -158,12 +139,12 @@
      */
     public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL,
             float cornerRadiusBR) {
-        maybeRunSynchronized(() -> {
+        synchronized (mAggregator) {
             mBlurRegion.cornerRadiusTL = cornerRadiusTL;
             mBlurRegion.cornerRadiusTR = cornerRadiusTR;
             mBlurRegion.cornerRadiusBL = cornerRadiusBL;
             mBlurRegion.cornerRadiusBR = cornerRadiusBR;
-        });
+        }
         updatePath();
         invalidateSelf();
     }
@@ -176,13 +157,12 @@
     }
 
     private void updatePath() {
-        maybeRunSynchronized(() -> {
+        synchronized (mAggregator) {
             mTmpRadii[0] = mTmpRadii[1] = mBlurRegion.cornerRadiusTL;
             mTmpRadii[2] = mTmpRadii[3] = mBlurRegion.cornerRadiusTR;
             mTmpRadii[4] = mTmpRadii[5] = mBlurRegion.cornerRadiusBL;
             mTmpRadii[6] = mTmpRadii[7] = mBlurRegion.cornerRadiusBR;
-        });
-
+        }
         mRectPath.reset();
         if (getAlpha() == 0 || !isVisible()) {
             return;
@@ -192,62 +172,19 @@
                 Path.Direction.CW);
     }
 
-    /**
-     * @hide
-     */
     @Override
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
         throw new IllegalArgumentException("not implemented");
     }
 
-    /**
-     * @hide
-     */
     @Override
     public int getOpacity() {
         return PixelFormat.TRANSLUCENT;
     }
 
     /**
-     *  @hide
-     */
-    @Override
-    public void onAttached(@NonNull View v) {
-        super.onAttached(v);
-        mAggregator = v.getViewRootImpl().getBlurRegionAggregator();
-    }
-
-    /**
-     *  @hide
-     */
-    @Override
-    public void onDetached(@NonNull View v) {
-        super.onDetached(v);
-        mAggregator = null;
-    }
-
-    /**
-     * The Aggregator is called from the RenderThread to aggregate all blur regions and send them
-     * to SurfaceFlinger. Since the BackgroundBlurDrawable could be updated at any time from the
-     * main thread, we need to synchronize the two threads. The BackgroundBlurDrawable may be
-     * instantiated before the ViewRootImpl is created, i.e. before the Aggregator is created.
-     * In that case, updates are not synchronized.
-     */
-    private void maybeRunSynchronized(Runnable r) {
-        if (mAggregator == null) {
-            r.run();
-        } else {
-            synchronized (mAggregator) {
-                r.run();
-            }
-        }
-    }
-
-    /**
      * Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a
      * message when it's time to propagate them.
-     *
-     * @hide
      */
     public static final class Aggregator {
 
@@ -262,6 +199,16 @@
         }
 
         /**
+         * Creates a blur region with default radius.
+         */
+        public BackgroundBlurDrawable createBackgroundBlurDrawable(Context context) {
+            BackgroundBlurDrawable drawable = new BackgroundBlurDrawable(this);
+            drawable.setBlurRadius(context.getResources().getDimensionPixelSize(
+                    R.dimen.default_background_blur_radius));
+            return drawable;
+        }
+
+        /**
          * Called from RenderThread only, already locked.
          * @param drawable
          * @param blurRegion
diff --git a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
index a11c7ef..dfcc914 100644
--- a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
+++ b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
@@ -16,8 +16,9 @@
 
 package com.android.internal.graphics.fonts;
 
+import android.os.ParcelFileDescriptor;
+import android.graphics.fonts.FontUpdateRequest;
 import android.text.FontConfig;
-import android.graphics.fonts.SystemFontState;
 
 /**
  * System private interface for talking with
@@ -26,4 +27,6 @@
  */
 interface IFontManager {
     FontConfig getFontConfig();
+
+    int updateFont(int baseVersion, in FontUpdateRequest request);
 }
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index e0f9554..3d896c8 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -59,6 +59,7 @@
 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
 import com.android.internal.jank.FrameTracker.ViewRootWrapper;
+import com.android.internal.util.PerfettoTrigger;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/core/java/com/android/internal/jank/PerfettoTrigger.java b/core/java/com/android/internal/jank/PerfettoTrigger.java
deleted file mode 100644
index 643d24a..0000000
--- a/core/java/com/android/internal/jank/PerfettoTrigger.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-//TODO (165884885): Make PerfettoTrigger more generic and move it to another package.
-package com.android.internal.jank;
-
-import android.annotation.NonNull;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-/**
- * A trigger implementation with perfetto backend.
- * @hide
- */
-public class PerfettoTrigger {
-    private static final String TAG = PerfettoTrigger.class.getSimpleName();
-    private static final boolean DEBUG = false;
-    private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
-
-    /**
-     * @param triggerName The name of the trigger. Must match the value defined in the AOT
-     *                    Perfetto config.
-     */
-    public static void trigger(String triggerName) {
-        try {
-            ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
-            if (DEBUG) {
-                StringBuilder sb = new StringBuilder();
-                for (String arg : pb.command()) {
-                    sb.append(arg).append(" ");
-                }
-                Log.d(TAG, "Triggering " + sb.toString());
-            }
-            Process process = pb.start();
-            if (DEBUG) {
-                readConsoleOutput(process);
-            }
-        } catch (IOException | InterruptedException e) {
-            Log.w(TAG, "Failed to trigger " + triggerName, e);
-        }
-    }
-
-    private static void readConsoleOutput(@NonNull Process process)
-            throws IOException, InterruptedException {
-        process.waitFor();
-        try (BufferedReader errReader =
-                     new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
-            StringBuilder errLine = new StringBuilder();
-            String line;
-            while ((line = errReader.readLine()) != null) {
-                errLine.append(line).append("\n");
-            }
-            errLine.append(", code=").append(process.exitValue());
-            Log.d(TAG, "err message=" + errLine.toString());
-        }
-    }
-}
diff --git a/core/java/com/android/internal/listeners/ListenerTransport.java b/core/java/com/android/internal/listeners/ListenerTransport.java
index 9d6210e..1a6870c 100644
--- a/core/java/com/android/internal/listeners/ListenerTransport.java
+++ b/core/java/com/android/internal/listeners/ListenerTransport.java
@@ -16,54 +16,43 @@
 
 package com.android.internal.listeners;
 
-
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 
-import com.android.internal.util.Preconditions;
-
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
- * A listener registration object which holds data associated with a listener, such the executor
- * the listener should run on.
+ * A listener transport object which can run listener operations on an executor.
  *
  * @param <TListener> listener type
  */
-public class ListenerTransport<TListener> {
-
-    private final Executor mExecutor;
-
-    private volatile @Nullable TListener mListener;
-
-    protected ListenerTransport(@NonNull Executor executor, @NonNull TListener listener) {
-        Preconditions.checkArgument(executor != null, "invalid null executor");
-        Preconditions.checkArgument(listener != null, "invalid null listener/callback");
-        mExecutor = executor;
-        mListener = listener;
-    }
+public interface ListenerTransport<TListener> {
 
     /**
-     * Prevents any listener invocations that happen-after this call.
+     * Should return a valid listener until {@link #unregister()} is invoked, and must return
+     * null after that. Recommended (but not required) that this is implemented via a volatile
+     * variable.
      */
-    public final void unregister() {
-        mListener = null;
-    }
+    @Nullable TListener getListener();
+
+    /**
+     * Must be implemented so that {@link #getListener()} returns null after this is invoked.
+     */
+    void unregister();
 
     /**
      * Executes the given operation for the listener.
      */
-    public final void execute(@NonNull Consumer<TListener> operation) {
+    default void execute(Executor executor, Consumer<TListener> operation) {
         Objects.requireNonNull(operation);
 
-        if (mListener == null) {
+        if (getListener() == null) {
             return;
         }
 
-        mExecutor.execute(() -> {
-            TListener listener = mListener;
+        executor.execute(() -> {
+            TListener listener = getListener();
             if (listener == null) {
                 return;
             }
@@ -71,15 +60,4 @@
             operation.accept(listener);
         });
     }
-
-    @Override
-    public final boolean equals(Object obj) {
-        // intentionally bound to reference equality so removal works as expected
-        return this == obj;
-    }
-
-    @Override
-    public final int hashCode() {
-        return super.hashCode();
-    }
 }
diff --git a/core/java/com/android/internal/listeners/ListenerTransportManager.java b/core/java/com/android/internal/listeners/ListenerTransportManager.java
new file mode 100644
index 0000000..0d5d1b7b
--- /dev/null
+++ b/core/java/com/android/internal/listeners/ListenerTransportManager.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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.internal.listeners;
+
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * A listener transport manager which handles mappings between the client facing listener and system
+ * server facing transport. Supports transports which may be removed either from the client side or
+ * from the system server side without leaking memory.
+ *
+ * @param <TTransport>> transport type
+ */
+public abstract class ListenerTransportManager<TTransport extends ListenerTransport<?>> {
+
+    @GuardedBy("mRegistrations")
+    private final Map<Object, WeakReference<TTransport>> mRegistrations;
+
+    protected ListenerTransportManager() {
+        // using weakhashmap means that the transport may be GCed if the server drops its reference,
+        // and thus the listener may be GCed as well if the client drops that reference. if the
+        // server will never drop a reference without warning (ie, transport removal may only be
+        // initiated from the client side), then arraymap or similar may be used without fear of
+        // memory leaks.
+        mRegistrations = new WeakHashMap<>();
+    }
+
+    /**
+     * Adds a new transport with the given listener key.
+     */
+    public final void addListener(Object key, TTransport transport) {
+        try {
+            synchronized (mRegistrations) {
+                // ordering of operations is important so that if an error occurs at any point we
+                // are left in a reasonable state
+                registerTransport(transport);
+                WeakReference<TTransport> oldTransportRef = mRegistrations.put(key,
+                        new WeakReference<>(transport));
+                if (oldTransportRef != null) {
+                    TTransport oldTransport = oldTransportRef.get();
+                    if (oldTransport != null) {
+                        oldTransport.unregister();
+                        unregisterTransport(oldTransport);
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes the transport with the given listener key.
+     */
+    public final void removeListener(Object key) {
+        try {
+            synchronized (mRegistrations) {
+                // ordering of operations is important so that if an error occurs at any point we
+                // are left in a reasonable state
+                WeakReference<TTransport> transportRef = mRegistrations.remove(key);
+                if (transportRef != null) {
+                    TTransport transport = transportRef.get();
+                    if (transport != null) {
+                        transport.unregister();
+                        unregisterTransport(transport);
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    protected abstract void registerTransport(TTransport transport) throws RemoteException;
+
+    protected abstract void unregisterTransport(TTransport transport) throws RemoteException;
+}
diff --git a/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java b/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java
deleted file mode 100644
index fc1d69f..0000000
--- a/core/java/com/android/internal/listeners/ListenerTransportMultiplexer.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2020 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.internal.listeners;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Build;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.IndentingPrintWriter;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-
-import java.io.FileDescriptor;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * A listener multiplexer designed for use by client-side code. This class ensures that listeners
- * are never invoked while a lock is held. This class is only useful for multiplexing listeners -
- * if all client listeners can be combined into a single server request, and all server results will
- * be delivered to all clients.
- *
- * By default, the multiplexer will replace requests on the server simply by registering the new
- * request and trusting the server to know this is replacing the old request. If the server needs to
- * have the old request unregistered first, subclasses should override
- * {@link #reregisterWithServer(Object, Object)}.
- *
- * @param <TRequest>  listener request type, may be Void
- * @param <TListener> listener type
- */
-public abstract class ListenerTransportMultiplexer<TRequest, TListener> {
-
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> mRegistrations =
-            new ArrayMap<>();
-
-    @GuardedBy("mLock")
-    private boolean mServiceRegistered = false;
-
-    @GuardedBy("mLock")
-    private TRequest mCurrentRequest;
-
-    /**
-     * Should be implemented to register the given merged request with the server.
-     *
-     * @see #reregisterWithServer(Object, Object)
-     */
-    protected abstract void registerWithServer(TRequest mergedRequest) throws RemoteException;
-
-    /**
-     * Invoked when the server already has a request registered, and it is being replaced with a new
-     * request. The default implementation simply registers the new request, trusting the server to
-     * overwrite the old request.
-     */
-    protected void reregisterWithServer(TRequest oldMergedRequest, TRequest mergedRequest)
-            throws RemoteException {
-        registerWithServer(mergedRequest);
-    }
-
-    /**
-     * Should be implemented to unregister from the server.
-     */
-    protected abstract void unregisterWithServer() throws RemoteException;
-
-    /**
-     * Called in order to generate a merged request from the given requests. The list of requests
-     * will never be empty.
-     */
-    protected @Nullable TRequest mergeRequests(Collection<TRequest> requests) {
-        if (Build.IS_DEBUGGABLE) {
-            for (TRequest request : requests) {
-                // if using non-null requests then implementations must override this method
-                Preconditions.checkState(request == null);
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Adds a new listener with no request, using the listener as the key.
-     */
-    public void addListener(@NonNull TListener listener, @NonNull Executor executor) {
-        addListener(listener, null, listener, executor);
-    }
-
-    /**
-     * Adds a new listener with the given request, using the listener as the key.
-     */
-    public void addListener(@Nullable TRequest request, @NonNull TListener listener,
-            @NonNull Executor executor) {
-        addListener(listener, request, listener, executor);
-    }
-
-    /**
-     * Adds a new listener with the given request using a custom key.
-     */
-    public void addListener(@NonNull Object key, @Nullable TRequest request,
-            @NonNull TListener listener, @NonNull Executor executor) {
-        Objects.requireNonNull(key);
-        RequestListenerTransport<TRequest, TListener> registration =
-                new RequestListenerTransport<>(request, executor, listener);
-
-        synchronized (mLock) {
-            ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> newRegistrations =
-                    new ArrayMap<>(mRegistrations.size() + 1);
-            newRegistrations.putAll(mRegistrations);
-            RequestListenerTransport<TRequest, TListener> old = newRegistrations.put(key,
-                    registration);
-            mRegistrations = newRegistrations;
-
-            if (old != null) {
-                old.unregister();
-            }
-
-            updateService();
-        }
-    }
-
-    /**
-     * Removes the listener with the given key.
-     */
-    public void removeListener(@NonNull Object key) {
-        Objects.requireNonNull(key);
-
-        synchronized (mLock) {
-            if (!mRegistrations.containsKey(key)) {
-                return;
-            }
-
-            ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> newRegistrations =
-                    new ArrayMap<>(mRegistrations);
-            RequestListenerTransport<TRequest, TListener> old = newRegistrations.remove(key);
-            mRegistrations = newRegistrations;
-
-            if (old != null) {
-                old.unregister();
-                updateService();
-            }
-        }
-    }
-
-    private void updateService() {
-        synchronized (mLock) {
-            if (mRegistrations.isEmpty()) {
-                mCurrentRequest = null;
-                if (mServiceRegistered) {
-                    try {
-                        mServiceRegistered = false;
-                        unregisterWithServer();
-                    } catch (RemoteException e) {
-                        throw e.rethrowFromSystemServer();
-                    }
-                }
-                return;
-            }
-
-            ArrayList<TRequest> requests = new ArrayList<>(mRegistrations.size());
-            for (int i = 0; i < mRegistrations.size(); i++) {
-                requests.add(mRegistrations.valueAt(i).getRequest());
-            }
-
-            TRequest merged = mergeRequests(requests);
-            if (!mServiceRegistered || !Objects.equals(merged, mCurrentRequest)) {
-                TRequest old = mCurrentRequest;
-                mCurrentRequest = null;
-                try {
-                    if (mServiceRegistered) {
-                        // if a remote exception is thrown the service should not be registered
-                        mServiceRegistered = false;
-                        reregisterWithServer(old, merged);
-                    } else {
-                        registerWithServer(merged);
-                    }
-                    mCurrentRequest = merged;
-                    mServiceRegistered = true;
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
-                }
-            }
-        }
-    }
-
-    protected final void deliverToListeners(Consumer<TListener> operation) {
-        ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> registrations;
-        synchronized (mLock) {
-            registrations = mRegistrations;
-        }
-
-        try {
-            for (int i = 0; i < registrations.size(); i++) {
-                registrations.valueAt(i).execute(operation);
-            }
-        } finally {
-            onOperationFinished(operation);
-        }
-    }
-
-    /**
-     * Invoked when an operation is finished. This method will always be called once for every call
-     * to {@link #deliverToListeners(Consumer)}, regardless of whether the operation encountered any
-     * error or failed to execute in any way for any listeners.
-     */
-    protected void onOperationFinished(@NonNull Consumer<TListener> operation) {}
-
-    /**
-     * Dumps debug information.
-     */
-    public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
-        ArrayMap<Object, RequestListenerTransport<TRequest, TListener>> registrations;
-        synchronized (mLock) {
-            registrations = mRegistrations;
-
-            ipw.print("service: ");
-            if (mServiceRegistered) {
-                if (mCurrentRequest == null) {
-                    ipw.print("request registered");
-                } else {
-                    ipw.print("request registered - " + mCurrentRequest);
-                }
-            } else {
-                ipw.print("unregistered");
-            }
-            ipw.println();
-        }
-
-        if (!registrations.isEmpty()) {
-            ipw.println("listeners:");
-
-            ipw.increaseIndent();
-            for (int i = 0; i < registrations.size(); i++) {
-                ipw.print(registrations.valueAt(i));
-            }
-            ipw.decreaseIndent();
-        }
-    }
-}
diff --git a/core/java/com/android/internal/listeners/RequestListenerTransport.java b/core/java/com/android/internal/listeners/RequestListenerTransport.java
deleted file mode 100644
index 178de06..0000000
--- a/core/java/com/android/internal/listeners/RequestListenerTransport.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2020 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.internal.listeners;
-
-import android.annotation.Nullable;
-
-import java.util.concurrent.Executor;
-
-/**
- * A listener transport with an associated request.
- *
- * @param <TRequest>  request type
- * @param <TListener> listener type
- */
-public class RequestListenerTransport<TRequest, TListener> extends ListenerTransport<TListener> {
-
-    private final @Nullable TRequest mRequest;
-
-    protected RequestListenerTransport(@Nullable TRequest request, Executor executor,
-            TListener listener) {
-        super(executor, listener);
-        mRequest = request;
-    }
-
-    /**
-     * Returns the request associated with this transport.
-     */
-    public final @Nullable TRequest getRequest() {
-        return mRequest;
-    }
-}
diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/com/android/internal/net/VpnInfo.java
deleted file mode 100644
index e74af5e..0000000
--- a/core/java/com/android/internal/net/VpnInfo.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2015 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.internal.net;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-
-/**
- * A lightweight container used to carry information of the ongoing VPN.
- * Internal use only..
- *
- * @hide
- */
-public class VpnInfo implements Parcelable {
-    public int ownerUid;
-    public String vpnIface;
-    public String[] underlyingIfaces;
-
-    @Override
-    public String toString() {
-        return "VpnInfo{"
-                + "ownerUid=" + ownerUid
-                + ", vpnIface='" + vpnIface + '\''
-                + ", underlyingIfaces='" + Arrays.toString(underlyingIfaces) + '\''
-                + '}';
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(ownerUid);
-        dest.writeString(vpnIface);
-        dest.writeStringArray(underlyingIfaces);
-    }
-
-    public static final Parcelable.Creator<VpnInfo> CREATOR = new Parcelable.Creator<VpnInfo>() {
-        @Override
-        public VpnInfo createFromParcel(Parcel source) {
-            VpnInfo info = new VpnInfo();
-            info.ownerUid = source.readInt();
-            info.vpnIface = source.readString();
-            info.underlyingIfaces = source.readStringArray();
-            return info;
-        }
-
-        @Override
-        public VpnInfo[] newArray(int size) {
-            return new VpnInfo[size];
-        }
-    };
-}
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 6609ebe..8fe17fb 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -54,6 +54,7 @@
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah)
                     .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs);
         }
+        // TODO(b/178140704): Attribute *measured* total usage for BatteryUsageStats.
     }
 
     /**
@@ -66,7 +67,8 @@
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
         final long durationMs = calculateDuration(batteryStats, rawRealtimeUs, statsType);
-        final double powerMah = mPowerEstimator.calculatePower(durationMs);
+        final double powerMah = getMeasuredOrEstimatedPower(
+                batteryStats.getScreenDozeEnergy(), durationMs);
         if (powerMah > 0) {
             BatterySipper bs = new BatterySipper(BatterySipper.DrainType.AMBIENT_DISPLAY, null, 0);
             bs.usagePowerMah = powerMah;
@@ -79,4 +81,12 @@
     private long calculateDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
         return batteryStats.getScreenDozeTime(rawRealtimeUs, statsType) / 1000;
     }
+
+    private double getMeasuredOrEstimatedPower(long measuredEnergyUJ, long durationMs) {
+        if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
+            return mAhToUJ(measuredEnergyUJ);
+        } else {
+            return mPowerEstimator.calculatePower(durationMs);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/BatterySipper.java b/core/java/com/android/internal/os/BatterySipper.java
index c8805dd..af61f91 100644
--- a/core/java/com/android/internal/os/BatterySipper.java
+++ b/core/java/com/android/internal/os/BatterySipper.java
@@ -35,6 +35,8 @@
     /**
      * Smeared power from screen usage.
      * We split the screen usage power and smear them among apps, based on activity time.
+     * The actual screen usage power may be measured or estimated, affecting the granularity and
+     * accuracy of the smearing, but the smearing algorithm is essentially the same.
      */
     public double screenPowerMah;
 
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 4a24358..aa5015a 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -23,7 +23,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.SensorManager;
-import android.net.ConnectivityManager;
 import android.os.BatteryStats;
 import android.os.BatteryStats.Uid;
 import android.os.Build;
@@ -37,11 +36,10 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.text.format.DateUtils;
+import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
-import android.util.SparseLongArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
@@ -120,12 +118,11 @@
     private double mMaxDrainedPower;
 
     public static boolean checkWifiOnly(Context context) {
-        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
-                Context.CONNECTIVITY_SERVICE);
-        if (cm == null) {
+        final TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+        if (tm == null) {
             return false;
         }
-        return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        return !tm.isDataCapable();
     }
 
     @UnsupportedAppUsage
@@ -380,6 +377,7 @@
         mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
                 * mPowerProfile.getBatteryCapacity()) / 100;
 
+        // Create list of (almost all) sippers, calculate their usage, and put them in mUsageList.
         processAppUsage(asUsers);
 
         Collections.sort(mUsageList);
@@ -557,8 +555,7 @@
     }
 
     /**
-     * Mark the {@link BatterySipper} that we should hide and smear the screen usage based on
-     * foreground activity time.
+     * Mark the {@link BatterySipper} that we should hide.
      *
      * @param sippers sipper list that need to check and remove
      * @return the total power of the hidden items of {@link BatterySipper}
@@ -566,7 +563,6 @@
      */
     public double removeHiddenBatterySippers(List<BatterySipper> sippers) {
         double proportionalSmearPowerMah = 0;
-        BatterySipper screenSipper = null;
         for (int i = sippers.size() - 1; i >= 0; i--) {
             final BatterySipper sipper = sippers.get(i);
             sipper.shouldHide = shouldHideSipper(sipper);
@@ -582,45 +578,11 @@
                     proportionalSmearPowerMah += sipper.totalPowerMah;
                 }
             }
-
-            if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
-                screenSipper = sipper;
-            }
         }
-
-        smearScreenBatterySipper(sippers, screenSipper);
-
         return proportionalSmearPowerMah;
     }
 
     /**
-     * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
-     * time.
-     */
-    public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
-        long totalActivityTimeMs = 0;
-        final SparseLongArray activityTimeArray = new SparseLongArray();
-        for (int i = 0, size = sippers.size(); i < size; i++) {
-            final BatteryStats.Uid uid = sippers.get(i).uidObj;
-            if (uid != null) {
-                final long timeMs = getProcessForegroundTimeMs(uid,
-                        BatteryStats.STATS_SINCE_CHARGED);
-                activityTimeArray.put(uid.getUid(), timeMs);
-                totalActivityTimeMs += timeMs;
-            }
-        }
-
-        if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
-            final double screenPowerMah = screenSipper.totalPowerMah;
-            for (int i = 0, size = sippers.size(); i < size; i++) {
-                final BatterySipper sipper = sippers.get(i);
-                sipper.screenPowerMah = screenPowerMah * activityTimeArray.get(sipper.getUid(), 0)
-                        / totalActivityTimeMs;
-            }
-        }
-    }
-
-    /**
      * Check whether we should hide the battery sipper.
      */
     public boolean shouldHideSipper(BatterySipper sipper) {
@@ -683,33 +645,6 @@
     }
 
     @VisibleForTesting
-    public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
-        final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
-        if (timer != null) {
-            return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
-        }
-
-        return 0;
-    }
-
-    @VisibleForTesting
-    public long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) {
-        final long rawRealTimeUs = convertMsToUs(SystemClock.elapsedRealtime());
-        final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP};
-
-        long timeUs = 0;
-        for (int type : foregroundTypes) {
-            final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which);
-            timeUs += localTime;
-        }
-
-        // Return the min value of STATE_TOP time and foreground activity time, since both of these
-        // time have some errors.
-        return convertUsToMs(
-                Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)));
-    }
-
-    @VisibleForTesting
     public void setPackageManager(PackageManager packageManager) {
         mPackageManager = packageManager;
     }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 37b621d..1f7a7aa 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,11 +16,11 @@
 
 package com.android.internal.os;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
 import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
 
-import static com.android.internal.power.MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -36,7 +36,6 @@
 import android.database.ContentObserver;
 import android.hardware.usb.UsbManager;
 import android.location.GnssSignalQuality;
-import android.net.ConnectivityManager;
 import android.net.INetworkStatsService;
 import android.net.NetworkStats;
 import android.net.Uri;
@@ -106,11 +105,12 @@
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
 import com.android.internal.power.MeasuredEnergyStats;
-import com.android.internal.power.MeasuredEnergyStats.EnergyBucket;
+import com.android.internal.power.MeasuredEnergyStats.StandardEnergyBucket;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.XmlUtils;
+import com.android.net.module.util.NetworkCapabilitiesUtils;
 
 import libcore.util.EmptyArray;
 
@@ -171,7 +171,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 191 + (USE_OLD_HISTORY ? 1000 : 0);
+    static final int VERSION = 193 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -360,7 +360,6 @@
 
     public interface PlatformIdleStateCallback {
         public void fillLowPowerStats(RpmStats rpmStats);
-        public String getPlatformLowPowerStats();
         public String getSubsystemLowPowerStats();
     }
 
@@ -1097,16 +1096,6 @@
     private long[] mCpuFreqs;
 
     /**
-     * Times spent by the system server process grouped by cluster and CPU speed.
-     */
-    private LongSamplingCounterArray mSystemServerCpuTimesUs;
-
-    /**
-     * Times spent by the system server threads grouped by cluster and CPU speed.
-     */
-    private LongSamplingCounterArray mSystemServerThreadCpuTimesUs;
-
-    /**
      * Times spent by the system server threads handling incoming binder requests.
      */
     private LongSamplingCounterArray mBinderThreadCpuTimesUs;
@@ -3490,11 +3479,6 @@
         }
         if (computeStepDetails) {
             if (mPlatformIdleStateCallback != null) {
-                mCurHistoryStepDetails.statPlatformIdleState =
-                        mPlatformIdleStateCallback.getPlatformLowPowerStats();
-                if (DEBUG) Slog.i(TAG, "WRITE PlatformIdleState:" +
-                        mCurHistoryStepDetails.statPlatformIdleState);
-
                 mCurHistoryStepDetails.statSubsystemPowerState =
                         mPlatformIdleStateCallback.getSubsystemLowPowerStats();
                 if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
@@ -6711,11 +6695,12 @@
     }
 
     /** @hide */
-    public void noteNetworkInterfaceType(String iface, int networkType) {
+    public void noteNetworkInterfaceForTransports(String iface, int[] transportTypes) {
         if (TextUtils.isEmpty(iface)) return;
+        final int displayTransport = NetworkCapabilitiesUtils.getDisplayTransport(transportTypes);
 
         synchronized (mModemNetworkLock) {
-            if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
+            if (displayTransport == TRANSPORT_CELLULAR) {
                 mModemIfaces = includeInStringArray(mModemIfaces, iface);
                 if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mModemIfaces);
             } else {
@@ -6725,7 +6710,7 @@
         }
 
         synchronized (mWifiNetworkLock) {
-            if (ConnectivityManager.isNetworkTypeWifi(networkType)) {
+            if (displayTransport == TRANSPORT_WIFI) {
                 mWifiIfaces = includeInStringArray(mWifiIfaces, iface);
                 if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces);
             } else {
@@ -7174,8 +7159,8 @@
         if (mGlobalMeasuredEnergyStats == null) {
             return ENERGY_DATA_UNAVAILABLE;
         }
-        return mGlobalMeasuredEnergyStats.getAccumulatedBucketEnergy(
-                MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+        return mGlobalMeasuredEnergyStats
+                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
     }
 
     @Override
@@ -7183,8 +7168,8 @@
         if (mGlobalMeasuredEnergyStats == null) {
             return ENERGY_DATA_UNAVAILABLE;
         }
-        return mGlobalMeasuredEnergyStats.getAccumulatedBucketEnergy(
-                MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
+        return mGlobalMeasuredEnergyStats
+                .getAccumulatedStandardBucketEnergy(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE);
     }
 
     @Override public long getStartClockTime() {
@@ -7948,27 +7933,27 @@
             return mUidMeasuredEnergyStats;
         }
 
-        /** Adds the given energy to the given energy bucket for this uid. */
-        private void addEnergyToEnergyBucketLocked(long energyDeltaUJ,
-                @MeasuredEnergyStats.EnergyBucket int energyBucket, boolean accumulate) {
+        /** Adds the given energy to the given standard energy bucket for this uid. */
+        private void addEnergyToStandardBucketLocked(long energyDeltaUJ,
+                @StandardEnergyBucket int energyBucket, boolean accumulate) {
             getOrCreateMeasuredEnergyStatsLocked()
-                    .updateBucket(energyBucket, energyDeltaUJ, accumulate);
+                    .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate);
         }
 
         /**
-         * Returns the energy used by this uid for an energy bucket of interest.
-         * @param bucket energy bucket of interest
+         * Returns the energy used by this uid for a standard energy bucket of interest.
+         * @param bucket standard energy bucket of interest
          * @return energy (in microjoules) used by this uid for this energy bucket
          */
-        public long getMeasuredEnergyMicroJoules(@MeasuredEnergyStats.EnergyBucket int bucket) {
+        public long getMeasuredEnergyMicroJoules(@StandardEnergyBucket int bucket) {
             if (mBsi.mGlobalMeasuredEnergyStats == null
-                    || !mBsi.mGlobalMeasuredEnergyStats.isEnergyBucketSupported(bucket)) {
+                    || !mBsi.mGlobalMeasuredEnergyStats.isStandardBucketSupported(bucket)) {
                 return ENERGY_DATA_UNAVAILABLE;
             }
             if (mUidMeasuredEnergyStats == null) {
                 return 0L; // It is supported, but was never filled, so it must be 0
             }
-            return mUidMeasuredEnergyStats.getAccumulatedBucketEnergy(bucket);
+            return mUidMeasuredEnergyStats.getAccumulatedStandardBucketEnergy(bucket);
         }
 
         /**
@@ -8640,11 +8625,7 @@
 
         @Override
         public long getScreenOnEnergy() {
-            if (mUidMeasuredEnergyStats == null) {
-                return ENERGY_DATA_UNAVAILABLE;
-            }
-            return mUidMeasuredEnergyStats.getAccumulatedBucketEnergy(
-                    MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
+            return getMeasuredEnergyMicroJoules(MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON);
         }
 
         void initNetworkActivityLocked() {
@@ -10853,6 +10834,14 @@
         }
     }
 
+    /**
+     * Starts tracking CPU time-in-state for threads of the system server process,
+     * keeping a separate account of threads receiving incoming binder calls.
+     */
+    public void startTrackingSystemServerCpuTime() {
+        mSystemServerCpuThreadReader.startTrackingThreadCpuTime();
+    }
+
     public void setCallback(BatteryCallback cb) {
         mCallback = cb;
     }
@@ -11505,8 +11494,6 @@
 
         MeasuredEnergyStats.resetIfNotNull(mGlobalMeasuredEnergyStats);
 
-        resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs);
-        resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs);
         resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
 
         mLastHistoryStepDetails = null;
@@ -12407,7 +12394,7 @@
             return;
         }
 
-        final @EnergyBucket int energyBucket =
+        final @StandardEnergyBucket int energyBucket =
                 MeasuredEnergyStats.getDisplayEnergyBucket(mScreenStateAtLastEnergyMeasurement);
         mScreenStateAtLastEnergyMeasurement = screenState;
 
@@ -12426,7 +12413,7 @@
             return;
         }
 
-        mGlobalMeasuredEnergyStats.updateBucket(energyBucket, energyUJ, true);
+        mGlobalMeasuredEnergyStats.updateStandardBucket(energyBucket, energyUJ, true);
 
         // Now we blame individual apps, but only if the display was ON.
         if (energyBucket != MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON) {
@@ -12464,7 +12451,7 @@
             final long appDisplayEnergyMJ =
                     (totalDisplayEnergyMJ * fgTimeMs + (totalFgTimeMs / 2))
                     / totalFgTimeMs;
-            uid.addEnergyToEnergyBucketLocked(appDisplayEnergyMJ * 1000, energyBucket, true);
+            uid.addEnergyToStandardBucketLocked(appDisplayEnergyMJ * 1000, energyBucket, true);
 
             // To mitigate round-off errors, remove this app from numerator & denominator totals
             totalDisplayEnergyMJ -= appDisplayEnergyMJ;
@@ -12692,27 +12679,17 @@
             return;
         }
 
-        if (mSystemServerCpuTimesUs == null) {
-            mSystemServerCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
-            mSystemServerThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+        if (mBinderThreadCpuTimesUs == null) {
             mBinderThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
         }
-        mSystemServerCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.processCpuTimesUs);
-        mSystemServerThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.threadCpuTimesUs);
         mBinderThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
 
         if (DEBUG_BINDER_STATS) {
-            Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
-            long totalCpuTimeMs = 0;
-            long totalThreadTimeMs = 0;
+            Slog.d(TAG, "System server threads per CPU cluster (incoming binder threads)");
             long binderThreadTimeMs = 0;
             int cpuIndex = 0;
-            final long[] systemServerCpuTimesUs =
-                    mSystemServerCpuTimesUs.getCountsLocked(0);
-            final long[] systemServerThreadCpuTimesUs =
-                    mSystemServerThreadCpuTimesUs.getCountsLocked(0);
-            final long[] binderThreadCpuTimesUs =
-                    mBinderThreadCpuTimesUs.getCountsLocked(0);
+            final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
+                    BatteryStats.STATS_SINCE_CHARGED);
             int index = 0;
             int numCpuClusters = mPowerProfile.getNumCpuClusters();
             for (int cluster = 0; cluster < numCpuClusters; cluster++) {
@@ -12723,28 +12700,15 @@
                     if (speed != 0) {
                         sb.append(", ");
                     }
-                    long totalCountMs = systemServerThreadCpuTimesUs[index] / 1000;
                     long binderCountMs = binderThreadCpuTimesUs[index] / 1000;
-                    sb.append(String.format("%d/%d(%.1f%%)",
-                            binderCountMs,
-                            totalCountMs,
-                            totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0));
+                    sb.append(TextUtils.formatSimple("%10d", binderCountMs));
 
-                    totalCpuTimeMs += systemServerCpuTimesUs[index] / 1000;
-                    totalThreadTimeMs += totalCountMs;
                     binderThreadTimeMs += binderCountMs;
                     index++;
                 }
                 cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
                 Slog.d(TAG, sb.toString());
             }
-
-            Slog.d(TAG, "Total system server CPU time (ms): " + totalCpuTimeMs);
-            Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTimeMs);
-            Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)",
-                    binderThreadTimeMs,
-                    binderThreadTimeMs != 0
-                            ? (double) binderThreadTimeMs * 100 / totalThreadTimeMs : 0));
         }
     }
 
@@ -14019,60 +13983,16 @@
     }
 
 
+    /**
+     * Estimates the time spent by the system server handling incoming binder requests.
+     */
     @Override
     public long[] getSystemServiceTimeAtCpuSpeeds() {
-        // Estimates the time spent by the system server handling incoming binder requests.
-        //
-        // The data that we can get from the kernel is this:
-        //   - CPU duration for a (thread - cluster - CPU speed) combination
-        //   - CPU duration for a (UID - cluster - CPU speed) combination
-        //
-        // The configuration we have in the Power Profile is this:
-        //   - Average CPU power for a (cluster - CPU speed) combination.
-        //
-        // The model used by BatteryStats can be illustrated with this example:
-        //
-        // - Let's say the system server has 10 threads.
-        // - These 10 threads spent 1000 ms of CPU time in aggregate
-        // - Of the 10 threads 4 were execute exclusively incoming binder calls.
-        // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
-        // - The real time spent by the system server process doing all of this is, say, 200 ms.
-        //
-        // We will assume that power consumption is proportional to the time spent by the CPU
-        // across all threads.  This is a crude assumption, but we don't have more detailed data.
-        // Thus,
-        //   binderRealTime = realTime * aggregateBinderThreadTime / aggregateAllThreadTime
-        //
-        // In our example,
-        //   binderRealTime = 200 * 600 / 1000 = 120ms
-        //
-        // We can then multiply this estimated time by the average power to obtain an estimate
-        // of the total power consumed by incoming binder calls for the given cluster/speed
-        // combination.
-
-        if (mSystemServerCpuTimesUs == null) {
+        if (mBinderThreadCpuTimesUs == null) {
             return null;
         }
 
-        final long[] systemServerCpuTimesUs = mSystemServerCpuTimesUs.getCountsLocked(
-                BatteryStats.STATS_SINCE_CHARGED);
-        final long [] systemServerThreadCpuTimesUs = mSystemServerThreadCpuTimesUs.getCountsLocked(
-                BatteryStats.STATS_SINCE_CHARGED);
-        final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
-                BatteryStats.STATS_SINCE_CHARGED);
-
-        final int size = systemServerCpuTimesUs.length;
-        final long[] results = new long[size];
-
-        for (int i = 0; i < size; i++) {
-            if (systemServerThreadCpuTimesUs[i] == 0) {
-                continue;
-            }
-
-            results[i] = systemServerCpuTimesUs[i] * binderThreadCpuTimesUs[i]
-                    / systemServerThreadCpuTimesUs[i];
-        }
-        return results;
+        return mBinderThreadCpuTimesUs.getCountsLocked(BatteryStats.STATS_SINCE_CHARGED);
     }
 
     /**
@@ -14216,33 +14136,34 @@
     /**
      * Initialize the measured energy stats data structures.
      *
-     * @param supportedEnergyBuckets boolean array indicating which buckets are currently supported
+     * @param supportedStandardBuckets boolean array indicating which {@link StandardEnergyBucket}s
+     *                                 are currently supported.
+     *                                 If null, none are supported (regardless of numCustomBuckets).
+     * @param numCustomBuckets number of custom (OTHER) EnergyConsumers on this device
      */
     @GuardedBy("this")
-    public void initMeasuredEnergyStatsLocked(boolean[] supportedEnergyBuckets) {
+    public void initMeasuredEnergyStatsLocked(@Nullable boolean[] supportedStandardBuckets,
+            int numCustomBuckets) {
         boolean supportedBucketMismatch = false;
         mScreenStateAtLastEnergyMeasurement = mScreenState;
 
-        if (supportedEnergyBuckets == null) {
+        if (supportedStandardBuckets == null) {
             if (mGlobalMeasuredEnergyStats != null) {
                 // Measured energy buckets no longer supported, wipe out the existing data.
                 supportedBucketMismatch = true;
             }
         } else if (mGlobalMeasuredEnergyStats == null) {
-            mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedEnergyBuckets);
+            mGlobalMeasuredEnergyStats
+                    = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
             return;
         } else {
-            for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
-                if (mGlobalMeasuredEnergyStats.isEnergyBucketSupported(i)
-                        != supportedEnergyBuckets[i]) {
-                    mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedEnergyBuckets);
-                    supportedBucketMismatch = true;
-                    break;
-                }
-            }
+            supportedBucketMismatch = !mGlobalMeasuredEnergyStats.isSupportEqualTo(
+                    supportedStandardBuckets, numCustomBuckets);
         }
 
         if (supportedBucketMismatch) {
+            mGlobalMeasuredEnergyStats = supportedStandardBuckets == null ?
+                    null : new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
             // Supported energy buckets changed since last boot.
             // Existing data is no longer reliable.
             resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime());
@@ -14503,7 +14424,7 @@
         }
 
         updateSystemServiceCallStats();
-        if (mSystemServerThreadCpuTimesUs != null) {
+        if (mBinderThreadCpuTimesUs != null) {
             pw.println("Per UID System server binder time in ms:");
             long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds();
             for (int i = 0; i < size; i++) {
@@ -16094,9 +16015,6 @@
             mUidStats.append(uid, u);
         }
 
-        mSystemServerCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
-        mSystemServerThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in,
-                mOnBatteryTimeBase);
         mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
     }
 
@@ -16305,8 +16223,6 @@
         } else {
             out.writeInt(0);
         }
-        LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs);
-        LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs);
         LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs);
     }
 
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index b8c066d..e76e34f 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.hardware.SensorManager;
-import android.net.ConnectivityManager;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
@@ -57,7 +56,7 @@
                 mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
-                if (!isWifiOnlyDevice(mContext)) {
+                if (!BatteryStatsHelper.checkWifiOnly(mContext)) {
                     mPowerCalculators.add(new MobileRadioPowerCalculator(mPowerProfile));
                 }
                 mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile));
@@ -81,18 +80,10 @@
         return mPowerCalculators;
     }
 
-    private static boolean isWifiOnlyDevice(Context context) {
-        ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
-        if (cm == null) {
-            return false;
-        }
-        return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
-    }
-
     /**
      * Returns a snapshot of battery attribution data.
      */
-    public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
+    public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
 
         // TODO(b/174186345): instead of BatteryStatsHelper, use PowerCalculators directly.
         final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext,
@@ -109,28 +100,27 @@
 
         batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users);
 
+        ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size());
+        for (int i = 0; i < queries.size(); i++) {
+            results.add(getBatteryUsageStats(queries.get(i), batteryStatsHelper, users));
+        }
+        return results;
+    }
+
+    private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query,
+            BatteryStatsHelper batteryStatsHelper, SparseArray<UserHandle> users) {
         // TODO(b/174186358): read extra power component number from configuration
         final int customPowerComponentCount = 0;
         final int customTimeComponentCount = 0;
-        final boolean includeModeledComponents =
-                (query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED)
-                        != 0;
-
 
         final BatteryUsageStats.Builder batteryUsageStatsBuilder =
-                new BatteryUsageStats.Builder(customPowerComponentCount, customTimeComponentCount,
-                        includeModeledComponents)
+                new BatteryUsageStats.Builder(customPowerComponentCount, customTimeComponentCount)
                         .setDischargePercentage(batteryStatsHelper.getStats().getDischargeAmount(0))
                         .setConsumedPower(batteryStatsHelper.getTotalPower());
 
-        final List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
-        for (int i = 0; i < usageList.size(); i++) {
-            final BatterySipper sipper = usageList.get(i);
-            if (sipper.drainType == BatterySipper.DrainType.APP) {
-                batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(sipper.uidObj)
-                        .setPackageWithHighestDrain(sipper.packageWithHighestDrain)
-                        .setConsumedPower(sipper.sumPower());
-            }
+        SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats();
+        for (int i = uidStats.size() - 1; i >= 0; i--) {
+            batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
         }
 
         final long realtimeUs = SystemClock.elapsedRealtime() * 1000;
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 7972924..11c8761 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -122,11 +122,5 @@
                 .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, cpuTimeMs)
                 .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, cpuFgTimeMs)
                 .setPackageWithHighestDrain(packageWithHighestDrain);
-
-        if ((query.getFlags()
-                & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_MODELED) != 0) {
-            app.setConsumedPowerForCustomComponent(BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                    + BatteryConsumer.POWER_COMPONENT_CPU, cpuPowerMah);
-        }
     }
 }
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index e6a9623..4d2a08a 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -16,23 +16,12 @@
 
 package com.android.internal.os;
 
-import static android.os.Process.PROC_OUT_LONG;
-import static android.os.Process.PROC_SPACE_TERM;
-
 import android.annotation.Nullable;
-import android.os.Process;
-import android.system.Os;
-import android.system.OsConstants;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
-import java.nio.file.DirectoryIteratorException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Arrays;
 
 /**
@@ -45,93 +34,65 @@
     private static final String TAG = "KernelSingleProcCpuThreadRdr";
 
     private static final boolean DEBUG = false;
-    private static final boolean NATIVE_ENABLED = true;
-
-    /**
-     * The name of the file to read CPU statistics from, must be found in {@code
-     * /proc/$PID/task/$TID}
-     */
-    private static final String CPU_STATISTICS_FILENAME = "time_in_state";
-
-    private static final String PROC_STAT_FILENAME = "stat";
-
-    /** Directory under /proc/$PID containing CPU stats files for threads */
-    public static final String THREAD_CPU_STATS_DIRECTORY = "task";
-
-    /** Default mount location of the {@code proc} filesystem */
-    private static final Path DEFAULT_PROC_PATH = Paths.get("/proc");
-
-    /** The initial {@code time_in_state} file for {@link ProcTimeInStateReader} */
-    private static final Path INITIAL_TIME_IN_STATE_PATH = Paths.get("self/time_in_state");
-
-    /** See https://man7.org/linux/man-pages/man5/proc.5.html */
-    private static final int[] PROCESS_FULL_STATS_FORMAT = new int[] {
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM,
-            PROC_SPACE_TERM | PROC_OUT_LONG,                  // 14: utime
-            PROC_SPACE_TERM | PROC_OUT_LONG,                  // 15: stime
-            // Ignore remaining fields
-    };
-
-    private final long[] mProcessFullStatsData = new long[2];
-
-    private static final int PROCESS_FULL_STAT_UTIME = 0;
-    private static final int PROCESS_FULL_STAT_STIME = 1;
-
-    /** Used to read and parse {@code time_in_state} files */
-    private final ProcTimeInStateReader mProcTimeInStateReader;
 
     private final int mPid;
 
-    /** Where the proc filesystem is mounted */
-    private final Path mProcPath;
+    private final CpuTimeInStateReader mCpuTimeInStateReader;
 
-    // How long a CPU jiffy is in milliseconds.
-    private final long mJiffyMillis;
-
-    // Path: /proc/<pid>/stat
-    private final String mProcessStatFilePath;
-
-    // Path: /proc/<pid>/task
-    private final Path mThreadsDirectoryPath;
+    private int[] mSelectedThreadNativeTids = new int[0];  // Sorted
 
     /**
-     * Count of frequencies read from the {@code time_in_state} file. Read from {@link
-     * #mProcTimeInStateReader#getCpuFrequenciesKhz()}.
+     * Count of frequencies read from the {@code time_in_state} file.
      */
     private int mFrequencyCount;
 
+    private boolean mIsTracking;
+
+    /**
+     * A CPU time-in-state provider for testing.  Imitates the behavior of the corresponding
+     * methods in frameworks/native/libs/cputimeinstate/cputimeinstate.c
+     */
+    @VisibleForTesting
+    public interface CpuTimeInStateReader {
+        /**
+         * Returns the overall number of cluster-frequency combinations.
+         */
+        int getCpuFrequencyCount();
+
+        /**
+         * Returns true to indicate success.
+         *
+         * Called from native.
+         */
+        boolean startTrackingProcessCpuTimes(int tgid);
+
+        /**
+         * Returns true to indicate success.
+         *
+         * Called from native.
+         */
+        boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey);
+
+        /**
+         * Must return an array of strings formatted like this:
+         * "aggKey:t0_0 t0_1...:t1_0 t1_1..."
+         * Times should be provided in nanoseconds.
+         *
+         * Called from native.
+         */
+        String[] getAggregatedTaskCpuFreqTimes(int pid);
+    }
+
     /**
      * Create with a path where `proc` is mounted. Used primarily for testing
      *
      * @param pid      PID of the process whose threads are to be read.
-     * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
      */
     @VisibleForTesting
-    public KernelSingleProcessCpuThreadReader(
-            int pid,
-            Path procPath) throws IOException {
+    public KernelSingleProcessCpuThreadReader(int pid,
+            @Nullable CpuTimeInStateReader cpuTimeInStateReader) throws IOException {
         mPid = pid;
-        mProcPath = procPath;
-        mProcTimeInStateReader = new ProcTimeInStateReader(
-                mProcPath.resolve(INITIAL_TIME_IN_STATE_PATH));
-        long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
-        mJiffyMillis = 1000 / jiffyHz;
-        mProcessStatFilePath =
-                mProcPath.resolve(String.valueOf(mPid)).resolve(PROC_STAT_FILENAME).toString();
-        mThreadsDirectoryPath =
-                mProcPath.resolve(String.valueOf(mPid)).resolve(THREAD_CPU_STATS_DIRECTORY);
+        mCpuTimeInStateReader = cpuTimeInStateReader;
     }
 
     /**
@@ -142,7 +103,7 @@
     @Nullable
     public static KernelSingleProcessCpuThreadReader create(int pid) {
         try {
-            return new KernelSingleProcessCpuThreadReader(pid, DEFAULT_PROC_PATH);
+            return new KernelSingleProcessCpuThreadReader(pid, null);
         } catch (IOException e) {
             Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e);
             return null;
@@ -150,146 +111,98 @@
     }
 
     /**
-     * Get the CPU frequencies that correspond to the times reported in {@link
-     * ProcessCpuUsage#processCpuTimesMillis} etc.
+     * Starts tracking aggregated CPU time-in-state of all threads of the process with the PID
+     * supplied in the constructor.
+     */
+    public void startTrackingThreadCpuTimes() {
+        if (!mIsTracking) {
+            if (!startTrackingProcessCpuTimes(mPid, mCpuTimeInStateReader)) {
+                Slog.e(TAG, "Failed to start tracking process CPU times for " + mPid);
+            }
+            if (mSelectedThreadNativeTids.length > 0) {
+                if (!startAggregatingThreadCpuTimes(mSelectedThreadNativeTids,
+                        mCpuTimeInStateReader)) {
+                    Slog.e(TAG, "Failed to start tracking aggregated thread CPU times for "
+                            + Arrays.toString(mSelectedThreadNativeTids));
+                }
+            }
+            mIsTracking = true;
+        }
+    }
+
+    /**
+     * @param nativeTids an array of native Thread IDs whose CPU times should
+     *                   be aggregated as a group.  This is expected to be a subset
+     *                   of all thread IDs owned by the process.
+     */
+    public void setSelectedThreadIds(int[] nativeTids) {
+        mSelectedThreadNativeTids = nativeTids.clone();
+        if (mIsTracking) {
+            startAggregatingThreadCpuTimes(mSelectedThreadNativeTids, mCpuTimeInStateReader);
+        }
+    }
+
+    /**
+     * Get the CPU frequencies that correspond to the times reported in {@link ProcessCpuUsage}.
      */
     public int getCpuFrequencyCount() {
         if (mFrequencyCount == 0) {
-            mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length;
+            mFrequencyCount = getCpuFrequencyCount(mCpuTimeInStateReader);
         }
         return mFrequencyCount;
     }
 
     /**
-     * Get the total and per-thread CPU usage of the process with the PID specified in the
-     * constructor.
-     *
-     * @param selectedThreadIds a SORTED array of native Thread IDs whose CPU times should
-     *                          be aggregated as a group.  This is expected to be a subset
-     *                          of all thread IDs owned by the process.
+     * Get the total CPU usage of the process with the PID specified in the
+     * constructor. The CPU usage time is aggregated across all threads and may
+     * exceed the time the entire process has been running.
      */
     @Nullable
-    public ProcessCpuUsage getProcessCpuUsage(int[] selectedThreadIds) {
+    public ProcessCpuUsage getProcessCpuUsage() {
         if (DEBUG) {
-            Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID "
-                    + mPid);
+            Slog.d(TAG, "Reading CPU thread usages for PID " + mPid);
         }
 
-        int cpuFrequencyCount = getCpuFrequencyCount();
-        ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);
+        ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount());
 
-        if (NATIVE_ENABLED) {
-            boolean result = readProcessCpuUsage(mProcPath.toString(), mPid,
-                    selectedThreadIds, processCpuUsage.processCpuTimesMillis,
-                    processCpuUsage.threadCpuTimesMillis,
-                    processCpuUsage.selectedThreadCpuTimesMillis);
-            if (!result) {
-                return null;
-            }
-            return processCpuUsage;
-        }
-
-        if (!isSorted(selectedThreadIds)) {
-            throw new IllegalArgumentException("selectedThreadIds is not sorted: "
-                    + Arrays.toString(selectedThreadIds));
-        }
-
-        if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null,
-                mProcessFullStatsData, null)) {
-            Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath);
+        boolean result = readProcessCpuUsage(mPid,
+                processCpuUsage.threadCpuTimesMillis,
+                processCpuUsage.selectedThreadCpuTimesMillis,
+                mCpuTimeInStateReader);
+        if (!result) {
             return null;
         }
 
-        long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME];
-        long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME];
-
-        long processCpuTimeMillis = (utime + stime) * mJiffyMillis;
-
-        try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
-            for (Path threadDirectory : threadPaths) {
-                readThreadCpuUsage(processCpuUsage, selectedThreadIds, threadDirectory);
-            }
-        } catch (IOException | DirectoryIteratorException e) {
-            // Expected when a process finishes
-            return null;
-        }
-
-        // Estimate per cluster per frequency CPU time for the entire process
-        // by distributing the total process CPU time proportionately to how much
-        // CPU time its threads took on those clusters/frequencies.  This algorithm
-        // works more accurately when when we have equally distributed concurrency.
-        // TODO(b/169279846): obtain actual process CPU times from the kernel
-        long totalCpuTimeAllThreads = 0;
-        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
-            totalCpuTimeAllThreads += processCpuUsage.threadCpuTimesMillis[i];
-        }
-
-        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
-            processCpuUsage.processCpuTimesMillis[i] =
-                    processCpuTimeMillis * processCpuUsage.threadCpuTimesMillis[i]
-                            / totalCpuTimeAllThreads;
+        if (DEBUG) {
+            Slog.d(TAG, "threadCpuTimesMillis = "
+                    + Arrays.toString(processCpuUsage.threadCpuTimesMillis));
+            Slog.d(TAG, "selectedThreadCpuTimesMillis = "
+                    + Arrays.toString(processCpuUsage.selectedThreadCpuTimesMillis));
         }
 
         return processCpuUsage;
     }
 
-    /**
-     * Reads a thread's CPU usage and aggregates the per-cluster per-frequency CPU times.
-     *
-     * @param threadDirectory the {@code /proc} directory of the thread
-     */
-    private void readThreadCpuUsage(ProcessCpuUsage processCpuUsage, int[] selectedThreadIds,
-            Path threadDirectory) {
-        // Get the thread ID from the directory name
-        final int threadId;
-        try {
-            final String directoryName = threadDirectory.getFileName().toString();
-            threadId = Integer.parseInt(directoryName);
-        } catch (NumberFormatException e) {
-            Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e);
-            return;
-        }
-
-        // Get the CPU statistics from the directory
-        final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME);
-        final long[] cpuUsages = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath);
-        if (cpuUsages == null) {
-            return;
-        }
-
-        final int cpuFrequencyCount = getCpuFrequencyCount();
-        final boolean isSelectedThread = Arrays.binarySearch(selectedThreadIds, threadId) >= 0;
-        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
-            processCpuUsage.threadCpuTimesMillis[i] += cpuUsages[i];
-            if (isSelectedThread) {
-                processCpuUsage.selectedThreadCpuTimesMillis[i] += cpuUsages[i];
-            }
-        }
-    }
-
     /** CPU usage of a process, all of its threads and a selected subset of its threads */
     public static class ProcessCpuUsage {
-        public long[] processCpuTimesMillis;
         public long[] threadCpuTimesMillis;
         public long[] selectedThreadCpuTimesMillis;
 
         public ProcessCpuUsage(int cpuFrequencyCount) {
-            processCpuTimesMillis = new long[cpuFrequencyCount];
             threadCpuTimesMillis = new long[cpuFrequencyCount];
             selectedThreadCpuTimesMillis = new long[cpuFrequencyCount];
         }
     }
 
-    private static boolean isSorted(int[] array) {
-        for (int i = 0; i < array.length - 1; i++) {
-            if (array[i] > array[i + 1]) {
-                return false;
-            }
-        }
-        return true;
-    }
+    private native int getCpuFrequencyCount(CpuTimeInStateReader reader);
 
-    private native boolean readProcessCpuUsage(String procPath, int pid, int[] selectedThreadIds,
-            long[] processCpuTimesMillis, long[] threadCpuTimesMillis,
-            long[] selectedThreadCpuTimesMillis);
+    private native boolean startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader);
+
+    private native boolean startAggregatingThreadCpuTimes(int[] selectedThreadIds,
+            CpuTimeInStateReader reader);
+
+    private native boolean readProcessCpuUsage(int pid,
+            long[] threadCpuTimesMillis,
+            long[] selectedThreadCpuTimesMillis,
+            CpuTimeInStateReader reader);
 }
diff --git a/core/java/com/android/internal/os/PowerCalculator.java b/core/java/com/android/internal/os/PowerCalculator.java
index 974894f..05fcc70 100644
--- a/core/java/com/android/internal/os/PowerCalculator.java
+++ b/core/java/com/android/internal/os/PowerCalculator.java
@@ -102,7 +102,7 @@
 
         // TODO(b/175156498): Temporary code during the transition from BatterySippers to
         //  BatteryConsumers.
-        UidBatteryConsumer.Builder builder = new UidBatteryConsumer.Builder(0, 0, false, u);
+        UidBatteryConsumer.Builder builder = new UidBatteryConsumer.Builder(0, 0, u);
         calculateApp(builder, u, rawRealtimeUs, rawUptimeUs, BatteryUsageStatsQuery.DEFAULT);
         final UidBatteryConsumer uidBatteryConsumer = builder.build();
         app.cpuPowerMah = uidBatteryConsumer.getConsumedPower(
@@ -162,4 +162,10 @@
         // Use English locale because this is never used in UI (only in checkin and dump).
         return String.format(Locale.ENGLISH, format, power);
     }
+
+    static double mAhToUJ(long energyUJ) {
+        // TODO(b/173765509): Convert properly. This is mJ / V * (h/3600s) = mAh with V = 3.7 fixed.
+        //                    Leaving for later since desired units of energy have yet to be decided
+        return energyUJ / 1000.0 / 3.7  / 3600;
+    }
 }
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 25f6b4d..c86c795 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -21,9 +21,14 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.SystemBatteryConsumer;
+import android.os.SystemClock;
 import android.os.UserHandle;
-import android.util.Log;
+import android.text.format.DateUtils;
+import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseLongArray;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.List;
 
@@ -57,6 +62,8 @@
                     .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
         }
+        // TODO(b/178140704): Attribute *measured* total usage for BatteryUsageStats.
+        // TODO(b/178140704): Attribute (measured/smeared) usage *per app* for BatteryUsageStats.
     }
 
     /**
@@ -65,15 +72,43 @@
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
+
+        final long energyUJ = batteryStats.getScreenOnEnergy();
+        final boolean isMeasuredDataAvailable = energyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE;
+
         final long durationMs = computeDuration(batteryStats, rawRealtimeUs, statsType);
-        final double powerMah = computePower(batteryStats, rawRealtimeUs, statsType, durationMs);
-        if (powerMah != 0) {
-            final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
-            bs.usagePowerMah = powerMah;
-            bs.usageTimeMs = durationMs;
-            bs.sumPower();
-            sippers.add(bs);
+        final double powerMah = getMeasuredOrComputedPower(
+                energyUJ, batteryStats, rawRealtimeUs, statsType, durationMs);
+        if (powerMah == 0) {
+            return;
         }
+
+        // First deal with the SCREEN BatterySipper (since we need this for smearing over apps).
+        final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.SCREEN, null, 0);
+        bs.usagePowerMah = powerMah;
+        bs.usageTimeMs = durationMs;
+        bs.sumPower();
+        sippers.add(bs);
+
+        // Now deal with each app's BatterySipper. The results are stored in the screenPowerMah
+        // field, which is considered smeared, but the method depends on the data source.
+        if (isMeasuredDataAvailable) {
+            super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers);
+        } else {
+            smearScreenBatterySipper(sippers, bs);
+        }
+    }
+
+    @Override
+    protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
+            long rawUptimeUs, int statsType) {
+        final long energyUJ = u.getScreenOnEnergy();
+        if (energyUJ < 0) {
+            Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
+            return;
+        }
+        if (energyUJ == 0) return;
+        app.screenPowerMah = mAhToUJ(u.getScreenOnEnergy());
     }
 
     private long computeDuration(BatteryStats batteryStats, long rawRealtimeUs, int statsType) {
@@ -89,11 +124,78 @@
             final double binPowerMah = mScreenFullPowerEstimator.calculatePower(brightnessTime)
                     * (i + 0.5f) / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
             if (DEBUG && binPowerMah != 0) {
-                Log.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
+                Slog.d(TAG, "Screen bin #" + i + ": time=" + brightnessTime
                         + " power=" + formatCharge(binPowerMah));
             }
             power += binPowerMah;
         }
         return power;
     }
+
+    private double getMeasuredOrComputedPower(long measuredEnergyUJ,
+            BatteryStats batteryStats, long rawRealtimeUs, int statsType, long durationMs) {
+
+        if (measuredEnergyUJ != BatteryStats.ENERGY_DATA_UNAVAILABLE) {
+            return mAhToUJ(measuredEnergyUJ);
+        } else {
+            return computePower(batteryStats, rawRealtimeUs, statsType, durationMs);
+        }
+    }
+
+    /**
+     * Smear the screen on power usage among {@code sippers}, based on ratio of foreground activity
+     * time, and store this in the {@link BatterySipper#screenPowerMah} field.
+     */
+    @VisibleForTesting
+    public void smearScreenBatterySipper(List<BatterySipper> sippers, BatterySipper screenSipper) {
+
+        long totalActivityTimeMs = 0;
+        final SparseLongArray activityTimeArray = new SparseLongArray();
+        for (int i = sippers.size() - 1; i >= 0; i--) {
+            final BatteryStats.Uid uid = sippers.get(i).uidObj;
+            if (uid != null) {
+                final long timeMs = getProcessForegroundTimeMs(uid);
+                activityTimeArray.put(uid.getUid(), timeMs);
+                totalActivityTimeMs += timeMs;
+            }
+        }
+
+        if (screenSipper != null && totalActivityTimeMs >= 10 * DateUtils.MINUTE_IN_MILLIS) {
+            final double totalScreenPowerMah = screenSipper.totalPowerMah;
+            for (int i = sippers.size() - 1; i >= 0; i--) {
+                final BatterySipper sipper = sippers.get(i);
+                sipper.screenPowerMah = totalScreenPowerMah
+                        * activityTimeArray.get(sipper.getUid(), 0)
+                        / totalActivityTimeMs;
+            }
+        }
+    }
+
+    /** Get the minimum of the uid's ForegroundActivity time and its TOP time. */
+    @VisibleForTesting
+    public long getProcessForegroundTimeMs(BatteryStats.Uid uid) {
+        final long rawRealTimeUs = SystemClock.elapsedRealtime() * 1000;
+        final int[] foregroundTypes = {BatteryStats.Uid.PROCESS_STATE_TOP};
+
+        long timeUs = 0;
+        for (int type : foregroundTypes) {
+            final long localTime = uid.getProcessStateTime(type, rawRealTimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            timeUs += localTime;
+        }
+
+        // Return the min value of STATE_TOP time and foreground activity time, since both of these
+        // time have some errors.
+        return Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)) / 1000;
+    }
+
+    /** Get the ForegroundActivity time of the given uid. */
+    @VisibleForTesting
+    public long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
+        final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
+        if (timer == null) {
+            return 0;
+        }
+        return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
+    }
 }
diff --git a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
index fbbee94..fbad75e 100644
--- a/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
+++ b/core/java/com/android/internal/os/SystemServerCpuThreadReader.java
@@ -22,8 +22,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Arrays;
 
 /**
  * Reads /proc/UID/task/TID/time_in_state files to obtain statistics on CPU usage
@@ -31,9 +29,7 @@
  */
 public class SystemServerCpuThreadReader {
     private final KernelSingleProcessCpuThreadReader mKernelCpuThreadReader;
-    private int[] mBinderThreadNativeTids = new int[0];  // Sorted
 
-    private long[] mLastProcessCpuTimeUs;
     private long[] mLastThreadCpuTimesUs;
     private long[] mLastBinderThreadCpuTimesUs;
 
@@ -41,8 +37,6 @@
      * Times (in microseconds) spent by the system server UID.
      */
     public static class SystemServiceCpuThreadTimes {
-        // The entire process
-        public long[] processCpuTimesUs;
         // All threads
         public long[] threadCpuTimesUs;
         // Just the threads handling incoming binder calls
@@ -61,8 +55,10 @@
     }
 
     @VisibleForTesting
-    public SystemServerCpuThreadReader(Path procPath, int pid) throws IOException {
-        this(new KernelSingleProcessCpuThreadReader(pid, procPath));
+    public SystemServerCpuThreadReader(int pid,
+            KernelSingleProcessCpuThreadReader.CpuTimeInStateReader cpuTimeInStateReader)
+            throws IOException {
+        this(new KernelSingleProcessCpuThreadReader(pid, cpuTimeInStateReader));
     }
 
     @VisibleForTesting
@@ -70,9 +66,15 @@
         mKernelCpuThreadReader = kernelCpuThreadReader;
     }
 
+    /**
+     * Start tracking CPU time-in-state for the process specified in the constructor.
+     */
+    public void startTrackingThreadCpuTime() {
+        mKernelCpuThreadReader.startTrackingThreadCpuTimes();
+    }
+
     public void setBinderThreadNativeTids(int[] nativeTids) {
-        mBinderThreadNativeTids = nativeTids.clone();
-        Arrays.sort(mBinderThreadNativeTids);
+        mKernelCpuThreadReader.setSelectedThreadIds(nativeTids);
     }
 
     /**
@@ -81,33 +83,27 @@
     @Nullable
     public SystemServiceCpuThreadTimes readDelta() {
         final int numCpuFrequencies = mKernelCpuThreadReader.getCpuFrequencyCount();
-        if (mLastProcessCpuTimeUs == null) {
-            mLastProcessCpuTimeUs = new long[numCpuFrequencies];
+        if (mLastThreadCpuTimesUs == null) {
             mLastThreadCpuTimesUs = new long[numCpuFrequencies];
             mLastBinderThreadCpuTimesUs = new long[numCpuFrequencies];
 
-            mDeltaCpuThreadTimes.processCpuTimesUs = new long[numCpuFrequencies];
             mDeltaCpuThreadTimes.threadCpuTimesUs = new long[numCpuFrequencies];
             mDeltaCpuThreadTimes.binderThreadCpuTimesUs = new long[numCpuFrequencies];
         }
 
         final KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
-                mKernelCpuThreadReader.getProcessCpuUsage(mBinderThreadNativeTids);
+                mKernelCpuThreadReader.getProcessCpuUsage();
         if (processCpuUsage == null) {
             return null;
         }
 
         for (int i = numCpuFrequencies - 1; i >= 0; i--) {
-            long processCpuTimesUs = processCpuUsage.processCpuTimesMillis[i] * 1000;
             long threadCpuTimesUs = processCpuUsage.threadCpuTimesMillis[i] * 1000;
             long binderThreadCpuTimesUs = processCpuUsage.selectedThreadCpuTimesMillis[i] * 1000;
-            mDeltaCpuThreadTimes.processCpuTimesUs[i] =
-                    Math.max(0, processCpuTimesUs - mLastProcessCpuTimeUs[i]);
             mDeltaCpuThreadTimes.threadCpuTimesUs[i] =
                     Math.max(0, threadCpuTimesUs - mLastThreadCpuTimesUs[i]);
             mDeltaCpuThreadTimes.binderThreadCpuTimesUs[i] =
                     Math.max(0, binderThreadCpuTimesUs - mLastBinderThreadCpuTimesUs[i]);
-            mLastProcessCpuTimeUs[i] = processCpuTimesUs;
             mLastThreadCpuTimesUs[i] = threadCpuTimesUs;
             mLastBinderThreadCpuTimesUs[i] = binderThreadCpuTimesUs;
         }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index fda87be..d196d4a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -103,7 +103,7 @@
      */
     public static final int PROFILE_FROM_SHELL = 1 << 15;
 
-    /*
+    /**
      * Enable using the ART app image startup cache
      */
     public static final int USE_APP_IMAGE_STARTUP_CACHE = 1 << 16;
@@ -116,14 +116,9 @@
      */
     public static final int DEBUG_IGNORE_APP_SIGNAL_HANDLER = 1 << 17;
 
-    /**
-     * Disable runtime access to {@link android.annotation.TestApi} annotated members.
-     *
-     * <p>This only takes effect if Hidden API access restrictions are enabled as well.
-     */
-    public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18;
-
     public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
+
+    public static final int MEMORY_TAG_LEVEL_NONE = 0;
     /**
      * Enable pointer tagging in this process.
      * Tags are checked during memory deallocation, but not on access.
@@ -167,7 +162,12 @@
      * GWP-ASan is activated unconditionally (but still, only a small subset of
      * allocations is protected).
      */
-    public static final int GWP_ASAN_LEVEL_ALWAYS = 2 << 21;
+    public static final int GWP_ASAN_LEVEL_ALWAYS = 1 << 22;
+
+    /**
+     * Enable automatic zero-initialization of native heap memory allocations.
+     */
+    public static final int NATIVE_HEAP_ZERO_INIT = 1 << 23;
 
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index a4ce027..585ddf6 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -492,10 +492,12 @@
                 long elapsedTimeMs = System.currentTimeMillis() - mUsapPoolRefillTriggerTimestamp;
 
                 if (elapsedTimeMs >= mUsapPoolRefillDelayMs) {
-                    // Normalize the poll timeout value when the time between one poll event and the
-                    // next pushes us over the delay value.  This prevents poll receiving a 0
-                    // timeout value, which would result in it returning immediately.
-                    pollTimeoutMs = -1;
+                    // The refill delay has elapsed during the period between poll invocations.
+                    // We will now check for any currently ready file descriptors before refilling
+                    // the USAP pool.
+                    pollTimeoutMs = 0;
+                    mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
+                    mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED;
 
                 } else if (elapsedTimeMs <= 0) {
                     // This can occur if the clock used by currentTimeMillis is reset, which is
@@ -517,9 +519,11 @@
             }
 
             if (pollReturnValue == 0) {
-                // The poll timeout has been exceeded.  This only occurs when we have finished the
-                // USAP pool refill delay period.
-
+                // The poll returned zero results either when the timeout value has been exceeded
+                // or when a non-blocking poll is issued and no FDs are ready.  In either case it
+                // is time to refill the pool.  This will result in a duplicate assignment when
+                // the non-blocking poll returns zero results, but it avoids an additional
+                // conditional in the else branch.
                 mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP;
                 mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED;
 
diff --git a/services/core/java/com/android/server/AttributeCache.java b/core/java/com/android/internal/policy/AttributeCache.java
similarity index 84%
rename from services/core/java/com/android/server/AttributeCache.java
rename to core/java/com/android/internal/policy/AttributeCache.java
index 58ec836..1bdad25 100644
--- a/services/core/java/com/android/server/AttributeCache.java
+++ b/core/java/com/android/internal/policy/AttributeCache.java
@@ -1,21 +1,20 @@
 /*
-**
-** Copyright 2007, 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.
-*/
+ * Copyright (C) 2007 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;
+package com.android.internal.policy;
 
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -34,6 +33,7 @@
  * TODO: This should be better integrated into the system so it doesn't need
  * special calls from the activity manager to clear it.
  */
+/** @hide */
 public final class AttributeCache {
     private static final int CACHE_SIZE = 4;
     private static AttributeCache sInstance = null;
@@ -54,11 +54,11 @@
             context = c;
         }
     }
-    
+
     public final static class Entry {
         public final Context context;
         public final TypedArray array;
-        
+
         public Entry(Context c, TypedArray ta) {
             context = c;
             array = ta;
@@ -70,17 +70,17 @@
             }
         }
     }
-    
+
     public static void init(Context context) {
         if (sInstance == null) {
             sInstance = new AttributeCache(context);
         }
     }
-    
+
     public static AttributeCache instance() {
         return sInstance;
     }
-    
+
     public AttributeCache(Context context) {
         mContext = context;
     }
@@ -115,7 +115,11 @@
             }
         }
     }
-    
+
+    public Entry get(String packageName, int resId, int[] styleable) {
+        return get(packageName, resId, styleable, UserHandle.USER_CURRENT);
+    }
+
     public Entry get(String packageName, int resId, int[] styleable, int userId) {
         synchronized (this) {
             Package pkg = mPackages.get(packageName);
@@ -143,12 +147,12 @@
                 pkg = new Package(context);
                 mPackages.put(packageName, pkg);
             }
-            
+
             if (map == null) {
                 map = new ArrayMap<>();
                 pkg.mMap.put(resId, map);
             }
-            
+
             try {
                 ent = new Entry(pkg.context,
                         pkg.context.obtainStyledAttributes(resId, styleable));
@@ -156,7 +160,7 @@
             } catch (Resources.NotFoundException e) {
                 return null;
             }
-            
+
             return ent;
         }
     }
diff --git a/services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java b/core/java/com/android/internal/policy/ClipRectLRAnimation.java
similarity index 95%
rename from services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java
rename to core/java/com/android/internal/policy/ClipRectLRAnimation.java
index 0db4c70..4dc3cd3 100644
--- a/services/core/java/com/android/server/wm/animation/ClipRectLRAnimation.java
+++ b/core/java/com/android/internal/policy/ClipRectLRAnimation.java
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.server.wm.animation;
+package com.android.internal.policy;
 
 import android.graphics.Rect;
 import android.view.animation.ClipRectAnimation;
diff --git a/services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java b/core/java/com/android/internal/policy/ClipRectTBAnimation.java
similarity index 96%
rename from services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java
rename to core/java/com/android/internal/policy/ClipRectTBAnimation.java
index 1f5b1a3..24913cf 100644
--- a/services/core/java/com/android/server/wm/animation/ClipRectTBAnimation.java
+++ b/core/java/com/android/internal/policy/ClipRectTBAnimation.java
@@ -11,16 +11,15 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.server.wm.animation;
+package com.android.internal.policy;
 
 import android.graphics.Rect;
 import android.view.animation.ClipRectAnimation;
 import android.view.animation.Interpolator;
 import android.view.animation.Transformation;
-import android.view.animation.TranslateAnimation;
 
 /**
  * Special case of ClipRectAnimation that animates only the top/bottom
diff --git a/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java b/core/java/com/android/internal/policy/LogDecelerateInterpolator.java
similarity index 93%
rename from services/core/java/com/android/server/policy/LogDecelerateInterpolator.java
rename to core/java/com/android/internal/policy/LogDecelerateInterpolator.java
index ed5dc6f..dee77aa 100644
--- a/services/core/java/com/android/server/policy/LogDecelerateInterpolator.java
+++ b/core/java/com/android/internal/policy/LogDecelerateInterpolator.java
@@ -11,13 +11,14 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.server.policy;
+package com.android.internal.policy;
 
 import android.view.animation.Interpolator;
 
+/** @hide */
 public class LogDecelerateInterpolator implements Interpolator {
 
     private int mBase;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 3be841c..5df175e 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2540,15 +2540,16 @@
             }
         }
 
-        if (a.getBoolean(R.styleable.Window_windowBackgroundBlurEnabled, false)) {
+        if (a.getBoolean(R.styleable.Window_windowBlurBehindEnabled, false)) {
             if ((getForcedWindowFlags() & WindowManager.LayoutParams.FLAG_BLUR_BEHIND) == 0) {
                 params.flags |= WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
             }
 
-            params.backgroundBlurRadius = a.getDimensionPixelSize(
-                        android.R.styleable.Window_windowBackgroundBlurRadius, 0);
+            params.blurBehindRadius = a.getDimensionPixelSize(
+                    android.R.styleable.Window_windowBlurBehindRadius, 0);
         }
 
+
         if (params.windowAnimations == 0) {
             params.windowAnimations = a.getResourceId(
                     R.styleable.Window_windowAnimationStyle, 0);
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
new file mode 100644
index 0000000..56b25b2
--- /dev/null
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -0,0 +1,960 @@
+/*
+ * Copyright (C) 2021 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.internal.policy;
+
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.ResourceId;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.SystemProperties;
+import android.util.Slog;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager.TransitionOldType;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.ClipRectAnimation;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
+
+import com.android.internal.R;
+
+import java.util.List;
+
+/** @hide */
+public class TransitionAnimation {
+    // These are the possible states for the enter/exit activities during a thumbnail transition
+    public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
+    public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
+    public static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
+    public static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
+
+    /**
+     * Maximum duration for the clip reveal animation. This is used when there is a lot of movement
+     * involved, to make it more understandable.
+     */
+    private static final int MAX_CLIP_REVEAL_TRANSITION_DURATION = 420;
+    private static final int CLIP_REVEAL_TRANSLATION_Y_DP = 8;
+    private static final int THUMBNAIL_APP_TRANSITION_DURATION = 336;
+
+    public static final int DEFAULT_APP_TRANSITION_DURATION = 336;
+
+    /** Fraction of animation at which the recents thumbnail becomes completely transparent */
+    private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.5f;
+
+    private final Context mContext;
+    private final String mTag;
+
+    private final LogDecelerateInterpolator mInterpolator = new LogDecelerateInterpolator(100, 0);
+    /** Interpolator to be used for animations that respond directly to a touch */
+    private final Interpolator mTouchResponseInterpolator =
+            new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+    private final Interpolator mClipHorizontalInterpolator = new PathInterpolator(0, 0, 0.4f, 1f);
+    private final Interpolator mDecelerateInterpolator;
+    private final Interpolator mLinearOutSlowInInterpolator;
+    private final Interpolator mThumbnailFadeOutInterpolator;
+    private final Rect mTmpFromClipRect = new Rect();
+    private final Rect mTmpToClipRect = new Rect();
+    private final Rect mTmpRect = new Rect();
+
+    private final int mClipRevealTranslationY;
+    private final int mConfigShortAnimTime;
+    private final int mDefaultWindowAnimationStyleResId;
+
+    private final boolean mDebug;
+    private final boolean mGridLayoutRecentsEnabled;
+    private final boolean mLowRamRecentsEnabled;
+
+    public TransitionAnimation(Context context, boolean debug, String tag) {
+        mContext = context;
+        mDebug = debug;
+        mTag = tag;
+
+        mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.decelerate_cubic);
+        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.linear_out_slow_in);
+        mThumbnailFadeOutInterpolator = input -> {
+            // Linear response for first fraction, then complete after that.
+            if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
+                float t = input / RECENTS_THUMBNAIL_FADEOUT_FRACTION;
+                return mLinearOutSlowInInterpolator.getInterpolation(t);
+            }
+            return 1f;
+        };
+
+        mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP
+                * mContext.getResources().getDisplayMetrics().density);
+        mConfigShortAnimTime = context.getResources().getInteger(
+                com.android.internal.R.integer.config_shortAnimTime);
+
+        mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
+        mLowRamRecentsEnabled = ActivityManager.isLowRamDeviceStatic();
+
+        final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
+                com.android.internal.R.styleable.Window);
+        mDefaultWindowAnimationStyleResId = windowStyle.getResourceId(
+                com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+        windowStyle.recycle();
+    }
+
+    public Animation loadKeyguardExitAnimation(int transit, int transitionFlags) {
+        if ((transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) != 0) {
+            return null;
+        }
+        final boolean toShade =
+                (transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0;
+        final boolean subtle =
+                (transitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0;
+        return createHiddenByKeyguardExit(mContext, mInterpolator,
+                transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER, toShade, subtle);
+    }
+
+    @Nullable
+    public Animation loadKeyguardUnoccludeAnimation(LayoutParams lp) {
+        return loadAnimationRes(lp, com.android.internal.R.anim.wallpaper_open_exit);
+    }
+
+    @Nullable
+    public Animation loadVoiceActivityOpenAnimation(LayoutParams lp, boolean enter) {
+        return loadAnimationRes(lp, enter
+                ? com.android.internal.R.anim.voice_activity_open_enter
+                : com.android.internal.R.anim.voice_activity_open_exit);
+    }
+
+    @Nullable
+    public Animation loadVoiceActivityExitAnimation(LayoutParams lp, boolean enter) {
+        return loadAnimationRes(lp, enter
+                ? com.android.internal.R.anim.voice_activity_close_enter
+                : com.android.internal.R.anim.voice_activity_close_exit);
+    }
+
+    @Nullable
+    public Animation loadAppTransitionAnimation(String packageName, int resId) {
+        return loadAnimationRes(packageName, resId);
+    }
+
+    @Nullable
+    public Animation loadCrossProfileAppEnterAnimation() {
+        return loadAnimationRes("android",
+                com.android.internal.R.anim.task_open_enter_cross_profile_apps);
+    }
+
+    @Nullable
+    public Animation loadCrossProfileAppThumbnailEnterAnimation() {
+        return loadAnimationRes(
+                "android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
+    }
+
+    @Nullable
+    private Animation loadAnimationRes(LayoutParams lp, int resId) {
+        Context context = mContext;
+        if (ResourceId.isValid(resId)) {
+            AttributeCache.Entry ent = getCachedAnimations(lp);
+            if (ent != null) {
+                context = ent.context;
+            }
+            return loadAnimationSafely(context, resId, mTag);
+        }
+        return null;
+    }
+
+    @Nullable
+    private Animation loadAnimationRes(String packageName, int resId) {
+        if (ResourceId.isValid(resId)) {
+            AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
+            if (ent != null) {
+                return loadAnimationSafely(ent.context, resId, mTag);
+            }
+        }
+        return null;
+    }
+
+    @Nullable
+    public Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
+        int resId = Resources.ID_NULL;
+        Context context = mContext;
+        if (animAttr >= 0) {
+            AttributeCache.Entry ent = getCachedAnimations(lp);
+            if (ent != null) {
+                context = ent.context;
+                resId = ent.array.getResourceId(animAttr, 0);
+            }
+        }
+        resId = updateToTranslucentAnimIfNeeded(resId, transit);
+        if (ResourceId.isValid(resId)) {
+            return loadAnimationSafely(context, resId, mTag);
+        }
+        return null;
+    }
+
+    @Nullable
+    private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
+        if (mDebug) {
+            Slog.v(mTag, "Loading animations: layout params pkg="
+                    + (lp != null ? lp.packageName : null)
+                    + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
+        }
+        if (lp != null && lp.windowAnimations != 0) {
+            // If this is a system resource, don't try to load it from the
+            // application resources.  It is nice to avoid loading application
+            // resources if we can.
+            String packageName = lp.packageName != null ? lp.packageName : "android";
+            int resId = getAnimationStyleResId(lp);
+            if ((resId & 0xFF000000) == 0x01000000) {
+                packageName = "android";
+            }
+            if (mDebug) {
+                Slog.v(mTag, "Loading animations: picked package=" + packageName);
+            }
+            return AttributeCache.instance().get(packageName, resId,
+                    com.android.internal.R.styleable.WindowAnimation);
+        }
+        return null;
+    }
+
+    @Nullable
+    private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
+        if (mDebug) {
+            Slog.v(mTag, "Loading animations: package="
+                    + packageName + " resId=0x" + Integer.toHexString(resId));
+        }
+        if (packageName != null) {
+            if ((resId & 0xFF000000) == 0x01000000) {
+                packageName = "android";
+            }
+            if (mDebug) {
+                Slog.v(mTag, "Loading animations: picked package="
+                        + packageName);
+            }
+            return AttributeCache.instance().get(packageName, resId,
+                    com.android.internal.R.styleable.WindowAnimation);
+        }
+        return null;
+    }
+
+    /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
+    public int getAnimationStyleResId(@NonNull LayoutParams lp) {
+        int resId = lp.windowAnimations;
+        if (lp.type == LayoutParams.TYPE_APPLICATION_STARTING) {
+            // Note that we don't want application to customize starting window animation.
+            // Since this window is specific for displaying while app starting,
+            // application should not change its animation directly.
+            // In this case, it will use system resource to get default animation.
+            resId = mDefaultWindowAnimationStyleResId;
+        }
+        return resId;
+    }
+
+    public Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets,
+            Rect startRect) {
+        setupDefaultNextAppTransitionStartRect(startRect, mTmpFromClipRect);
+        final int left = mTmpFromClipRect.left;
+        final int top = mTmpFromClipRect.top;
+        mTmpFromClipRect.offset(-left, -top);
+        // TODO: Isn't that strange that we ignore exact position of the containingFrame?
+        mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
+        AnimationSet set = new AnimationSet(true);
+        float fromWidth = mTmpFromClipRect.width();
+        float toWidth = mTmpToClipRect.width();
+        float fromHeight = mTmpFromClipRect.height();
+        // While the window might span the whole display, the actual content will be cropped to the
+        // system decoration frame, for example when the window is docked. We need to take into
+        // account the visible height when constructing the animation.
+        float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom;
+        int translateAdjustment = 0;
+        if (fromWidth <= toWidth && fromHeight <= toHeight) {
+            // The final window is larger in both dimensions than current window (e.g. we are
+            // maximizing), so we can simply unclip the new window and there will be no disappearing
+            // frame.
+            set.addAnimation(new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect));
+        } else {
+            // The disappearing window has one larger dimension. We need to apply scaling, so the
+            // first frame of the entry animation matches the old window.
+            set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
+            // We might not be going exactly full screen, but instead be aligned under the status
+            // bar using cropping. We still need to account for the cropped part, which will also
+            // be scaled.
+            translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight);
+        }
+
+        // We animate the translation from the old position of the removed window, to the new
+        // position of the added window. The latter might not be full screen, for example docked for
+        // docked windows.
+        TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
+                0, top - containingFrame.top - translateAdjustment, 0);
+        set.addAnimation(translate);
+        set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+        set.setZAdjustment(Animation.ZORDER_TOP);
+        return set;
+    }
+
+    private void setupDefaultNextAppTransitionStartRect(Rect startRect, Rect rect) {
+        if (startRect == null) {
+            Slog.e(mTag, "Starting rect for app requested, but none available", new Throwable());
+            rect.setEmpty();
+        } else {
+            rect.set(startRect);
+        }
+    }
+
+    public Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame,
+            Rect displayFrame, Rect startRect) {
+        final Animation anim;
+        if (enter) {
+            final int appWidth = appFrame.width();
+            final int appHeight = appFrame.height();
+
+            // mTmpRect will contain an area around the launcher icon that was pressed. We will
+            // clip reveal from that area in the final area of the app.
+            setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
+
+            float t = 0f;
+            if (appHeight > 0) {
+                t = (float) mTmpRect.top / displayFrame.height();
+            }
+            int translationY = mClipRevealTranslationY + (int) (displayFrame.height() / 7f * t);
+            int translationX = 0;
+            int translationYCorrection = translationY;
+            int centerX = mTmpRect.centerX();
+            int centerY = mTmpRect.centerY();
+            int halfWidth = mTmpRect.width() / 2;
+            int halfHeight = mTmpRect.height() / 2;
+            int clipStartX = centerX - halfWidth - appFrame.left;
+            int clipStartY = centerY - halfHeight - appFrame.top;
+            boolean cutOff = false;
+
+            // If the starting rectangle is fully or partially outside of the target rectangle, we
+            // need to start the clipping at the edge and then achieve the rest with translation
+            // and extending the clip rect from that edge.
+            if (appFrame.top > centerY - halfHeight) {
+                translationY = (centerY - halfHeight) - appFrame.top;
+                translationYCorrection = 0;
+                clipStartY = 0;
+                cutOff = true;
+            }
+            if (appFrame.left > centerX - halfWidth) {
+                translationX = (centerX - halfWidth) - appFrame.left;
+                clipStartX = 0;
+                cutOff = true;
+            }
+            if (appFrame.right < centerX + halfWidth) {
+                translationX = (centerX + halfWidth) - appFrame.right;
+                clipStartX = appWidth - mTmpRect.width();
+                cutOff = true;
+            }
+            final long duration = calculateClipRevealTransitionDuration(cutOff, translationX,
+                    translationY, displayFrame);
+
+            // Clip third of the from size of launch icon, expand to full width/height
+            Animation clipAnimLR = new ClipRectLRAnimation(
+                    clipStartX, clipStartX + mTmpRect.width(), 0, appWidth);
+            clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
+            clipAnimLR.setDuration((long) (duration / 2.5f));
+
+            TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0);
+            translate.setInterpolator(cutOff ? mTouchResponseInterpolator
+                    : mLinearOutSlowInInterpolator);
+            translate.setDuration(duration);
+
+            Animation clipAnimTB = new ClipRectTBAnimation(
+                    clipStartY, clipStartY + mTmpRect.height(),
+                    0, appHeight,
+                    translationYCorrection, 0,
+                    mLinearOutSlowInInterpolator);
+            clipAnimTB.setInterpolator(mTouchResponseInterpolator);
+            clipAnimTB.setDuration(duration);
+
+            // Quick fade-in from icon to app window
+            final long alphaDuration = duration / 4;
+            AlphaAnimation alpha = new AlphaAnimation(0.5f, 1);
+            alpha.setDuration(alphaDuration);
+            alpha.setInterpolator(mLinearOutSlowInInterpolator);
+
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(clipAnimLR);
+            set.addAnimation(clipAnimTB);
+            set.addAnimation(translate);
+            set.addAnimation(alpha);
+            set.setZAdjustment(Animation.ZORDER_TOP);
+            set.initialize(appWidth, appHeight, appWidth, appHeight);
+            anim = set;
+        } else {
+            final long duration;
+            switch (transit) {
+                case TRANSIT_OLD_ACTIVITY_OPEN:
+                case TRANSIT_OLD_ACTIVITY_CLOSE:
+                    duration = mConfigShortAnimTime;
+                    break;
+                default:
+                    duration = DEFAULT_APP_TRANSITION_DURATION;
+                    break;
+            }
+            if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN
+                    || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) {
+                // If we are on top of the wallpaper, we need an animation that
+                // correctly handles the wallpaper staying static behind all of
+                // the animated elements.  To do this, will just have the existing
+                // element fade out.
+                anim = new AlphaAnimation(1, 0);
+                anim.setDetachWallpaper(true);
+            } else {
+                // For normal animations, the exiting element just holds in place.
+                anim = new AlphaAnimation(1, 1);
+            }
+            anim.setInterpolator(mDecelerateInterpolator);
+            anim.setDuration(duration);
+            anim.setFillAfter(true);
+        }
+        return anim;
+    }
+
+    public Animation createScaleUpAnimationLocked(int transit, boolean enter,
+            Rect containingFrame, Rect startRect) {
+        Animation a;
+        setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
+        if (enter) {
+            // Entering app zooms out from the center of the initial rect.
+            float scaleW = mTmpRect.width() / (float) appWidth;
+            float scaleH = mTmpRect.height() / (float) appHeight;
+            Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
+                    computePivot(mTmpRect.left, scaleW),
+                    computePivot(mTmpRect.top, scaleH));
+            scale.setInterpolator(mDecelerateInterpolator);
+
+            Animation alpha = new AlphaAnimation(0, 1);
+            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
+
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            set.addAnimation(alpha);
+            set.setDetachWallpaper(true);
+            a = set;
+        } else  if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN
+                || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) {
+            // If we are on top of the wallpaper, we need an animation that
+            // correctly handles the wallpaper staying static behind all of
+            // the animated elements.  To do this, will just have the existing
+            // element fade out.
+            a = new AlphaAnimation(1, 0);
+            a.setDetachWallpaper(true);
+        } else {
+            // For normal animations, the exiting element just holds in place.
+            a = new AlphaAnimation(1, 1);
+        }
+
+        // Pick the desired duration.  If this is an inter-activity transition,
+        // it  is the standard duration for that.  Otherwise we use the longer
+        // task transition duration.
+        final long duration;
+        switch (transit) {
+            case TRANSIT_OLD_ACTIVITY_OPEN:
+            case TRANSIT_OLD_ACTIVITY_CLOSE:
+                duration = mConfigShortAnimTime;
+                break;
+            default:
+                duration = DEFAULT_APP_TRANSITION_DURATION;
+                break;
+        }
+        a.setDuration(duration);
+        a.setFillAfter(true);
+        a.setInterpolator(mDecelerateInterpolator);
+        a.initialize(appWidth, appHeight, appWidth, appHeight);
+        return a;
+    }
+
+    /**
+     * This animation is created when we are doing a thumbnail transition, for the activity that is
+     * leaving, and the activity that is entering.
+     */
+    public Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState,
+            Rect containingFrame, int transit, HardwareBuffer thumbnailHeader,
+            Rect startRect) {
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
+        Animation a;
+        setupDefaultNextAppTransitionStartRect(startRect, mTmpRect);
+        final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
+        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+
+        switch (thumbTransitState) {
+            case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
+                // Entering app scales up with the thumbnail
+                float scaleW = thumbWidth / appWidth;
+                float scaleH = thumbHeight / appHeight;
+                a = new ScaleAnimation(scaleW, 1, scaleH, 1,
+                        computePivot(mTmpRect.left, scaleW),
+                        computePivot(mTmpRect.top, scaleH));
+                break;
+            }
+            case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
+                // Exiting app while the thumbnail is scaling up should fade or stay in place
+                if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
+                    // Fade out while bringing up selected activity. This keeps the
+                    // current activity from showing through a launching wallpaper
+                    // activity.
+                    a = new AlphaAnimation(1, 0);
+                } else {
+                    // noop animation
+                    a = new AlphaAnimation(1, 1);
+                }
+                break;
+            }
+            case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
+                // Entering the other app, it should just be visible while we scale the thumbnail
+                // down above it
+                a = new AlphaAnimation(1, 1);
+                break;
+            }
+            case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
+                // Exiting the current app, the app should scale down with the thumbnail
+                float scaleW = thumbWidth / appWidth;
+                float scaleH = thumbHeight / appHeight;
+                Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
+                        computePivot(mTmpRect.left, scaleW),
+                        computePivot(mTmpRect.top, scaleH));
+
+                Animation alpha = new AlphaAnimation(1, 0);
+
+                AnimationSet set = new AnimationSet(true);
+                set.addAnimation(scale);
+                set.addAnimation(alpha);
+                set.setZAdjustment(Animation.ZORDER_TOP);
+                a = set;
+                break;
+            }
+            default:
+                throw new RuntimeException("Invalid thumbnail transition state");
+        }
+
+        return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
+    }
+
+    /**
+     * This alternate animation is created when we are doing a thumbnail transition, for the
+     * activity that is leaving, and the activity that is entering.
+     */
+    public Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
+            int orientation, int transit, Rect containingFrame, Rect contentInsets,
+            @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
+            Rect startRect, Rect defaultStartRect) {
+        Animation a;
+        final int appWidth = containingFrame.width();
+        final int appHeight = containingFrame.height();
+        setupDefaultNextAppTransitionStartRect(defaultStartRect, mTmpRect);
+        final int thumbWidthI = mTmpRect.width();
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = mTmpRect.height();
+        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+        final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left;
+        final int thumbStartY = mTmpRect.top - containingFrame.top;
+
+        switch (thumbTransitState) {
+            case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
+            case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
+                final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+                if (freeform && scaleUp) {
+                    a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
+                            containingFrame, surfaceInsets, startRect, defaultStartRect);
+                } else if (freeform) {
+                    a = createAspectScaledThumbnailExitFreeformAnimationLocked(
+                            containingFrame, surfaceInsets, startRect, defaultStartRect);
+                } else {
+                    AnimationSet set = new AnimationSet(true);
+
+                    // In portrait, we scale to fit the width
+                    mTmpFromClipRect.set(containingFrame);
+                    mTmpToClipRect.set(containingFrame);
+
+                    // Containing frame is in screen space, but we need the clip rect in the
+                    // app space.
+                    mTmpFromClipRect.offsetTo(0, 0);
+                    mTmpToClipRect.offsetTo(0, 0);
+
+                    // Exclude insets region from the source clip.
+                    mTmpFromClipRect.inset(contentInsets);
+
+                    if (shouldScaleDownThumbnailTransition(orientation)) {
+                        // We scale the width and clip to the top/left square
+                        float scale =
+                                thumbWidth / (appWidth - contentInsets.left - contentInsets.right);
+                        if (!mGridLayoutRecentsEnabled) {
+                            int unscaledThumbHeight = (int) (thumbHeight / scale);
+                            mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
+                        }
+
+                        Animation scaleAnim = new ScaleAnimation(
+                                scaleUp ? scale : 1, scaleUp ? 1 : scale,
+                                scaleUp ? scale : 1, scaleUp ? 1 : scale,
+                                containingFrame.width() / 2f,
+                                containingFrame.height() / 2f + contentInsets.top);
+                        final float targetX = (mTmpRect.left - containingFrame.left);
+                        final float x = containingFrame.width() / 2f
+                                - containingFrame.width() / 2f * scale;
+                        final float targetY = (mTmpRect.top - containingFrame.top);
+                        float y = containingFrame.height() / 2f
+                                - containingFrame.height() / 2f * scale;
+
+                        // During transition may require clipping offset from any top stable insets
+                        // such as the statusbar height when statusbar is hidden
+                        if (mLowRamRecentsEnabled && contentInsets.top == 0 && scaleUp) {
+                            mTmpFromClipRect.top += stableInsets.top;
+                            y += stableInsets.top;
+                        }
+                        final float startX = targetX - x;
+                        final float startY = targetY - y;
+                        Animation clipAnim = scaleUp
+                                ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
+                                : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
+                        Animation translateAnim = scaleUp
+                                ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0)
+                                : createCurvedMotion(0, startX, 0, startY - contentInsets.top);
+
+                        set.addAnimation(clipAnim);
+                        set.addAnimation(scaleAnim);
+                        set.addAnimation(translateAnim);
+
+                    } else {
+                        // In landscape, we don't scale at all and only crop
+                        mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI;
+                        mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI;
+
+                        Animation clipAnim = scaleUp
+                                ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
+                                : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
+                        Animation translateAnim = scaleUp
+                                ? createCurvedMotion(thumbStartX, 0,
+                                thumbStartY - contentInsets.top, 0)
+                                : createCurvedMotion(0, thumbStartX, 0,
+                                        thumbStartY - contentInsets.top);
+
+                        set.addAnimation(clipAnim);
+                        set.addAnimation(translateAnim);
+                    }
+                    a = set;
+                    a.setZAdjustment(Animation.ZORDER_TOP);
+                }
+                break;
+            }
+            case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
+                // Previous app window during the scale up
+                if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
+                    // Fade out the source activity if we are animating to a wallpaper
+                    // activity.
+                    a = new AlphaAnimation(1, 0);
+                } else {
+                    a = new AlphaAnimation(1, 1);
+                }
+                break;
+            }
+            case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
+                // Target app window during the scale down
+                if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
+                    // Fade in the destination activity if we are animating from a wallpaper
+                    // activity.
+                    a = new AlphaAnimation(0, 1);
+                } else {
+                    a = new AlphaAnimation(1, 1);
+                }
+                break;
+            }
+            default:
+                throw new RuntimeException("Invalid thumbnail transition state");
+        }
+
+        return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight,
+                THUMBNAIL_APP_TRANSITION_DURATION, mTouchResponseInterpolator);
+    }
+
+    /**
+     * Prepares the specified animation with a standard duration, interpolator, etc.
+     */
+    private Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight,
+            int transit) {
+        // Pick the desired duration.  If this is an inter-activity transition,
+        // it  is the standard duration for that.  Otherwise we use the longer
+        // task transition duration.
+        final int duration;
+        switch (transit) {
+            case TRANSIT_OLD_ACTIVITY_OPEN:
+            case TRANSIT_OLD_ACTIVITY_CLOSE:
+                duration = mConfigShortAnimTime;
+                break;
+            default:
+                duration = DEFAULT_APP_TRANSITION_DURATION;
+                break;
+        }
+        return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight, duration,
+                mDecelerateInterpolator);
+    }
+
+
+    private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
+            @Nullable Rect surfaceInsets, @Nullable Rect startRect,
+            @Nullable Rect defaultStartRect) {
+        getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
+        return createAspectScaledThumbnailFreeformAnimationLocked(mTmpRect, frame, surfaceInsets,
+                true);
+    }
+
+    private Animation createAspectScaledThumbnailExitFreeformAnimationLocked(Rect frame,
+            @Nullable Rect surfaceInsets, @Nullable Rect startRect,
+            @Nullable Rect defaultStartRect) {
+        getNextAppTransitionStartRect(startRect, defaultStartRect, mTmpRect);
+        return createAspectScaledThumbnailFreeformAnimationLocked(frame, mTmpRect, surfaceInsets,
+                false);
+    }
+
+    private void getNextAppTransitionStartRect(Rect startRect, Rect defaultStartRect, Rect rect) {
+        if (startRect == null && defaultStartRect == null) {
+            Slog.e(mTag, "Starting rect for container not available", new Throwable());
+            rect.setEmpty();
+        } else {
+            rect.set(startRect != null ? startRect : defaultStartRect);
+        }
+    }
+
+    private AnimationSet createAspectScaledThumbnailFreeformAnimationLocked(Rect sourceFrame,
+            Rect destFrame, @Nullable Rect surfaceInsets, boolean enter) {
+        final float sourceWidth = sourceFrame.width();
+        final float sourceHeight = sourceFrame.height();
+        final float destWidth = destFrame.width();
+        final float destHeight = destFrame.height();
+        final float scaleH = enter ? sourceWidth / destWidth : destWidth / sourceWidth;
+        final float scaleV = enter ? sourceHeight / destHeight : destHeight / sourceHeight;
+        AnimationSet set = new AnimationSet(true);
+        final int surfaceInsetsH = surfaceInsets == null
+                ? 0 : surfaceInsets.left + surfaceInsets.right;
+        final int surfaceInsetsV = surfaceInsets == null
+                ? 0 : surfaceInsets.top + surfaceInsets.bottom;
+        // We want the scaling to happen from the center of the surface. In order to achieve that,
+        // we need to account for surface insets that will be used to enlarge the surface.
+        final float scaleHCenter = ((enter ? destWidth : sourceWidth) + surfaceInsetsH) / 2;
+        final float scaleVCenter = ((enter ? destHeight : sourceHeight) + surfaceInsetsV) / 2;
+        final ScaleAnimation scale = enter
+                ? new ScaleAnimation(scaleH, 1, scaleV, 1, scaleHCenter, scaleVCenter)
+                : new ScaleAnimation(1, scaleH, 1, scaleV, scaleHCenter, scaleVCenter);
+        final int sourceHCenter = sourceFrame.left + sourceFrame.width() / 2;
+        final int sourceVCenter = sourceFrame.top + sourceFrame.height() / 2;
+        final int destHCenter = destFrame.left + destFrame.width() / 2;
+        final int destVCenter = destFrame.top + destFrame.height() / 2;
+        final int fromX = enter ? sourceHCenter - destHCenter : destHCenter - sourceHCenter;
+        final int fromY = enter ? sourceVCenter - destVCenter : destVCenter - sourceVCenter;
+        final TranslateAnimation translation = enter ? new TranslateAnimation(fromX, 0, fromY, 0)
+                : new TranslateAnimation(0, fromX, 0, fromY);
+        set.addAnimation(scale);
+        set.addAnimation(translation);
+        return set;
+    }
+
+    /**
+     * @return whether the transition should show the thumbnail being scaled down.
+     */
+    private boolean shouldScaleDownThumbnailTransition(int orientation) {
+        return mGridLayoutRecentsEnabled
+                || orientation == Configuration.ORIENTATION_PORTRAIT;
+    }
+
+    private static int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) {
+        if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN
+                && anim == R.anim.activity_open_enter) {
+            return R.anim.activity_translucent_open_enter;
+        }
+        if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE
+                && anim == R.anim.activity_close_exit) {
+            return R.anim.activity_translucent_close_exit;
+        }
+        return anim;
+    }
+
+    /**
+     * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
+     * the start rect is outside of the target rect, and there is a lot of movement going on.
+     *
+     * @param cutOff whether the start rect was not fully contained by the end rect
+     * @param translationX the total translation the surface moves in x direction
+     * @param translationY the total translation the surfaces moves in y direction
+     * @param displayFrame our display frame
+     *
+     * @return the duration of the clip reveal animation, in milliseconds
+     */
+    private static long calculateClipRevealTransitionDuration(boolean cutOff, float translationX,
+            float translationY, Rect displayFrame) {
+        if (!cutOff) {
+            return DEFAULT_APP_TRANSITION_DURATION;
+        }
+        final float fraction = Math.max(Math.abs(translationX) / displayFrame.width(),
+                Math.abs(translationY) / displayFrame.height());
+        return (long) (DEFAULT_APP_TRANSITION_DURATION + fraction
+                * (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
+    }
+
+    /**
+     * Prepares the specified animation with a standard duration, interpolator, etc.
+     */
+    private static Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth,
+            int appHeight, long duration, Interpolator interpolator) {
+        if (duration > 0) {
+            a.setDuration(duration);
+        }
+        a.setFillAfter(true);
+        if (interpolator != null) {
+            a.setInterpolator(interpolator);
+        }
+        a.initialize(appWidth, appHeight, appWidth, appHeight);
+        return a;
+    }
+
+    private static Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
+        return new TranslateAnimation(fromX, toX, fromY, toY);
+    }
+
+    /**
+     * Compute the pivot point for an animation that is scaling from a small
+     * rect on screen to a larger rect.  The pivot point varies depending on
+     * the distance between the inner and outer edges on both sides.  This
+     * function computes the pivot point for one dimension.
+     * @param startPos  Offset from left/top edge of outer rectangle to
+     * left/top edge of inner rectangle.
+     * @param finalScale The scaling factor between the size of the outer
+     * and inner rectangles.
+     */
+    public static float computePivot(int startPos, float finalScale) {
+
+        /*
+        Theorem of intercepting lines:
+
+          +      +   +-----------------------------------------------+
+          |      |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+        x |    y |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+          |      |   |                                               |
+          |      +   |             +--------------------+            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             |                    |            |
+          |          |             +--------------------+            |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          |                                               |
+          |          +-----------------------------------------------+
+          |
+          |
+          |
+          |
+          |
+          |
+          |
+          |
+          |
+          +                                 ++
+                                         p  ++
+
+        scale = (x - y) / x
+        <=> x = -y / (scale - 1)
+        */
+        final float denom = finalScale - 1;
+        if (Math.abs(denom) < .0001f) {
+            return startPos;
+        }
+        return -startPos / denom;
+    }
+
+    @Nullable
+    public static Animation loadAnimationSafely(Context context, int resId, String tag) {
+        try {
+            return AnimationUtils.loadAnimation(context, resId);
+        } catch (Resources.NotFoundException e) {
+            Slog.w(tag, "Unable to load animation resource", e);
+            return null;
+        }
+    }
+
+    public static Animation createHiddenByKeyguardExit(Context context,
+            LogDecelerateInterpolator interpolator, boolean onWallpaper,
+            boolean goingToNotificationShade, boolean subtleAnimation) {
+        if (goingToNotificationShade) {
+            return AnimationUtils.loadAnimation(context, R.anim.lock_screen_behind_enter_fade_in);
+        }
+
+        final int resource;
+        if (subtleAnimation) {
+            resource = R.anim.lock_screen_behind_enter_subtle;
+        } else if (onWallpaper) {
+            resource = R.anim.lock_screen_behind_enter_wallpaper;
+        } else  {
+            resource = R.anim.lock_screen_behind_enter;
+        }
+
+        AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(context, resource);
+
+        // TODO: Use XML interpolators when we have log interpolators available in XML.
+        final List<Animation> animations = set.getAnimations();
+        for (int i = animations.size() - 1; i >= 0; --i) {
+            animations.get(i).setInterpolator(interpolator);
+        }
+
+        return set;
+    }
+}
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index b744a5d..e310f8d 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -20,8 +20,10 @@
 import static android.os.BatteryStats.ENERGY_DATA_UNAVAILABLE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
+import android.util.DebugUtils;
 import android.util.Slog;
 import android.view.Display;
 
@@ -33,7 +35,9 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Tracks the measured energy usage of various subsystems according to their {@link EnergyBucket}.
+ * Tracks the measured energy usage of various subsystems according to their
+ * {@link StandardEnergyBucket} or custom energy bucket (which is tied to
+ * {@link android.hardware.power.stats.EnergyConsumer.ordinal}).
  *
  * This class doesn't use a TimeBase, and instead requires manually decisions about when to
  * accumulate since it is trivial. However, in the future, a TimeBase could be used instead.
@@ -42,15 +46,13 @@
 public class MeasuredEnergyStats {
     private static final String TAG = "MeasuredEnergyStats";
 
-    // Note: {@link com.android.internal.os.BatteryStatsImpl#VERSION} must be updated if energy
-    // bucket integers are modified.
+    // Note: {@link com.android.internal.os.BatteryStatsImpl#VERSION} MUST be updated if standard
+    // energy bucket integers are modified/added/removed.
     public static final int ENERGY_BUCKET_UNKNOWN = -1;
     public static final int ENERGY_BUCKET_SCREEN_ON = 0;
     public static final int ENERGY_BUCKET_SCREEN_DOZE = 1;
     public static final int ENERGY_BUCKET_SCREEN_OTHER = 2;
-    public static final int NUMBER_ENERGY_BUCKETS = 3;
-    private static final String[] ENERGY_BUCKET_NAMES =
-            {"screen-on", "screen-doze", "screen-other"};
+    public static final int NUMBER_STANDARD_ENERGY_BUCKETS = 3; // Buckets above this are custom.
 
     @IntDef(prefix = {"ENERGY_BUCKET_"}, value = {
             ENERGY_BUCKET_UNKNOWN,
@@ -59,28 +61,37 @@
             ENERGY_BUCKET_SCREEN_OTHER,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface EnergyBucket {
+    public @interface StandardEnergyBucket {
     }
 
     /**
-     * Total energy (in microjoules) that an {@link EnergyBucket} has accumulated since the last
-     * reset. Values MUST be non-zero or ENERGY_DATA_UNAVAILABLE. Accumulation only occurs
+     * Total energy (in microjoules) that an energy bucket (including both
+     * {@link StandardEnergyBucket} and custom buckets) has accumulated since the last reset.
+     * Values MUST be non-zero or ENERGY_DATA_UNAVAILABLE. Accumulation only occurs
      * while the necessary conditions are satisfied (e.g. on battery).
      *
+     * Energy for both {@link StandardEnergyBucket}s and custom energy buckets are stored in this
+     * array, and may internally both referred to as 'buckets'. This is an implementation detail;
+     * externally, we differentiate between these two data sources.
+     *
      * Warning: Long array is used for access speed. If the number of supported subsystems
      * becomes large, consider using an alternate data structure such as a SparseLongArray.
      */
-    private final long[] mAccumulatedEnergiesMicroJoules = new long[NUMBER_ENERGY_BUCKETS];
+    private final long[] mAccumulatedEnergiesMicroJoules;
 
     /**
      * Creates a MeasuredEnergyStats set to support the provided energy buckets.
-     * supportedEnergyBuckets should generally be of size {@link #NUMBER_ENERGY_BUCKETS}.
+     * supportedStandardBuckets must be of size {@link #NUMBER_STANDARD_ENERGY_BUCKETS}.
+     * numCustomBuckets >= 0 is the number of (non-standard) custom energy buckets on the device.
      */
-    public MeasuredEnergyStats(boolean[] supportedEnergyBuckets) {
+    public MeasuredEnergyStats(boolean[] supportedStandardBuckets, int numCustomBuckets) {
+        final int numTotalBuckets = NUMBER_STANDARD_ENERGY_BUCKETS + numCustomBuckets;
+        mAccumulatedEnergiesMicroJoules = new long[numTotalBuckets];
         // Initialize to all zeros where supported, otherwise ENERGY_DATA_UNAVAILABLE.
-        for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
-            if (!supportedEnergyBuckets[bucket]) {
-                mAccumulatedEnergiesMicroJoules[bucket] = ENERGY_DATA_UNAVAILABLE;
+        // All custom buckets are, by definition, supported, so their values stay at 0.
+        for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_ENERGY_BUCKETS; stdBucket++) {
+            if (!supportedStandardBuckets[stdBucket]) {
+                mAccumulatedEnergiesMicroJoules[stdBucket] = ENERGY_DATA_UNAVAILABLE;
             }
         }
     }
@@ -90,10 +101,13 @@
      * supported. This certainly does NOT produce an exact clone of the template.
      */
     private MeasuredEnergyStats(MeasuredEnergyStats template) {
+        final int numIndices = template.getNumberOfIndices();
+        mAccumulatedEnergiesMicroJoules = new long[numIndices];
         // Initialize to all zeros where supported, otherwise ENERGY_DATA_UNAVAILABLE.
-        for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
-            if (!template.isEnergyBucketSupported(bucket)) {
-                mAccumulatedEnergiesMicroJoules[bucket] = ENERGY_DATA_UNAVAILABLE;
+        // All custom buckets are, by definition, supported, so their values stay at 0.
+        for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_ENERGY_BUCKETS; stdBucket++) {
+            if (!template.isIndexSupported(stdBucket)) {
+                mAccumulatedEnergiesMicroJoules[stdBucket] = ENERGY_DATA_UNAVAILABLE;
             }
         }
     }
@@ -108,18 +122,22 @@
 
     /**
      * Constructor for creating a temp MeasuredEnergyStats.
-     * See {@link #readSummaryFromParcel(MeasuredEnergyStats, Parcel)}.
+     * See {@link #createAndReadSummaryFromParcel(Parcel, MeasuredEnergyStats)}.
      */
-    private MeasuredEnergyStats() {
+    private MeasuredEnergyStats(int numIndices) {
+        mAccumulatedEnergiesMicroJoules = new long[numIndices];
     }
 
     /** Construct from parcel. */
     public MeasuredEnergyStats(Parcel in) {
+        final int size = in.readInt();
+        mAccumulatedEnergiesMicroJoules = new long[size];
         in.readLongArray(mAccumulatedEnergiesMicroJoules);
     }
 
     /** Write to parcel */
     public void writeToParcel(Parcel out) {
+        out.writeInt(mAccumulatedEnergiesMicroJoules.length);
         out.writeLongArray(mAccumulatedEnergiesMicroJoules);
     }
 
@@ -129,16 +147,18 @@
      * summary parcel was written. Availability has already been correctly set in the constructor.
      * Note: {@link com.android.internal.os.BatteryStatsImpl#VERSION} must be updated if summary
      *       parceling changes.
+     *
+     * Corresponding write performed by {@link #writeSummaryToParcel(Parcel, boolean)}.
      */
     private void readSummaryFromParcel(Parcel in, boolean overwriteAvailability) {
-        final int size = in.readInt();
-        for (int i = 0; i < size; i++) {
-            final int bucket = in.readInt();
+        final int numWrittenEntries = in.readInt();
+        for (int entry = 0; entry < numWrittenEntries; entry++) {
+            final int index = in.readInt();
             final long energyUJ = in.readLong();
             if (overwriteAvailability) {
-                mAccumulatedEnergiesMicroJoules[bucket] = energyUJ;
+                mAccumulatedEnergiesMicroJoules[index] = energyUJ;
             } else {
-                setValueIfSupported(bucket, energyUJ);
+                setValueIfSupported(index, energyUJ);
             }
         }
     }
@@ -146,52 +166,90 @@
     /**
      * Write to summary parcel.
      * Note: Measured subsystem availability may be different when the summary parcel is read.
+     *
+     * Corresponding read performed by {@link #readSummaryFromParcel(Parcel, boolean)}.
      */
     private void writeSummaryToParcel(Parcel out, boolean skipZero) {
-        final int sizePos = out.dataPosition();
+        final int posOfNumWrittenEntries = out.dataPosition();
         out.writeInt(0);
-        int size = 0;
-        // Write only the supported buckets with non-zero energy.
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
-            final long energy = mAccumulatedEnergiesMicroJoules[i];
+        int numWrittenEntries = 0;
+        // Write only the supported buckets (with non-zero energy, if applicable).
+        for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
+            final long energy = mAccumulatedEnergiesMicroJoules[index];
             if (energy < 0) continue;
             if (energy == 0 && skipZero) continue;
 
-            out.writeInt(i);
-            out.writeLong(mAccumulatedEnergiesMicroJoules[i]);
-            size++;
+            out.writeInt(index);
+            out.writeLong(mAccumulatedEnergiesMicroJoules[index]);
+            numWrittenEntries++;
         }
         final int currPos = out.dataPosition();
-        out.setDataPosition(sizePos);
-        out.writeInt(size);
+        out.setDataPosition(posOfNumWrittenEntries);
+        out.writeInt(numWrittenEntries);
         out.setDataPosition(currPos);
     }
 
-    /** Updates the given bucket with the given energy iff accumulate is true. */
-    public void updateBucket(@EnergyBucket int bucket, long energyDeltaUJ, boolean accumulate) {
+    /** Get number of possible buckets, including both standard and custom ones. */
+    private int getNumberOfIndices() {
+        return mAccumulatedEnergiesMicroJoules.length;
+    }
+
+    // TODO: Get rid of the 'accumulate' boolean. It's always true.
+    /** Updates the given standard energy bucket with the given energy if accumulate is true. */
+    public void updateStandardBucket(@StandardEnergyBucket int bucket, long energyDeltaUJ,
+            boolean accumulate) {
+        checkValidStandardBucket(bucket);
+        updateEntry(bucket, energyDeltaUJ, accumulate);
+    }
+
+    /** Updates the given custom energy bucket with the given energy if accumulate is true. */
+    public void updateCustomBucket(int customBucket, long energyDeltaUJ, boolean accumulate) {
+        if (!isValidCustomBucket(customBucket)) {
+            Slog.e(TAG, "Attempted to update invalid custom bucket " + customBucket);
+            return;
+        }
+        final int index = customBucketToIndex(customBucket);
+        updateEntry(index, energyDeltaUJ, accumulate);
+    }
+
+    /** Updates the given index with the given energy if accumulate is true. */
+    private void updateEntry(int index, long energyDeltaUJ, boolean accumulate) {
         if (accumulate) {
-            if (mAccumulatedEnergiesMicroJoules[bucket] >= 0L) {
-                mAccumulatedEnergiesMicroJoules[bucket] += energyDeltaUJ;
+            if (mAccumulatedEnergiesMicroJoules[index] >= 0L) {
+                mAccumulatedEnergiesMicroJoules[index] += energyDeltaUJ;
             } else {
                 Slog.wtf(TAG, "Attempting to add " + energyDeltaUJ + " to unavailable bucket "
-                        + ENERGY_BUCKET_NAMES[bucket] + " whose value was "
-                        + mAccumulatedEnergiesMicroJoules[bucket]);
+                        + getBucketName(index) + " whose value was "
+                        + mAccumulatedEnergiesMicroJoules[index]);
             }
         }
     }
 
     /**
-     * Return accumulated energy (in microjoules) for the given energy bucket since last reset.
-     * Returns {@link BatteryStats#ENERGY_DATA_UNAVAILABLE} if this energy data is unavailable.
+     * Return accumulated energy (in microjoules) for a standard energy bucket since last reset.
+     * Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
+     * @throws IllegalArgumentException if no such {@link StandardEnergyBucket}.
      */
-    public long getAccumulatedBucketEnergy(@EnergyBucket int bucket) {
+    public long getAccumulatedStandardBucketEnergy(@StandardEnergyBucket int bucket) {
+        checkValidStandardBucket(bucket);
         return mAccumulatedEnergiesMicroJoules[bucket];
     }
 
     /**
-     * Map {@link MeasuredEnergySubsystem} and device state to a Display {@link EnergyBucket}.
+     * Return accumulated energy (in microjoules) for the a custom energy bucket since last reset.
+     * Returns {@link android.os.BatteryStats#ENERGY_DATA_UNAVAILABLE} if this data is unavailable.
      */
-    public static @EnergyBucket int getDisplayEnergyBucket(int screenState) {
+    public long getAccumulatedCustomBucketEnergy(int customBucket) {
+        if (!isValidCustomBucket(customBucket)) {
+            return ENERGY_DATA_UNAVAILABLE;
+        }
+        return mAccumulatedEnergiesMicroJoules[customBucketToIndex(customBucket)];
+    }
+
+    /**
+     * Map {@link MeasuredEnergySubsystem} and device state to Display {@link StandardEnergyBucket}.
+     */
+    public static @StandardEnergyBucket int getDisplayEnergyBucket(int screenState) {
         if (Display.isOnState(screenState)) {
             return ENERGY_BUCKET_SCREEN_ON;
         }
@@ -204,15 +262,20 @@
     /**
      * Create a MeasuredEnergyStats object from a summary parcel.
      *
+     * Corresponding write performed by
+     * {@link #writeSummaryToParcel(MeasuredEnergyStats, Parcel, boolean)}.
+     *
      * @return a new MeasuredEnergyStats object as described.
      *         Returns null if the parcel indicates there is no data to populate.
      */
     public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(Parcel in) {
+        final int arraySize = in.readInt();
         // Check if any MeasuredEnergyStats exists on the parcel
-        if (in.readInt() == 0) return null;
+        if (arraySize == 0) return null;
 
-        final MeasuredEnergyStats stats =
-                new MeasuredEnergyStats(new boolean[NUMBER_ENERGY_BUCKETS]);
+        final int numCustomBuckets = arraySize - NUMBER_STANDARD_ENERGY_BUCKETS;
+        final MeasuredEnergyStats stats = new MeasuredEnergyStats(
+                new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], numCustomBuckets);
         stats.readSummaryFromParcel(in, true);
         return stats;
     }
@@ -221,6 +284,12 @@
      * Create a MeasuredEnergyStats using the template to determine which buckets are supported,
      * and populate this new object from the given parcel.
      *
+     * The parcel must be consistent with the template in terms of the number of
+     * possible (not necessarily supported) standard and custom buckets.
+     *
+     * Corresponding write performed by
+     * {@link #writeSummaryToParcel(MeasuredEnergyStats, Parcel, boolean)}.
+     *
      * @return a new MeasuredEnergyStats object as described.
      *         Returns null if the stats contain no non-0 information (such as if template is null
      *         or if the parcel indicates there is no data to populate).
@@ -229,12 +298,22 @@
      */
     public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(Parcel in,
             @Nullable MeasuredEnergyStats template) {
+        final int arraySize = in.readInt();
         // Check if any MeasuredEnergyStats exists on the parcel
-        if (in.readInt() == 0) return null;
+        if (arraySize == 0) return null;
 
         if (template == null) {
-            // Nothing supported now. Create placeholder object just to consume the parcel data.
-            final MeasuredEnergyStats mes = new MeasuredEnergyStats();
+            // Nothing supported anymore. Create placeholder object just to consume the parcel data.
+            final MeasuredEnergyStats mes = new MeasuredEnergyStats(arraySize);
+            mes.readSummaryFromParcel(in, false);
+            return null;
+        }
+
+        if (arraySize != template.getNumberOfIndices()) {
+            Slog.wtf(TAG, "Size of MeasuredEnergyStats parcel (" + arraySize
+                    + ") does not match template (" + template.getNumberOfIndices() + ").");
+            // Something is horribly wrong. Just consume the parcel and return null.
+            final MeasuredEnergyStats mes = new MeasuredEnergyStats(arraySize);
             mes.readSummaryFromParcel(in, false);
             return null;
         }
@@ -251,14 +330,17 @@
 
     /** Returns true iff any of the buckets are supported and non-zero. */
     private boolean containsInterestingData() {
-        for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
-            if (mAccumulatedEnergiesMicroJoules[bucket] > 0) return true;
+        for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
+            if (mAccumulatedEnergiesMicroJoules[index] > 0) return true;
         }
         return false;
     }
 
     /**
      * Write a MeasuredEnergyStats to a parcel. If the stats is null, just write a 0.
+     *
+     * Corresponding read performed by {@link #createAndReadSummaryFromParcel(Parcel)}
+     * and {@link #createAndReadSummaryFromParcel(Parcel, MeasuredEnergyStats)}.
      */
     public static void writeSummaryToParcel(@Nullable MeasuredEnergyStats stats,
             Parcel dest, boolean skipZero) {
@@ -266,14 +348,15 @@
             dest.writeInt(0);
             return;
         }
-        dest.writeInt(1);
+        dest.writeInt(stats.getNumberOfIndices());
         stats.writeSummaryToParcel(dest, skipZero);
     }
 
     /** Reset accumulated energy. */
     private void reset() {
-        for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
-            setValueIfSupported(bucket, 0L);
+        final int numIndices = getNumberOfIndices();
+        for (int index = 0; index < numIndices; index++) {
+            setValueIfSupported(index, 0L);
         }
     }
 
@@ -282,33 +365,93 @@
         if (stats != null) stats.reset();
     }
 
-    /** If the bucket is AVAILABLE, overwrite its value; otherwise leave it as UNAVAILABLE. */
-    private void setValueIfSupported(@EnergyBucket int bucket, long value) {
-        if (mAccumulatedEnergiesMicroJoules[bucket] != ENERGY_DATA_UNAVAILABLE) {
-            mAccumulatedEnergiesMicroJoules[bucket] = value;
+    /** If the index is AVAILABLE, overwrite its value; otherwise leave it as UNAVAILABLE. */
+    private void setValueIfSupported(int index, long value) {
+        if (mAccumulatedEnergiesMicroJoules[index] != ENERGY_DATA_UNAVAILABLE) {
+            mAccumulatedEnergiesMicroJoules[index] = value;
         }
     }
 
-    /** Check if measuring the energy of the given bucket is supported by this device. */
-    public boolean isEnergyBucketSupported(@EnergyBucket int bucket) {
-        return mAccumulatedEnergiesMicroJoules[bucket] != ENERGY_DATA_UNAVAILABLE;
+    /**
+     * Check if measuring the energy of the given bucket is supported by this device.
+     * @throws IllegalArgumentException if not a valid {@link StandardEnergyBucket}.
+     */
+    public boolean isStandardBucketSupported(@StandardEnergyBucket int bucket) {
+        checkValidStandardBucket(bucket);
+        return isIndexSupported(bucket);
+    }
+
+    private boolean isIndexSupported(int index) {
+        return mAccumulatedEnergiesMicroJoules[index] != ENERGY_DATA_UNAVAILABLE;
+    }
+
+    /** Check if the supported energy buckets are precisely those given. */
+    public boolean isSupportEqualTo(
+            @NonNull boolean[] queriedStandardBuckets, int numCustomBuckets) {
+
+        final int numBuckets = getNumberOfIndices();
+        // TODO(b/178504428): Detect whether custom buckets have changed qualitatively, not just
+        //                    quantitatively, and treat as mismatch if so.
+        if (numBuckets != NUMBER_STANDARD_ENERGY_BUCKETS + numCustomBuckets) {
+            return false;
+        }
+        for (int stdBucket = 0; stdBucket < NUMBER_STANDARD_ENERGY_BUCKETS; stdBucket++) {
+            if (isStandardBucketSupported(stdBucket) != queriedStandardBuckets[stdBucket]) {
+                return false;
+            }
+        }
+        return true;
     }
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
         pw.println("Accumulated energy since last reset (microjoules):");
         pw.print("   ");
-        for (int bucket = 0; bucket < NUMBER_ENERGY_BUCKETS; bucket++) {
-            pw.print(ENERGY_BUCKET_NAMES[bucket]);
+        for (int index = 0; index < mAccumulatedEnergiesMicroJoules.length; index++) {
+            pw.print(getBucketName(index));
             pw.print(" : ");
-            pw.print(mAccumulatedEnergiesMicroJoules[bucket]);
-            if (!isEnergyBucketSupported(bucket)) {
+            pw.print(mAccumulatedEnergiesMicroJoules[index]);
+            if (!isIndexSupported(index)) {
                 pw.print(" (unsupported)");
             }
-            if (bucket != NUMBER_ENERGY_BUCKETS - 1) {
+            if (index != mAccumulatedEnergiesMicroJoules.length - 1) {
                 pw.print(", ");
             }
         }
         pw.println();
     }
+
+    /**
+     * If the index is a standard bucket, returns its name; otherwise returns its prefixed custom
+     * bucket number.
+     */
+    private static String getBucketName(int index) {
+        if (isValidStandardBucket(index)) {
+            return DebugUtils.valueToString(MeasuredEnergyStats.class, "ENERGY_BUCKET_", index);
+        }
+        return "CUSTOM_" + indexToCustomBucket(index);
+    }
+
+    private static int customBucketToIndex(int customBucket) {
+        return customBucket + NUMBER_STANDARD_ENERGY_BUCKETS;
+    }
+
+    private static int indexToCustomBucket(int index) {
+        return index - NUMBER_STANDARD_ENERGY_BUCKETS;
+    }
+
+    private static void checkValidStandardBucket(@StandardEnergyBucket int bucket) {
+        if (!isValidStandardBucket(bucket)) {
+            throw new IllegalArgumentException("Illegal StandardEnergyBucket " + bucket);
+        }
+    }
+
+    private static boolean isValidStandardBucket(@StandardEnergyBucket int bucket) {
+        return bucket >= 0 && bucket < NUMBER_STANDARD_ENERGY_BUCKETS;
+    }
+
+    private boolean isValidCustomBucket(int customBucket) {
+        return customBucket >= 0
+                && customBucketToIndex(customBucket) < mAccumulatedEnergiesMicroJoules.length;
+    }
 }
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 3cf00ae..c6fd6ee 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -35,6 +35,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.IntFunction;
 
 /**
@@ -599,6 +600,20 @@
         return cur;
     }
 
+    /**
+     * Similar to {@link Set#addAll(Collection)}}, but with support for set values of {@code null}.
+     */
+    public static @NonNull <T> ArraySet<T> addAll(@Nullable ArraySet<T> cur,
+            @Nullable Collection<T> val) {
+        if (cur == null) {
+            cur = new ArraySet<>();
+        }
+        if (val != null) {
+            cur.addAll(val);
+        }
+        return cur;
+    }
+
     public static @Nullable <T> ArraySet<T> remove(@Nullable ArraySet<T> cur, T val) {
         if (cur == null) {
             return null;
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 1e9801f..f42f468 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -14,6 +14,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.IntDef;
 import android.content.Context;
 import android.os.Build;
 import android.os.SystemClock;
@@ -23,9 +24,12 @@
 import android.util.Log;
 import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.EventLogTags;
 import com.android.internal.os.BackgroundThread;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.ThreadLocalRandom;
 
 /**
@@ -91,6 +95,34 @@
      */
     public static final int ACTION_START_RECENTS_ANIMATION = 8;
 
+    private static final int[] ACTIONS_ALL = {
+        ACTION_EXPAND_PANEL,
+        ACTION_TOGGLE_RECENTS,
+        ACTION_FINGERPRINT_WAKE_AND_UNLOCK,
+        ACTION_CHECK_CREDENTIAL,
+        ACTION_CHECK_CREDENTIAL_UNLOCKED,
+        ACTION_TURN_ON_SCREEN,
+        ACTION_ROTATE_SCREEN,
+        ACTION_FACE_WAKE_AND_UNLOCK,
+        ACTION_START_RECENTS_ANIMATION
+    };
+
+    /** @hide */
+    @IntDef({
+        ACTION_EXPAND_PANEL,
+        ACTION_TOGGLE_RECENTS,
+        ACTION_FINGERPRINT_WAKE_AND_UNLOCK,
+        ACTION_CHECK_CREDENTIAL,
+        ACTION_CHECK_CREDENTIAL_UNLOCKED,
+        ACTION_TURN_ON_SCREEN,
+        ACTION_ROTATE_SCREEN,
+        ACTION_FACE_WAKE_AND_UNLOCK,
+        ACTION_START_RECENTS_ANIMATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Action {
+    }
+
     private static final int[] STATSD_ACTION = new int[]{
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS,
@@ -105,24 +137,27 @@
 
     private static LatencyTracker sLatencyTracker;
 
+    private final Object mLock = new Object();
     private final SparseLongArray mStartRtc = new SparseLongArray();
-    private final Context mContext;
-    private volatile int mSamplingInterval;
-    private volatile boolean mEnabled;
+    @GuardedBy("mLock")
+    private final int[] mTraceThresholdPerAction = new int[ACTIONS_ALL.length];
+    @GuardedBy("mLock")
+    private int mSamplingInterval;
+    @GuardedBy("mLock")
+    private boolean mEnabled;
 
     public static LatencyTracker getInstance(Context context) {
         if (sLatencyTracker == null) {
             synchronized (LatencyTracker.class) {
                 if (sLatencyTracker == null) {
-                    sLatencyTracker = new LatencyTracker(context);
+                    sLatencyTracker = new LatencyTracker();
                 }
             }
         }
         return sLatencyTracker;
     }
 
-    public LatencyTracker(Context context) {
-        mContext = context;
+    private LatencyTracker() {
         mEnabled = DEFAULT_ENABLED;
         mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
 
@@ -134,20 +169,26 @@
     }
 
     private void updateProperties(DeviceConfig.Properties properties) {
-        mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
-                DEFAULT_SAMPLING_INTERVAL);
-        mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
+        synchronized (mLock) {
+            mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
+                    DEFAULT_SAMPLING_INTERVAL);
+            mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
+            for (int action : ACTIONS_ALL) {
+                mTraceThresholdPerAction[action] =
+                    properties.getInt(getTraceTriggerNameForAction(action), -1);
+            }
+        }
     }
 
     /**
      * A helper method to translate action type to name.
      *
-     * @param action the action type defined in AtomsProto.java
+     * @param atomsProtoAction the action type defined in AtomsProto.java
      * @return the name of the action
      */
-    public static String getNameOfAction(int action) {
+    public static String getNameOfAction(int atomsProtoAction) {
         // Defined in AtomsProto.java
-        switch (action) {
+        switch (atomsProtoAction) {
             case 0:
                 return "UNKNOWN";
             case 1:
@@ -173,8 +214,12 @@
         }
     }
 
-    private String getTraceNameOfAcion(int action) {
-        return "L<" + getNameOfAction(action) + ">";
+    private static String getTraceNameOfAction(@Action int action) {
+        return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">";
+    }
+
+    private static String getTraceTriggerNameForAction(@Action int action) {
+        return "latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
     }
 
     public static boolean isEnabled(Context ctx) {
@@ -182,7 +227,9 @@
     }
 
     public boolean isEnabled() {
-        return mEnabled;
+        synchronized (mLock) {
+            return mEnabled;
+        }
     }
 
     /**
@@ -190,11 +237,11 @@
      *
      * @param action The action to start. One of the ACTION_* values.
      */
-    public void onActionStart(int action) {
+    public void onActionStart(@Action int action) {
         if (!isEnabled()) {
             return;
         }
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, getTraceNameOfAcion(action), 0);
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0);
         mStartRtc.put(action, SystemClock.elapsedRealtime());
     }
 
@@ -203,7 +250,7 @@
      *
      * @param action The action to end. One of the ACTION_* values.
      */
-    public void onActionEnd(int action) {
+    public void onActionEnd(@Action int action) {
         if (!isEnabled()) {
             return;
         }
@@ -213,7 +260,7 @@
             return;
         }
         mStartRtc.delete(action);
-        Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, getTraceNameOfAcion(action), 0);
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0);
         logAction(action, (int) (endRtc - startRtc));
     }
 
@@ -223,20 +270,31 @@
      * @param action   The action to end. One of the ACTION_* values.
      * @param duration The duration of the action in ms.
      */
-    public void logAction(int action, int duration) {
-        boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+    public void logAction(@Action int action, int duration) {
+        boolean shouldSample;
+        int traceThreshold;
+        synchronized (mLock) {
+            shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0;
+            traceThreshold = mTraceThresholdPerAction[action];
+        }
+
+        if (traceThreshold > 0 && duration >= traceThreshold) {
+            PerfettoTrigger.trigger(getTraceTriggerNameForAction(action));
+        }
+
         logActionDeprecated(action, duration, shouldSample);
     }
 
     /**
      * Logs an action that has started and ended. This needs to be called from the main thread.
      *
-     * @param action          The action to end. One of the ACTION_* values.
-     * @param duration        The duration of the action in ms.
+     * @param action The action to end. One of the ACTION_* values.
+     * @param duration The duration of the action in ms.
      * @param writeToStatsLog Whether to write the measured latency to FrameworkStatsLog.
      */
-    public static void logActionDeprecated(int action, int duration, boolean writeToStatsLog) {
-        Log.i(TAG, "action=" + action + " latency=" + duration);
+    public static void logActionDeprecated(
+            @Action int action, int duration, boolean writeToStatsLog) {
+        Log.i(TAG, getNameOfAction(STATSD_ACTION[action]) + " latency=" + duration);
         EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration);
 
         if (writeToStatsLog) {
diff --git a/core/java/com/android/internal/util/Parcelling.java b/core/java/com/android/internal/util/Parcelling.java
index dd64c40..1ab316d 100644
--- a/core/java/com/android/internal/util/Parcelling.java
+++ b/core/java/com/android/internal/util/Parcelling.java
@@ -27,6 +27,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.regex.Pattern;
 
 /**
@@ -291,5 +292,19 @@
                 return s == null ? null : Pattern.compile(s);
             }
         }
+
+        class ForUUID implements Parcelling<UUID> {
+
+            @Override
+            public void parcel(UUID item, Parcel dest, int parcelFlags) {
+                dest.writeString(item == null ? null : item.toString());
+            }
+
+            @Override
+            public UUID unparcel(Parcel source) {
+                String string = source.readString();
+                return string == null ? null : UUID.fromString(string);
+            }
+        }
     }
 }
diff --git a/core/java/com/android/internal/util/PerfettoTrigger.java b/core/java/com/android/internal/util/PerfettoTrigger.java
new file mode 100644
index 0000000..9c87c69
--- /dev/null
+++ b/core/java/com/android/internal/util/PerfettoTrigger.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.internal.util;
+
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * A trigger implementation with perfetto backend.
+ * @hide
+ */
+public class PerfettoTrigger {
+    private static final String TAG = "PerfettoTrigger";
+    private static final String TRIGGER_COMMAND = "/system/bin/trigger_perfetto";
+
+    /**
+     * @param triggerName The name of the trigger. Must match the value defined in the AOT
+     *                    Perfetto config.
+     */
+    public static void trigger(String triggerName) {
+        try {
+            ProcessBuilder pb = new ProcessBuilder(TRIGGER_COMMAND, triggerName);
+            Log.v(TAG, "Triggering " + String.join(" ", pb.command()));
+            Process process = pb.start();
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to trigger " + triggerName, e);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index e7b7bf4..19506a3 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -79,7 +79,7 @@
     private static final int DO_CLOSE_CONNECTION = 150;
     private static final int DO_COMMIT_CONTENT = 160;
     private static final int DO_GET_SURROUNDING_TEXT = 41;
-    private static final int DO_SET_IME_TEMPORARILY_CONSUMES_INPUT = 170;
+    private static final int DO_SET_IME_CONSUMES_INPUT = 170;
 
 
     @GuardedBy("mLock")
@@ -268,13 +268,12 @@
     }
 
     /**
-     * Dispatches the request for setting ime temporarily consumes input.
+     * Dispatches the request for setting ime consumes input.
      *
-     * <p>See {@link InputConnection#setImeTemporarilyConsumesInput(boolean)}.
+     * <p>See {@link InputConnection#setImeConsumesInput(boolean)}.
      */
-    public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
-        dispatchMessage(obtainMessageB(DO_SET_IME_TEMPORARILY_CONSUMES_INPUT,
-                imeTemporarilyConsumesInput));
+    public void setImeConsumesInput(boolean imeConsumesInput) {
+        dispatchMessage(obtainMessageB(DO_SET_IME_CONSUMES_INPUT, imeConsumesInput));
     }
 
     void dispatchMessage(Message msg) {
@@ -822,17 +821,17 @@
                 }
                 return;
             }
-            case DO_SET_IME_TEMPORARILY_CONSUMES_INPUT: {
+            case DO_SET_IME_CONSUMES_INPUT: {
                 Trace.traceBegin(Trace.TRACE_TAG_INPUT,
-                        "InputConnection#setImeTemporarilyConsumesInput");
+                        "InputConnection#setImeConsumesInput");
                 try {
                     InputConnection ic = getInputConnection();
                     if (ic == null || !isActive()) {
                         Log.w(TAG,
-                                "setImeTemporarilyConsumesInput on inactive InputConnection");
+                                "setImeConsumesInput on inactive InputConnection");
                         return;
                     }
-                    ic.setImeTemporarilyConsumesInput(msg.arg1 == 1);
+                    ic.setImeConsumesInput(msg.arg1 == 1);
                 } finally {
                     Trace.traceEnd(Trace.TRACE_TAG_INPUT);
                 }
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index 586404c..b06b4e5 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -86,5 +86,5 @@
     void getSurroundingText(int beforeLength, int afterLength, int flags,
             ISurroundingTextResultCallback callback);
 
-    void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput);
+    void setImeConsumesInput(boolean imeConsumesInput);
 }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index b42404f..50bbfc5 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -41,22 +41,24 @@
             int untrustedDisplayId);
 
     // TODO: Use ParceledListSlice instead
-    void getInputMethodList(int userId, in IInputMethodInfoListResultCallback resultCallback);
-    // TODO: Use ParceledListSlice instead
-    void getEnabledInputMethodList(int userId,
+    oneway void getInputMethodList(int userId,
             in IInputMethodInfoListResultCallback resultCallback);
-    void getEnabledInputMethodSubtypeList(in String imiId, boolean allowsImplicitlySelectedSubtypes,
+    // TODO: Use ParceledListSlice instead
+    oneway void getEnabledInputMethodList(int userId,
+            in IInputMethodInfoListResultCallback resultCallback);
+    oneway void getEnabledInputMethodSubtypeList(in String imiId,
+            boolean allowsImplicitlySelectedSubtypes,
             in IInputMethodSubtypeListResultCallback resultCallback);
-    void getLastInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback);
+    oneway void getLastInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback);
 
-    void showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
+    oneway void showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
             in ResultReceiver resultReceiver, in IBooleanResultCallback resultCallback);
-    void hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
+    oneway void hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
             in ResultReceiver resultReceiver, in IBooleanResultCallback resultCallback);
     // If windowToken is null, this just does startInput().  Otherwise this reports that a window
     // has gained focus, and if 'attribute' is non-null then also does startInput.
     // @NonNull
-    void startInputOrWindowGainedFocus(
+    oneway void startInputOrWindowGainedFocus(
             /* @StartInputReason */ int startInputReason,
             in IInputMethodClient client, in IBinder windowToken,
             /* @StartInputFlags */ int startInputFlags,
@@ -66,32 +68,35 @@
             int unverifiedTargetSdkVersion,
             in IInputBindResultResultCallback inputBindResult);
 
-    void showInputMethodPickerFromClient(in IInputMethodClient client,
+    oneway void showInputMethodPickerFromClient(in IInputMethodClient client,
             int auxiliarySubtypeMode, in IVoidResultCallback resultCallback);
-    void showInputMethodPickerFromSystem(in IInputMethodClient client, int auxiliarySubtypeMode,
-            int displayId, in IVoidResultCallback resultCallback);
-    void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId,
+    oneway void showInputMethodPickerFromSystem(in IInputMethodClient client,
+            int auxiliarySubtypeMode, int displayId, in IVoidResultCallback resultCallback);
+    oneway void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client,
+            String topId, in IVoidResultCallback resultCallback);
+    oneway void isInputMethodPickerShownForTest(in IBooleanResultCallback resultCallback);
+    oneway void getCurrentInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback);
+    oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes,
             in IVoidResultCallback resultCallback);
-    void isInputMethodPickerShownForTest(in IBooleanResultCallback resultCallback);
-    void getCurrentInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback);
-    void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
     // This is kept due to @UnsupportedAppUsage.
     // TODO(Bug 113914148): Consider removing this.
-    void getInputMethodWindowVisibleHeight(IIntResultCallback resultCallback);
+    oneway void getInputMethodWindowVisibleHeight(IIntResultCallback resultCallback);
 
-    void reportActivityView(in IInputMethodClient parentClient, int childDisplayId,
+    oneway void reportActivityView(in IInputMethodClient parentClient, int childDisplayId,
             in float[] matrixValues, in IVoidResultCallback resultCallback);
 
     oneway void reportPerceptible(in IBinder windowToken, boolean perceptible);
     /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
-    void removeImeSurface();
+    oneway void removeImeSurface(in IVoidResultCallback resultCallback);
     /** Remove the IME surface. Requires passing the currently focused window. */
-    void removeImeSurfaceFromWindow(in IBinder windowToken);
-    void startProtoDump(in byte[] protoDump, int source, String where);
-    void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
+    oneway void removeImeSurfaceFromWindow(in IBinder windowToken,
+            in IVoidResultCallback resultCallback);
+    oneway void startProtoDump(in byte[] protoDump, int source, String where,
+            in IVoidResultCallback resultCallback);
+    oneway void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
 
     // Starts an ime trace.
-    void startImeTrace();
+    oneway void startImeTrace(in IVoidResultCallback resultCallback);
     // Stops an ime trace.
-    void stopImeTrace();
+    oneway void stopImeTrace(in IVoidResultCallback resultCallback);
 }
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 84c92ca..0e9d135 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -525,12 +525,12 @@
     }
 
     /**
-     * See {@link InputConnection#setImeTemporarilyConsumesInput(boolean)}.
+     * See {@link InputConnection#setImeConsumesInput(boolean)}.
      */
     @AnyThread
-    public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
+    public boolean setImeConsumesInput(boolean imeConsumesInput) {
         try {
-            mIInputContext.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput);
+            mIInputContext.setImeConsumesInput(imeConsumesInput);
             return true;
         } catch (RemoteException e) {
             return false;
diff --git a/core/java/com/android/internal/view/ScrollCaptureInternal.java b/core/java/com/android/internal/view/ScrollCaptureInternal.java
index ae1a815..4b9a160 100644
--- a/core/java/com/android/internal/view/ScrollCaptureInternal.java
+++ b/core/java/com/android/internal/view/ScrollCaptureInternal.java
@@ -59,6 +59,11 @@
     public static final int TYPE_RECYCLING = 2;
 
     /**
+     * The ViewGroup scrolls, but has no child views in
+     */
+    private static final int TYPE_OPAQUE = 3;
+
+    /**
      * Performs tests on the given View and determines:
      * 1. If scrolling is possible
      * 2. What mechanisms are used for scrolling.
@@ -95,8 +100,15 @@
             }
             return TYPE_RECYCLING;
         }
+        // At least one child view is required.
+        if (((ViewGroup) view).getChildCount() < 1) {
+            if (DEBUG_VERBOSE) {
+                Log.v(TAG, "scrollable with no children");
+            }
+            return TYPE_OPAQUE;
+        }
         if (DEBUG_VERBOSE) {
-            Log.v(TAG, "hint: less than two child views");
+            Log.v(TAG, "hint: single child view");
         }
         //Because recycling containers don't use scrollY, a non-zero value means Scroll view.
         if (view.getScrollY() != 0) {
diff --git a/core/java/com/android/internal/widget/DisableImageView.java b/core/java/com/android/internal/widget/DisableImageView.java
new file mode 100644
index 0000000..0d9bf71
--- /dev/null
+++ b/core/java/com/android/internal/widget/DisableImageView.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+/**
+ * ImageView which applies a saturation image filter, used by AppWidgets to represent disabled icon
+ */
+@RemoteViews.RemoteView
+public class DisableImageView extends ImageView {
+
+    public DisableImageView(Context context) {
+        this(context, null, 0, 0);
+    }
+
+    public DisableImageView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0, 0);
+    }
+
+    public DisableImageView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public DisableImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        // Apply the disabled filter
+        ColorMatrix brightnessMatrix = new ColorMatrix();
+        float brightnessF = 0.5f;
+        int brightnessI = (int) (255 * brightnessF);
+        // Brightness: C-new = C-old*(1-amount) + amount
+        float scale = 1f - brightnessF;
+        float[] mat = brightnessMatrix.getArray();
+        mat[0] = scale;
+        mat[6] = scale;
+        mat[12] = scale;
+        mat[4] = brightnessI;
+        mat[9] = brightnessI;
+        mat[14] = brightnessI;
+
+        ColorMatrix filterMatrix = new ColorMatrix();
+        filterMatrix.setSaturation(0);
+        filterMatrix.preConcat(brightnessMatrix);
+        setColorFilter(new ColorMatrixColorFilter(filterMatrix));
+    }
+}
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 4ccf9ce..3d054a5 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -245,11 +245,11 @@
     }
 
     @Override
-    public boolean setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
+    public boolean setImeConsumesInput(boolean imeConsumesInput) {
         if (mTextView == null) {
-            return super.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput);
+            return super.setImeConsumesInput(imeConsumesInput);
         }
-        mTextView.setImeTemporarilyConsumesInput(imeTemporarilyConsumesInput);
+        mTextView.setImeConsumesInput(imeConsumesInput);
         return true;
     }
 
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index bad21d2..73c5460 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -148,6 +148,7 @@
     private int mRegularColor;
     private int mErrorColor;
     private int mSuccessColor;
+    private int mDotColor;
 
     private final Interpolator mFastOutSlowInInterpolator;
     private final Interpolator mLinearOutSlowInInterpolator;
@@ -318,6 +319,7 @@
         mRegularColor = a.getColor(R.styleable.LockPatternView_regularColor, 0);
         mErrorColor = a.getColor(R.styleable.LockPatternView_errorColor, 0);
         mSuccessColor = a.getColor(R.styleable.LockPatternView_successColor, 0);
+        mDotColor = a.getColor(R.styleable.LockPatternView_dotColor, mRegularColor);
 
         int pathColor = a.getColor(R.styleable.LockPatternView_pathColor, mRegularColor);
         mPathPaint.setColor(pathColor);
@@ -522,7 +524,7 @@
                 getCenterYForRow(cellState.row) + startTranslationY);
         cellState.hwCenterX = CanvasProperty.createFloat(getCenterXForColumn(cellState.col));
         cellState.hwRadius = CanvasProperty.createFloat(mDotSize/2 * startScale);
-        mPaint.setColor(getCurrentColor(false));
+        mPaint.setColor(getDotColor());
         mPaint.setAlpha((int) (startAlpha * 255));
         cellState.hwPaint = CanvasProperty.createPaint(new Paint(mPaint));
 
@@ -1163,29 +1165,6 @@
         final Path currentPath = mCurrentPath;
         currentPath.rewind();
 
-        // draw the circles
-        for (int i = 0; i < 3; i++) {
-            float centerY = getCenterYForRow(i);
-            for (int j = 0; j < 3; j++) {
-                CellState cellState = mCellStates[i][j];
-                float centerX = getCenterXForColumn(j);
-                float translationY = cellState.translationY;
-
-                if (mUseLockPatternDrawable) {
-                    drawCellDrawable(canvas, i, j, cellState.radius, drawLookup[i][j]);
-                } else {
-                    if (isHardwareAccelerated() && cellState.hwAnimating) {
-                        RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
-                        recordingCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,
-                                cellState.hwRadius, cellState.hwPaint);
-                    } else {
-                        drawCircle(canvas, (int) centerX, (int) centerY + translationY,
-                                cellState.radius, drawLookup[i][j], cellState.alpha);
-                    }
-                }
-            }
-        }
-
         // TODO: the path should be created and cached every time we hit-detect a cell
         // only the last segment of the path should be computed here
         // draw the path of the pattern (unless we are in stealth mode)
@@ -1256,6 +1235,29 @@
                 canvas.drawPath(currentPath, mPathPaint);
             }
         }
+
+        // draw the circles
+        for (int i = 0; i < 3; i++) {
+            float centerY = getCenterYForRow(i);
+            for (int j = 0; j < 3; j++) {
+                CellState cellState = mCellStates[i][j];
+                float centerX = getCenterXForColumn(j);
+                float translationY = cellState.translationY;
+
+                if (mUseLockPatternDrawable) {
+                    drawCellDrawable(canvas, i, j, cellState.radius, drawLookup[i][j]);
+                } else {
+                    if (isHardwareAccelerated() && cellState.hwAnimating) {
+                        RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
+                        recordingCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,
+                                cellState.hwRadius, cellState.hwPaint);
+                    } else {
+                        drawCircle(canvas, (int) centerX, (int) centerY + translationY,
+                                cellState.radius, drawLookup[i][j], cellState.alpha);
+                    }
+                }
+            }
+        }
     }
 
     private float calculateLastSegmentAlpha(float x, float y, float lastX, float lastY) {
@@ -1266,6 +1268,17 @@
         return Math.min(1f, Math.max(0f, (frac - 0.3f) * 4f));
     }
 
+    private int getDotColor() {
+        if (mInStealthMode) {
+            // Always use the default color in this case
+            return mDotColor;
+        } else if (mPatternDisplayMode == DisplayMode.Wrong) {
+            // the pattern is wrong
+            return mErrorColor;
+        }
+        return mDotColor;
+    }
+
     private int getCurrentColor(boolean partOfPattern) {
         if (!partOfPattern || mInStealthMode || mPatternInProgress) {
             // unselected circle
@@ -1286,7 +1299,7 @@
      */
     private void drawCircle(Canvas canvas, float centerX, float centerY, float radius,
             boolean partOfPattern, float alpha) {
-        mPaint.setColor(getCurrentColor(partOfPattern));
+        mPaint.setColor(getDotColor());
         mPaint.setAlpha((int) (alpha * 255));
         canvas.drawCircle(centerX, centerY, radius, mPaint);
     }
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index ae566c3..d284d51 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -5,3 +5,17 @@
 per-file *LockScreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
 per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS
 per-file *LockSettings* = file:/services/core/java/com/android/server/locksettings/OWNERS
+
+# Notification related
+per-file *Notification* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *Messaging* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *Message* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *Conversation* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *People* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *ImageResolver* = file:/services/core/java/com/android/server/notification/OWNERS
+per-file CallLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file CachingIconView.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file ImageFloatingTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file ObservableTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file RemeasuringLinearLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file ViewClippingUtil.java = file:/services/core/java/com/android/server/notification/OWNERS
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index b4d8e50..95999a7 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -23,7 +23,6 @@
 import android.os.Build;
 import android.os.DropBoxManager;
 import android.os.Environment;
-import android.os.FileObserver;
 import android.os.FileUtils;
 import android.os.RecoverySystem;
 import android.os.RemoteException;
@@ -74,7 +73,6 @@
         SystemProperties.getInt("ro.debuggable", 0) == 1 ? 196608 : 65536;
     private static final int GMSCORE_LASTK_LOG_SIZE = 196608;
 
-    private static final File TOMBSTONE_DIR = new File("/data/tombstones");
     private static final String TAG_TOMBSTONE = "SYSTEM_TOMBSTONE";
 
     // The pre-froyo package and class of the system updater, which
@@ -85,9 +83,6 @@
     private static final String OLD_UPDATER_CLASS =
         "com.google.android.systemupdater.SystemUpdateReceiver";
 
-    // Keep a reference to the observer so the finalizer doesn't disable it.
-    private static FileObserver sTombstoneObserver = null;
-
     private static final String LOG_FILES_FILE = "log-files.xml";
     private static final AtomicFile sFile = new AtomicFile(new File(
             Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files");
@@ -153,7 +148,7 @@
         Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
     }
 
-    private String getPreviousBootHeaders() {
+    private static String getPreviousBootHeaders() {
         try {
             return FileUtils.readTextFile(lastHeaderFile, 0, null);
         } catch (IOException e) {
@@ -161,7 +156,7 @@
         }
     }
 
-    private String getCurrentBootHeaders() throws IOException {
+    private static String getCurrentBootHeaders() throws IOException {
         return new StringBuilder(512)
             .append("Build: ").append(Build.FINGERPRINT).append("\n")
             .append("Hardware: ").append(Build.BOARD).append("\n")
@@ -175,7 +170,7 @@
     }
 
 
-    private String getBootHeadersToLogAndUpdate() throws IOException {
+    private static String getBootHeadersToLogAndUpdate() throws IOException {
         final String oldHeaders = getPreviousBootHeaders();
         final String newHeaders = getCurrentBootHeaders();
 
@@ -247,38 +242,27 @@
         logFsMountTime();
         addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");
         logSystemServerShutdownTimeMetrics();
-
-        // Scan existing tombstones (in case any new ones appeared)
-        File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
-        for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
-            if (tombstoneFiles[i].isFile()) {
-                addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(),
-                        LOG_SIZE, "SYSTEM_TOMBSTONE");
-            }
-        }
-
         writeTimestamps(timestamps);
+    }
 
-        // Start watching for new tombstone files; will record them as they occur.
-        // This gets registered with the singleton file observer thread.
-        sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CREATE) {
-            @Override
-            public void onEvent(int event, String path) {
-                HashMap<String, Long> timestamps = readTimestamps();
-                try {
-                    File file = new File(TOMBSTONE_DIR, path);
-                    if (file.isFile() && file.getName().startsWith("tombstone_")) {
-                        addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE,
-                                TAG_TOMBSTONE);
-                    }
-                } catch (IOException e) {
-                    Slog.e(TAG, "Can't log tombstone", e);
-                }
-                writeTimestamps(timestamps);
-            }
-        };
-
-        sTombstoneObserver.startWatching();
+    /**
+     * Add a tombstone to the DropBox.
+     *
+     * @param ctx Context
+     * @param tombstone path to the tombstone
+     */
+    public static void addTombstoneToDropBox(Context ctx, File tombstone) {
+        final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
+        final String bootReason = SystemProperties.get("ro.boot.bootreason", null);
+        HashMap<String, Long> timestamps = readTimestamps();
+        try {
+            final String headers = getBootHeadersToLogAndUpdate();
+            addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
+                    TAG_TOMBSTONE);
+        } catch (IOException e) {
+            Slog.e(TAG, "Can't log tombstone", e);
+        }
+        writeTimestamps(timestamps);
     }
 
     private static void addLastkToDropBox(
@@ -761,7 +745,7 @@
         }
     }
 
-    private void writeTimestamps(HashMap<String, Long> timestamps) {
+    private static void writeTimestamps(HashMap<String, Long> timestamps) {
         synchronized (sFile) {
             final FileOutputStream stream;
             try {
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index e5bc470..cb586d6 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -178,8 +178,9 @@
     // be delivered anonymously even to apps which target O+.
     final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>();
 
-    // These are the package names of apps which should be in the 'always'
-    // URL-handling state upon factory reset.
+    // These are the package names of apps which should be automatically granted domain verification
+    // for all of their domains. The only way these apps can be overridden by the user is by
+    // explicitly disabling overall link handling support in app info.
     final ArraySet<String> mLinkedApps = new ArraySet<>();
 
     // These are the components that are enabled by default as VR mode listener services.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 0b48e72..8edc8a1 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -207,9 +207,9 @@
             ],
 
             shared_libs: [
-                "audioclient-types-aidl-unstable-cpp",
-                "audioflinger-aidl-unstable-cpp",
-                "av-types-aidl-unstable-cpp",
+                "audioclient-types-aidl-cpp",
+                "audioflinger-aidl-cpp",
+                "av-types-aidl-cpp",
                 "libandroidicu",
                 "libbpf_android",
                 "libnetdbpf",
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 66753e4..f1998a5 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -47,6 +47,7 @@
 static struct assetsprovider_offsets_t {
   jclass classObject;
   jmethodID loadAssetFd;
+  jmethodID toString;
 } gAssetsProviderOffsets;
 
 static struct {
@@ -72,9 +73,22 @@
 class LoaderAssetsProvider : public AssetsProvider {
  public:
   static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
-    return (!assets_provider) ? nullptr
+    return (!assets_provider) ? EmptyAssetsProvider::Create()
                               : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider(
-                                  env->NewGlobalRef(assets_provider)));
+                                    env, assets_provider));
+  }
+
+  bool ForEachFile(const std::string& /* root_path */,
+                   const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+    return true;
+  }
+
+  const std::string& GetDebugName() const override {
+    return debug_name_;
+  }
+
+  bool IsUpToDate() const override {
+    return true;
   }
 
   ~LoaderAssetsProvider() override {
@@ -142,20 +156,26 @@
       *file_exists = true;
     }
 
-    return ApkAssets::CreateAssetFromFd(base::unique_fd(fd),
-                                        nullptr /* path */,
-                                        static_cast<off64_t>(mOffset),
-                                        static_cast<off64_t>(mLength));
+    return AssetsProvider::CreateAssetFromFd(base::unique_fd(fd),
+                                             nullptr /* path */,
+                                             static_cast<off64_t>(mOffset),
+                                             static_cast<off64_t>(mLength));
   }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoaderAssetsProvider);
 
-  explicit LoaderAssetsProvider(jobject assets_provider)
-    : assets_provider_(assets_provider) { }
+  explicit LoaderAssetsProvider(JNIEnv* env, jobject assets_provider) {
+    assets_provider_ = env->NewGlobalRef(assets_provider);
+    auto string_result = static_cast<jstring>(env->CallObjectMethod(
+        assets_provider_, gAssetsProviderOffsets.toString));
+    ScopedUtfChars str(env, string_result);
+    debug_name_ = std::string(str.c_str(), str.size());
+  }
 
   // The global reference to the AssetsProvider
   jobject assets_provider_;
+  std::string debug_name_;
 };
 
 static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
@@ -170,18 +190,26 @@
   auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
   std::unique_ptr<const ApkAssets> apk_assets;
   switch (format) {
-    case FORMAT_APK:
-      apk_assets = ApkAssets::Load(path.c_str(), property_flags, std::move(loader_assets));
+    case FORMAT_APK: {
+      auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
+                                                ZipAssetsProvider::Create(path.c_str()));
+      apk_assets = ApkAssets::Load(std::move(assets), property_flags);
       break;
+    }
     case FORMAT_IDMAP:
       apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
       break;
     case FORMAT_ARSC:
-      apk_assets = ApkAssets::LoadTable(path.c_str(), property_flags, std::move(loader_assets));
+      apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
+                                        std::move(loader_assets),
+                                        property_flags);
       break;
-    case FORMAT_DIRECTORY:
-      apk_assets = ApkAssets::LoadFromDir(path.c_str(), property_flags,  std::move(loader_assets));
+    case FORMAT_DIRECTORY: {
+      auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
+                                                DirectoryAssetsProvider::Create(path.c_str()));
+      apk_assets = ApkAssets::Load(std::move(assets), property_flags);
       break;
+    }
     default:
       const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
       jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -221,13 +249,17 @@
   auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
   std::unique_ptr<const ApkAssets> apk_assets;
   switch (format) {
-    case FORMAT_APK:
-      apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
-                                         property_flags, std::move(loader_assets));
+    case FORMAT_APK: {
+      auto assets = MultiAssetsProvider::Create(
+          std::move(loader_assets),
+          ZipAssetsProvider::Create(std::move(dup_fd), friendly_name_utf8.c_str()));
+      apk_assets = ApkAssets::Load(std::move(assets), property_flags);
       break;
+    }
     case FORMAT_ARSC:
-      apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
-                                              property_flags, std::move(loader_assets));
+      apk_assets = ApkAssets::LoadTable(
+          AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */),
+          std::move(loader_assets), property_flags);
       break;
     default:
       const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
@@ -282,17 +314,20 @@
   auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
   std::unique_ptr<const ApkAssets> apk_assets;
   switch (format) {
-    case FORMAT_APK:
-      apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
-                                         property_flags, std::move(loader_assets),
-                                         static_cast<off64_t>(offset),
-                                         static_cast<off64_t>(length));
+    case FORMAT_APK: {
+      auto assets = MultiAssetsProvider::Create(
+          std::move(loader_assets),
+          ZipAssetsProvider::Create(std::move(dup_fd), friendly_name_utf8.c_str(),
+                                    static_cast<off64_t>(offset), static_cast<off64_t>(length)));
+      apk_assets = ApkAssets::Load(std::move(assets), property_flags);
       break;
+    }
     case FORMAT_ARSC:
-      apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
-                                              property_flags, std::move(loader_assets),
-                                              static_cast<off64_t>(offset),
-                                              static_cast<off64_t>(length));
+      apk_assets = ApkAssets::LoadTable(
+          AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */,
+                                            static_cast<off64_t>(offset),
+                                            static_cast<off64_t>(length)),
+          std::move(loader_assets), property_flags);
       break;
     default:
       const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
@@ -310,8 +345,7 @@
 }
 
 static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
-  auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
-  auto apk_assets = ApkAssets::LoadEmpty(flags, std::move(loader_assets));
+  auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
   return reinterpret_cast<jlong>(apk_assets.release());
 }
 
@@ -458,6 +492,8 @@
   gAssetsProviderOffsets.loadAssetFd = GetMethodIDOrDie(
       env, gAssetsProviderOffsets.classObject, "loadAssetFd",
       "(Ljava/lang/String;I)Landroid/content/res/AssetFileDescriptor;");
+  gAssetsProviderOffsets.toString = GetMethodIDOrDie(
+      env, gAssetsProviderOffsets.classObject, "toString", "()Ljava/lang/String;");
 
   jclass parcelFd = FindClassOrDie(env, "android/os/ParcelFileDescriptor");
   gParcelFileDescriptorOffsets.detachFd = GetMethodIDOrDie(env, parcelFd, "detachFd", "()I");
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 5f2dcdf..5630a1e 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -41,6 +41,10 @@
 #define ENCODING_OPUS           20
 #define ENCODING_PCM_24BIT_PACKED 21
 #define ENCODING_PCM_32BIT 22
+#define ENCODING_MPEGH_BL_L3 23
+#define ENCODING_MPEGH_BL_L4 24
+#define ENCODING_MPEGH_LC_L3 25
+#define ENCODING_MPEGH_LC_L4 26
 
 #define ENCODING_INVALID    0
 #define ENCODING_DEFAULT    1
@@ -98,6 +102,14 @@
         return AUDIO_FORMAT_PCM_24_BIT_PACKED;
     case ENCODING_PCM_32BIT:
         return AUDIO_FORMAT_PCM_32_BIT;
+    case ENCODING_MPEGH_BL_L3:
+        return AUDIO_FORMAT_MPEGH_BL_L3;
+    case ENCODING_MPEGH_BL_L4:
+        return AUDIO_FORMAT_MPEGH_BL_L4;
+    case ENCODING_MPEGH_LC_L3:
+        return AUDIO_FORMAT_MPEGH_LC_L3;
+    case ENCODING_MPEGH_LC_L4:
+        return AUDIO_FORMAT_MPEGH_LC_L4;
     default:
         return AUDIO_FORMAT_INVALID;
     }
@@ -159,6 +171,14 @@
         return ENCODING_DOLBY_MAT;
     case AUDIO_FORMAT_OPUS:
         return ENCODING_OPUS;
+    case AUDIO_FORMAT_MPEGH_BL_L3:
+        return ENCODING_MPEGH_BL_L3;
+    case AUDIO_FORMAT_MPEGH_BL_L4:
+        return ENCODING_MPEGH_BL_L4;
+    case AUDIO_FORMAT_MPEGH_LC_L3:
+        return ENCODING_MPEGH_LC_L3;
+    case AUDIO_FORMAT_MPEGH_LC_L4:
+        return ENCODING_MPEGH_LC_L4;
     case AUDIO_FORMAT_DEFAULT:
         return ENCODING_DEFAULT;
     default:
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 2155246..e2af87e 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include <android/file_descriptor_jni.h>
 #include <arpa/inet.h>
 #include <linux/filter.h>
 #include <linux/if_arp.h>
@@ -83,7 +84,7 @@
         filter_code,
     };
 
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
                 "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
@@ -93,7 +94,7 @@
 static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
     int optval_ignored = 0;
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) !=
         0) {
         jniThrowExceptionFmt(env, "java/net/SocketException",
@@ -117,10 +118,9 @@
     return (jboolean) !setNetworkForResolv(netId);
 }
 
-static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket,
-        jint netId)
-{
-    return setNetworkForSocket(netId, socket);
+static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd,
+                                                  jint netId) {
+    return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd));
 }
 
 static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket)
@@ -128,6 +128,10 @@
     return (jboolean) !protectFromVpn(socket);
 }
 
+static jboolean android_net_utils_protectFromVpnWithFd(JNIEnv *env, jobject thiz, jobject javaFd) {
+    return android_net_utils_protectFromVpn(env, thiz, AFileDescriptor_getFD(env, javaFd));
+}
+
 static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId)
 {
     return (jboolean) !queryUserAccess(uid, netId);
@@ -178,7 +182,7 @@
 }
 
 static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     int rcode;
     std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
 
@@ -205,7 +209,7 @@
 }
 
 static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     resNetworkCancel(fd);
     jniSetFileDescriptorOfFD(env, javaFd, -1);
 }
@@ -231,7 +235,7 @@
         return NULL;
     }
 
-    int fd = jniGetFDFromFileDescriptor(env, javaFd);
+    int fd = AFileDescriptor_getFD(env, javaFd);
     struct tcp_repair_window trw = {};
     socklen_t size = sizeof(trw);
 
@@ -271,8 +275,9 @@
     { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
     { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
-    { "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
-    { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
+    { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
+    { "protectFromVpn", "(I)Z", (void*) android_net_utils_protectFromVpn },
+    { "protectFromVpn", "(Ljava/io/FileDescriptor;)Z", (void*) android_net_utils_protectFromVpnWithFd },
     { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
     { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
     { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 787d348..3acbd1e 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -36,6 +36,7 @@
 #include <utils/List.h>
 #include <utils/KeyedVector.h>
 #include <binder/Parcel.h>
+#include <binder/ParcelRef.h>
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
 #include <utils/threads.h>
@@ -515,8 +516,9 @@
 
 static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
 {
-    Parcel* parcel = new Parcel();
-    return reinterpret_cast<jlong>(parcel);
+    sp<ParcelRef> parcelRef = ParcelRef::create();
+    parcelRef->incStrong(reinterpret_cast<const void*>(android_os_Parcel_create));
+    return reinterpret_cast<jlong>(static_cast<Parcel *>(parcelRef.get()));
 }
 
 static void android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
@@ -529,8 +531,8 @@
 
 static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr)
 {
-    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
-    delete parcel;
+    ParcelRef* derivative = static_cast<ParcelRef*>(reinterpret_cast<Parcel*>(nativePtr));
+    derivative->decStrong(reinterpret_cast<const void*>(android_os_Parcel_create));
 }
 
 static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr)
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 675648a..f7b3f30 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -35,6 +35,7 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/Parcel.h>
+#include <binder/ParcelRef.h>
 #include <binder/ProcessState.h>
 #include <binder/Stability.h>
 #include <binderthreadstate/CallerUtils.h>
@@ -1367,7 +1368,8 @@
 }
 
 static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
-        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
+        jint code, jobject dataObj, jobject replyObj, jboolean replyObjOwnsNativeParcel,
+        jint flags) // throws RemoteException
 {
     if (dataObj == NULL) {
         jniThrowNullPointerException(env, NULL);
@@ -1409,6 +1411,21 @@
     status_t err = target->transact(code, *data, reply, flags);
     //if (reply) printf("Transact from Java code to %p received: ", target); reply->print();
 
+    if (reply) {
+        if (replyObjOwnsNativeParcel) {
+            // as per Parcel java class constructor, here, "reply" MUST be a "ParcelRef"
+            // only for Parcel that contained Binder objects
+            if (reply->objectsCount() > 0) {
+                IPCThreadState::self()->createTransactionReference(static_cast<ParcelRef*>(reply));
+            }
+        } else {
+            // as per Parcel.java, if Parcel java object NOT owning native Parcel object, it will
+            // NOT destroy the native Parcel object upon GC(finalize()), so, there will be no race
+            // condtion in this case. Please refer to the java class methods: Parcel.finalize(),
+            // Parcel.destroy().
+        }
+    }
+
     if (kEnableBinderSample) {
         if (time_binder_calls) {
             conditionally_log_binder_call(start_millis, target, code);
@@ -1535,7 +1552,7 @@
     {"pingBinder",          "()Z", (void*)android_os_BinderProxy_pingBinder},
     {"isBinderAlive",       "()Z", (void*)android_os_BinderProxy_isBinderAlive},
     {"getInterfaceDescriptor", "()Ljava/lang/String;", (void*)android_os_BinderProxy_getInterfaceDescriptor},
-    {"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
+    {"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;ZI)Z", (void*)android_os_BinderProxy_transact},
     {"linkToDeath",         "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
     {"unlinkToDeath",       "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
     {"getNativeFinalizer",  "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 4eaa016..9cc7243 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -70,7 +70,8 @@
                                           deviceInfo.isExternal(), deviceInfo.getSources(),
                                           deviceInfo.getKeyboardType(), kcmObj.get(),
                                           deviceInfo.hasVibrator(), hasMic,
-                                          deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor()));
+                                          deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(),
+                                          deviceInfo.hasBattery()));
 
     const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
     for (const InputDeviceInfo::MotionRange& range: ranges) {
@@ -90,9 +91,10 @@
     gInputDeviceClassInfo.clazz = FindClassOrDie(env, "android/view/InputDevice");
     gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
 
-    gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
-                                                  "(IIILjava/lang/String;IILjava/lang/"
-                                                  "String;ZIILandroid/view/KeyCharacterMap;ZZZZ)V");
+    gInputDeviceClassInfo.ctor =
+            GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
+                             "(IIILjava/lang/String;IILjava/lang/"
+                             "String;ZIILandroid/view/KeyCharacterMap;ZZZZZ)V");
 
     gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
             "addMotionRange", "(IIFFFFF)V");
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0e878fc..8597308 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -27,6 +27,7 @@
 #include <android-base/chrono_utils.h>
 #include <android/graphics/region.h>
 #include <android/gui/BnScreenCaptureListener.h>
+#include <android/os/IInputConstants.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
 #include <android_runtime/android_view_Surface.h>
@@ -110,10 +111,12 @@
 static struct {
     jfieldID pixelFormat;
     jfieldID sourceCrop;
-    jfieldID frameScale;
+    jfieldID frameScaleX;
+    jfieldID frameScaleY;
     jfieldID captureSecureLayers;
     jfieldID allowProtected;
     jfieldID uid;
+    jfieldID grayscale;
 } gCaptureArgsClassInfo;
 
 static struct {
@@ -262,7 +265,7 @@
         }
     }
 
-    binder::Status onScreenCaptureComplete(
+    binder::Status onScreenCaptureCompleted(
             const gui::ScreenCaptureResults& captureResults) override {
         JNIEnv* env = getenv();
         if (captureResults.result != NO_ERROR || captureResults.buffer == nullptr) {
@@ -270,6 +273,7 @@
                                 gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
             return binder::Status::ok();
         }
+        captureResults.fence->waitForever("");
         jobject jhardwareBuffer = android_hardware_HardwareBuffer_createFromAHardwareBuffer(
                 env, captureResults.buffer->toAHardwareBuffer());
         const jint namedColorSpace =
@@ -379,13 +383,17 @@
     captureArgs.sourceCrop =
             rectFromObj(env,
                         env->GetObjectField(captureArgsObject, gCaptureArgsClassInfo.sourceCrop));
-    captureArgs.frameScale =
-            env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScale);
+    captureArgs.frameScaleX =
+            env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX);
+    captureArgs.frameScaleY =
+            env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleY);
     captureArgs.captureSecureLayers =
             env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.captureSecureLayers);
     captureArgs.allowProtected =
             env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.allowProtected);
     captureArgs.uid = env->GetLongField(captureArgsObject, gCaptureArgsClassInfo.uid);
+    captureArgs.grayscale =
+            env->GetBooleanField(captureArgsObject, gCaptureArgsClassInfo.grayscale);
 }
 
 static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
@@ -1401,16 +1409,6 @@
     transaction->deferTransactionUntil_legacy(ctrl, barrier, frameNumber);
 }
 
-static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong transactionObj,
-        jlong nativeObject,
-        jlong newParentObject) {
-
-    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    auto newParent = reinterpret_cast<SurfaceControl *>(newParentObject);
-    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-    transaction->reparentChildren(ctrl, newParent);
-}
-
 static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject,
         jlong newParentObject) {
@@ -1618,7 +1616,8 @@
                                         jlong frameTimelineVsyncId) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
 
-    transaction->setFrameTimelineVsync(frameTimelineVsyncId);
+    transaction->setFrameTimelineInfo(
+            {frameTimelineVsyncId, android::os::IInputConstants::INVALID_INPUT_EVENT_ID});
 }
 
 class JankDataListenerWrapper : public JankDataListener {
@@ -1829,8 +1828,6 @@
             (void*)nativeGetProtectedContentSupport },
     {"nativeDeferTransactionUntil", "(JJJJ)V",
             (void*)nativeDeferTransactionUntil },
-    {"nativeReparentChildren", "(JJJ)V",
-            (void*)nativeReparentChildren } ,
     {"nativeReparent", "(JJJ)V",
             (void*)nativeReparent },
     {"nativeCaptureDisplay",
@@ -2031,12 +2028,14 @@
     gCaptureArgsClassInfo.pixelFormat = GetFieldIDOrDie(env, captureArgsClazz, "mPixelFormat", "I");
     gCaptureArgsClassInfo.sourceCrop =
             GetFieldIDOrDie(env, captureArgsClazz, "mSourceCrop", "Landroid/graphics/Rect;");
-    gCaptureArgsClassInfo.frameScale = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScale", "F");
+    gCaptureArgsClassInfo.frameScaleX = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleX", "F");
+    gCaptureArgsClassInfo.frameScaleY = GetFieldIDOrDie(env, captureArgsClazz, "mFrameScaleY", "F");
     gCaptureArgsClassInfo.captureSecureLayers =
             GetFieldIDOrDie(env, captureArgsClazz, "mCaptureSecureLayers", "Z");
     gCaptureArgsClassInfo.allowProtected =
             GetFieldIDOrDie(env, captureArgsClazz, "mAllowProtected", "Z");
     gCaptureArgsClassInfo.uid = GetFieldIDOrDie(env, captureArgsClazz, "mUid", "J");
+    gCaptureArgsClassInfo.grayscale = GetFieldIDOrDie(env, captureArgsClazz, "mGrayscale", "Z");
 
     jclass displayCaptureArgsClazz =
             FindClassOrDie(env, "android/view/SurfaceControl$DisplayCaptureArgs");
diff --git a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
index 52bed6b..dfae684 100644
--- a/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp
@@ -26,239 +26,230 @@
 #include <android_runtime/Log.h>
 
 #include <nativehelper/ScopedPrimitiveArray.h>
-#include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
 
+static constexpr uint16_t DEFAULT_THREAD_AGGREGATION_KEY = 0;
+static constexpr uint16_t SELECTED_THREAD_AGGREGATION_KEY = 1;
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
 // Number of milliseconds in a jiffy - the unit of time measurement for processes and threads
 static const uint32_t gJiffyMillis = (uint32_t)(1000 / sysconf(_SC_CLK_TCK));
 
-// Given a PID, returns a vector of all TIDs for the process' tasks. Thread IDs are
-// file names in the /proc/<pid>/task directory.
-static bool getThreadIds(const std::string &procPath, const pid_t pid,
-                         std::vector<pid_t> &outThreadIds) {
-    std::string taskPath = android::base::StringPrintf("%s/%u/task", procPath.c_str(), pid);
+// Abstract class for readers of CPU time-in-state. There are two implementations of
+// this class: BpfCpuTimeInStateReader and MockCpuTimeInStateReader.  The former is used
+// by the production code. The latter is used by unit tests to provide mock
+// CPU time-in-state data via a Java implementation.
+class ICpuTimeInStateReader {
+public:
+    virtual ~ICpuTimeInStateReader() {}
 
-    struct dirent **dirlist;
-    int threadCount = scandir(taskPath.c_str(), &dirlist, NULL, NULL);
-    if (threadCount == -1) {
-        ALOGE("Cannot read directory %s", taskPath.c_str());
-        return false;
-    }
+    // Returns the overall number of cluser-frequency combinations
+    virtual size_t getCpuFrequencyCount();
 
-    outThreadIds.reserve(threadCount);
+    // Marks the CPU time-in-state tracking for threads of the specified TGID
+    virtual bool startTrackingProcessCpuTimes(pid_t) = 0;
 
-    for (int i = 0; i < threadCount; i++) {
-        pid_t tid;
-        if (android::base::ParseInt<pid_t>(dirlist[i]->d_name, &tid)) {
-            outThreadIds.push_back(tid);
+    // Marks the thread specified by its PID for CPU time-in-state tracking.
+    virtual bool startAggregatingTaskCpuTimes(pid_t, uint16_t) = 0;
+
+    // Retrieves the accumulated time-in-state data, which is organized as a map
+    // from aggregation keys to vectors of vectors using the format:
+    // { aggKey0 -> [[t0_0_0, t0_0_1, ...], [t0_1_0, t0_1_1, ...], ...],
+    //   aggKey1 -> [[t1_0_0, t1_0_1, ...], [t1_1_0, t1_1_1, ...], ...], ... }
+    // where ti_j_k is the ns tid i spent running on the jth cluster at the cluster's kth lowest
+    // freq.
+    virtual std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+    getAggregatedTaskCpuFreqTimes(pid_t, const std::vector<uint16_t> &);
+};
+
+// ICpuTimeInStateReader that uses eBPF to provide a map of aggregated CPU time-in-state values.
+// See cputtimeinstate.h/.cpp
+class BpfCpuTimeInStateReader : public ICpuTimeInStateReader {
+public:
+    size_t getCpuFrequencyCount() {
+        std::optional<std::vector<std::vector<uint32_t>>> cpuFreqs = android::bpf::getCpuFreqs();
+        if (!cpuFreqs) {
+            ALOGE("Cannot obtain CPU frequency count");
+            return 0;
         }
-        free(dirlist[i]);
-    }
-    free(dirlist);
 
-    return true;
+        size_t freqCount = 0;
+        for (auto cluster : *cpuFreqs) {
+            freqCount += cluster.size();
+        }
+
+        return freqCount;
+    }
+
+    bool startTrackingProcessCpuTimes(pid_t tgid) {
+        return android::bpf::startTrackingProcessCpuTimes(tgid);
+    }
+
+    bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
+        return android::bpf::startAggregatingTaskCpuTimes(pid, aggregationKey);
+    }
+
+    std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+    getAggregatedTaskCpuFreqTimes(pid_t pid, const std::vector<uint16_t> &aggregationKeys) {
+        return android::bpf::getAggregatedTaskCpuFreqTimes(pid, aggregationKeys);
+    }
+};
+
+// ICpuTimeInStateReader that uses JNI to provide a map of aggregated CPU time-in-state
+// values.
+// This version of CpuTimeInStateReader is used exclusively for providing mock data in tests.
+class MockCpuTimeInStateReader : public ICpuTimeInStateReader {
+private:
+    JNIEnv *mEnv;
+    jobject mCpuTimeInStateReader;
+
+public:
+    MockCpuTimeInStateReader(JNIEnv *env, jobject cpuTimeInStateReader)
+          : mEnv(env), mCpuTimeInStateReader(cpuTimeInStateReader) {}
+
+    size_t getCpuFrequencyCount();
+
+    bool startTrackingProcessCpuTimes(pid_t tgid);
+
+    bool startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey);
+
+    std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+    getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector<uint16_t> &aggregationKeys);
+};
+
+static ICpuTimeInStateReader *getCpuTimeInStateReader(JNIEnv *env,
+                                                      jobject cpuTimeInStateReaderObject) {
+    if (cpuTimeInStateReaderObject) {
+        return new MockCpuTimeInStateReader(env, cpuTimeInStateReaderObject);
+    } else {
+        return new BpfCpuTimeInStateReader();
+    }
 }
 
-// Reads contents of a time_in_state file and returns times as a vector of times per frequency
-// A time_in_state file contains pairs of frequency - time (in jiffies):
-//
-//    cpu0
-//    300000 30
-//    403200 0
-//    cpu4
-//    710400 10
-//    825600 20
-//    940800 30
-//
-static bool getThreadTimeInState(const std::string &procPath, const pid_t pid, const pid_t tid,
-                                 const size_t frequencyCount,
-                                 std::vector<uint64_t> &outThreadTimeInState) {
-    std::string timeInStateFilePath =
-            android::base::StringPrintf("%s/%u/task/%u/time_in_state", procPath.c_str(), pid, tid);
-    std::string data;
+static jint getCpuFrequencyCount(JNIEnv *env, jclass, jobject cpuTimeInStateReaderObject) {
+    std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
+            getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
+    return cpuTimeInStateReader->getCpuFrequencyCount();
+}
 
-    if (!android::base::ReadFileToString(timeInStateFilePath, &data)) {
-        ALOGE("Cannot read file: %s", timeInStateFilePath.c_str());
-        return false;
-    }
+static jboolean startTrackingProcessCpuTimes(JNIEnv *env, jclass, jint tgid,
+                                             jobject cpuTimeInStateReaderObject) {
+    std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
+            getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
+    return cpuTimeInStateReader->startTrackingProcessCpuTimes(tgid);
+}
 
-    auto lines = android::base::Split(data, "\n");
-    size_t index = 0;
-    for (const auto &line : lines) {
-        if (line.empty()) {
-            continue;
-        }
+static jboolean startAggregatingThreadCpuTimes(JNIEnv *env, jclass, jintArray selectedThreadIdArray,
+                                               jobject cpuTimeInStateReaderObject) {
+    ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
+    std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
+            getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
 
-        auto numbers = android::base::Split(line, " ");
-        if (numbers.size() != 2) {
-            continue;
-        }
-        uint64_t timeInState;
-        if (!android::base::ParseUint<uint64_t>(numbers[1], &timeInState)) {
-            ALOGE("Invalid time_in_state file format: %s", timeInStateFilePath.c_str());
+    for (int i = 0; i < selectedThreadIds.size(); i++) {
+        if (!cpuTimeInStateReader->startAggregatingTaskCpuTimes(selectedThreadIds[i],
+                                                                SELECTED_THREAD_AGGREGATION_KEY)) {
             return false;
         }
-        if (index < frequencyCount) {
-            outThreadTimeInState[index] = timeInState;
-        }
-        index++;
     }
+    return true;
+}
 
+// Converts time-in-state data from a vector of vectors to a flat array.
+// Also converts from nanoseconds to milliseconds.
+static bool flattenTimeInStateData(ScopedLongArrayRW &cpuTimesMillis,
+                                   const std::vector<std::vector<uint64_t>> &data) {
+    size_t frequencyCount = cpuTimesMillis.size();
+    size_t index = 0;
+    for (const auto &cluster : data) {
+        for (const uint64_t &timeNanos : cluster) {
+            if (index < frequencyCount) {
+                cpuTimesMillis[index] = timeNanos / NSEC_PER_MSEC;
+            }
+            index++;
+        }
+    }
     if (index != frequencyCount) {
-        ALOGE("Incorrect number of frequencies %u in %s. Expected %u",
-              (uint32_t)outThreadTimeInState.size(), timeInStateFilePath.c_str(),
-              (uint32_t)frequencyCount);
+        ALOGE("CPU time-in-state reader returned data for %zu frequencies; expected: %zu", index,
+              frequencyCount);
         return false;
     }
 
     return true;
 }
 
-static int pidCompare(const void *a, const void *b) {
-    return (*(pid_t *)a - *(pid_t *)b);
-}
-
-static inline bool isSelectedThread(const pid_t tid, const pid_t *selectedThreadIds,
-                                    const size_t selectedThreadCount) {
-    return bsearch(&tid, selectedThreadIds, selectedThreadCount, sizeof(pid_t), pidCompare) != NULL;
-}
-
-// Reads all /proc/<pid>/task/*/time_in_state files and aggregates per-frequency
+// Reads all CPU time-in-state data accumulated by BPF and aggregates per-frequency
 // time in state data for all threads.  Also, separately aggregates time in state for
 // selected threads whose TIDs are passes as selectedThreadIds.
-static void aggregateThreadCpuTimes(const std::string &procPath, const pid_t pid,
-                                    const std::vector<pid_t> &threadIds,
-                                    const size_t frequencyCount, const pid_t *selectedThreadIds,
-                                    const size_t selectedThreadCount,
-                                    uint64_t *threadCpuTimesMillis,
-                                    uint64_t *selectedThreadCpuTimesMillis) {
-    for (size_t j = 0; j < frequencyCount; j++) {
-        threadCpuTimesMillis[j] = 0;
-        selectedThreadCpuTimesMillis[j] = 0;
-    }
-
-    for (size_t i = 0; i < threadIds.size(); i++) {
-        pid_t tid = threadIds[i];
-        std::vector<uint64_t> timeInState(frequencyCount);
-        if (!getThreadTimeInState(procPath, pid, tid, frequencyCount, timeInState)) {
-            continue;
-        }
-
-        bool selectedThread = isSelectedThread(tid, selectedThreadIds, selectedThreadCount);
-        for (size_t j = 0; j < frequencyCount; j++) {
-            threadCpuTimesMillis[j] += timeInState[j];
-            if (selectedThread) {
-                selectedThreadCpuTimesMillis[j] += timeInState[j];
-            }
-        }
-    }
-    for (size_t i = 0; i < frequencyCount; i++) {
-        threadCpuTimesMillis[i] *= gJiffyMillis;
-        selectedThreadCpuTimesMillis[i] *= gJiffyMillis;
-    }
-}
-
-// Reads process utime and stime from the /proc/<pid>/stat file.
-// Format of this file is described in https://man7.org/linux/man-pages/man5/proc.5.html.
-static bool getProcessCpuTime(const std::string &procPath, const pid_t pid,
-                              uint64_t &outTimeMillis) {
-    std::string statFilePath = android::base::StringPrintf("%s/%u/stat", procPath.c_str(), pid);
-    std::string data;
-    if (!android::base::ReadFileToString(statFilePath, &data)) {
-        return false;
-    }
-
-    auto fields = android::base::Split(data, " ");
-    uint64_t utime, stime;
-
-    // Field 14 (counting from 1) is utime - process time in user space, in jiffies
-    // Field 15 (counting from 1) is stime - process time in system space, in jiffies
-    if (fields.size() < 15 || !android::base::ParseUint(fields[13], &utime) ||
-        !android::base::ParseUint(fields[14], &stime)) {
-        ALOGE("Invalid file format %s", statFilePath.c_str());
-        return false;
-    }
-
-    outTimeMillis = (utime + stime) * gJiffyMillis;
-    return true;
-}
-
-// Estimates per cluster per frequency CPU time for the entire process
-// by distributing the total process CPU time proportionately to how much
-// CPU time its threads took on those clusters/frequencies.  This algorithm
-// works more accurately when when we have equally distributed concurrency.
-// TODO(b/169279846): obtain actual process CPU times from the kernel
-static void estimateProcessTimeInState(const uint64_t processCpuTimeMillis,
-                                       const uint64_t *threadCpuTimesMillis,
-                                       const size_t frequencyCount,
-                                       uint64_t *processCpuTimesMillis) {
-    uint64_t totalCpuTimeAllThreads = 0;
-    for (size_t i = 0; i < frequencyCount; i++) {
-        totalCpuTimeAllThreads += threadCpuTimesMillis[i];
-    }
-
-    if (totalCpuTimeAllThreads != 0) {
-        for (size_t i = 0; i < frequencyCount; i++) {
-            processCpuTimesMillis[i] =
-                    processCpuTimeMillis * threadCpuTimesMillis[i] / totalCpuTimeAllThreads;
-        }
-    } else {
-        for (size_t i = 0; i < frequencyCount; i++) {
-            processCpuTimesMillis[i] = 0;
-        }
-    }
-}
-
-static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jstring procPath, jint pid,
-                                    jintArray selectedThreadIdArray,
-                                    jlongArray processCpuTimesMillisArray,
+static jboolean readProcessCpuUsage(JNIEnv *env, jclass, jint pid,
                                     jlongArray threadCpuTimesMillisArray,
-                                    jlongArray selectedThreadCpuTimesMillisArray) {
-    ScopedUtfChars procPathChars(env, procPath);
-    ScopedIntArrayRO selectedThreadIds(env, selectedThreadIdArray);
-    ScopedLongArrayRW processCpuTimesMillis(env, processCpuTimesMillisArray);
+                                    jlongArray selectedThreadCpuTimesMillisArray,
+                                    jobject cpuTimeInStateReaderObject) {
     ScopedLongArrayRW threadCpuTimesMillis(env, threadCpuTimesMillisArray);
     ScopedLongArrayRW selectedThreadCpuTimesMillis(env, selectedThreadCpuTimesMillisArray);
+    std::unique_ptr<ICpuTimeInStateReader> cpuTimeInStateReader(
+            getCpuTimeInStateReader(env, cpuTimeInStateReaderObject));
 
-    std::string procPathStr(procPathChars.c_str());
-
-    // Get all thread IDs for the process.
-    std::vector<pid_t> threadIds;
-    if (!getThreadIds(procPathStr, pid, threadIds)) {
-        ALOGE("Could not obtain thread IDs from: %s", procPathStr.c_str());
-        return false;
-    }
-
-    size_t frequencyCount = processCpuTimesMillis.size();
+    const size_t frequencyCount = cpuTimeInStateReader->getCpuFrequencyCount();
 
     if (threadCpuTimesMillis.size() != frequencyCount) {
-        ALOGE("Invalid array length: threadCpuTimesMillis");
+        ALOGE("Invalid threadCpuTimesMillis array length: %zu frequencies; expected: %zu",
+              threadCpuTimesMillis.size(), frequencyCount);
         return false;
     }
+
     if (selectedThreadCpuTimesMillis.size() != frequencyCount) {
-        ALOGE("Invalid array length: selectedThreadCpuTimesMillisArray");
+        ALOGE("Invalid selectedThreadCpuTimesMillis array length: %zu frequencies; expected: %zu",
+              selectedThreadCpuTimesMillis.size(), frequencyCount);
         return false;
     }
 
-    aggregateThreadCpuTimes(procPathStr, pid, threadIds, frequencyCount, selectedThreadIds.get(),
-                            selectedThreadIds.size(),
-                            reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
-                            reinterpret_cast<uint64_t *>(selectedThreadCpuTimesMillis.get()));
-
-    uint64_t processCpuTime;
-    bool ret = getProcessCpuTime(procPathStr, pid, processCpuTime);
-    if (ret) {
-        estimateProcessTimeInState(processCpuTime,
-                                   reinterpret_cast<uint64_t *>(threadCpuTimesMillis.get()),
-                                   frequencyCount,
-                                   reinterpret_cast<uint64_t *>(processCpuTimesMillis.get()));
+    for (size_t i = 0; i < frequencyCount; i++) {
+        threadCpuTimesMillis[i] = 0;
+        selectedThreadCpuTimesMillis[i] = 0;
     }
-    return ret;
+
+    std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>> data =
+            cpuTimeInStateReader->getAggregatedTaskCpuFreqTimes(pid,
+                                                                {DEFAULT_THREAD_AGGREGATION_KEY,
+                                                                 SELECTED_THREAD_AGGREGATION_KEY});
+    if (!data) {
+        ALOGE("Cannot read thread CPU times for PID %d", pid);
+        return false;
+    }
+
+    if (!flattenTimeInStateData(threadCpuTimesMillis, (*data)[DEFAULT_THREAD_AGGREGATION_KEY])) {
+        return false;
+    }
+
+    if (!flattenTimeInStateData(selectedThreadCpuTimesMillis,
+                                (*data)[SELECTED_THREAD_AGGREGATION_KEY])) {
+        return false;
+    }
+
+    // threadCpuTimesMillis returns CPU times for _all_ threads, including the selected ones
+    for (size_t i = 0; i < frequencyCount; i++) {
+        threadCpuTimesMillis[i] += selectedThreadCpuTimesMillis[i];
+    }
+
+    return true;
 }
 
 static const JNINativeMethod g_single_methods[] = {
-        {"readProcessCpuUsage", "(Ljava/lang/String;I[I[J[J[J)Z", (void *)readProcessCpuUsage},
+        {"getCpuFrequencyCount",
+         "(Lcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)I",
+         (void *)getCpuFrequencyCount},
+        {"startTrackingProcessCpuTimes",
+         "(ILcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
+         (void *)startTrackingProcessCpuTimes},
+        {"startAggregatingThreadCpuTimes",
+         "([ILcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
+         (void *)startAggregatingThreadCpuTimes},
+        {"readProcessCpuUsage",
+         "(I[J[J"
+         "Lcom/android/internal/os/KernelSingleProcessCpuThreadReader$CpuTimeInStateReader;)Z",
+         (void *)readProcessCpuUsage},
 };
 
 int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv *env) {
@@ -266,4 +257,77 @@
                                 g_single_methods, NELEM(g_single_methods));
 }
 
+size_t MockCpuTimeInStateReader::getCpuFrequencyCount() {
+    jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
+    jmethodID mid = mEnv->GetMethodID(cls, "getCpuFrequencyCount", "()I");
+    if (mid == 0) {
+        ALOGE("Couldn't find the method getCpuFrequencyCount");
+        return false;
+    }
+    return (size_t)mEnv->CallIntMethod(mCpuTimeInStateReader, mid);
+}
+
+bool MockCpuTimeInStateReader::startTrackingProcessCpuTimes(pid_t tgid) {
+    jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
+    jmethodID mid = mEnv->GetMethodID(cls, "startTrackingProcessCpuTimes", "(I)Z");
+    if (mid == 0) {
+        ALOGE("Couldn't find the method startTrackingProcessCpuTimes");
+        return false;
+    }
+    return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, tgid);
+}
+
+bool MockCpuTimeInStateReader::startAggregatingTaskCpuTimes(pid_t pid, uint16_t aggregationKey) {
+    jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
+    jmethodID mid = mEnv->GetMethodID(cls, "startAggregatingTaskCpuTimes", "(II)Z");
+    if (mid == 0) {
+        ALOGE("Couldn't find the method startAggregatingTaskCpuTimes");
+        return false;
+    }
+    return mEnv->CallBooleanMethod(mCpuTimeInStateReader, mid, pid, aggregationKey);
+}
+
+std::optional<std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>>>
+MockCpuTimeInStateReader::getAggregatedTaskCpuFreqTimes(
+        pid_t pid, const std::vector<uint16_t> &aggregationKeys) {
+    jclass cls = mEnv->GetObjectClass(mCpuTimeInStateReader);
+    jmethodID mid =
+            mEnv->GetMethodID(cls, "getAggregatedTaskCpuFreqTimes", "(I)[Ljava/lang/String;");
+    if (mid == 0) {
+        ALOGE("Couldn't find the method getAggregatedTaskCpuFreqTimes");
+        return {};
+    }
+
+    std::unordered_map<uint16_t, std::vector<std::vector<uint64_t>>> map;
+
+    jobjectArray stringArray =
+            (jobjectArray)mEnv->CallObjectMethod(mCpuTimeInStateReader, mid, pid);
+    int size = mEnv->GetArrayLength(stringArray);
+    for (int i = 0; i < size; i++) {
+        ScopedUtfChars line(mEnv, (jstring)mEnv->GetObjectArrayElement(stringArray, i));
+        uint16_t aggregationKey;
+        std::vector<std::vector<uint64_t>> times;
+
+        // Each string is formatted like this: "aggKey:t0_0 t0_1...:t1_0 t1_1..."
+        auto fields = android::base::Split(line.c_str(), ":");
+        android::base::ParseUint(fields[0], &aggregationKey);
+
+        for (int j = 1; j < fields.size(); j++) {
+            auto numbers = android::base::Split(fields[j], " ");
+
+            std::vector<uint64_t> chunk;
+            for (int k = 0; k < numbers.size(); k++) {
+                uint64_t time;
+                android::base::ParseUint(numbers[k], &time);
+                chunk.emplace_back(time);
+            }
+            times.emplace_back(chunk);
+        }
+
+        map.emplace(aggregationKey, times);
+    }
+
+    return map;
+}
+
 } // namespace android
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c76a5d3..3d1c38d 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -70,7 +70,6 @@
 #include <bionic/malloc.h>
 #include <bionic/mte.h>
 #include <cutils/fs.h>
-#include <cutils/memory.h>
 #include <cutils/multiuser.h>
 #include <cutils/sockets.h>
 #include <private/android_filesystem_config.h>
@@ -340,6 +339,7 @@
     GWP_ASAN_LEVEL_NEVER = 0 << 21,
     GWP_ASAN_LEVEL_LOTTERY = 1 << 21,
     GWP_ASAN_LEVEL_ALWAYS = 2 << 21,
+    NATIVE_HEAP_ZERO_INIT = 1 << 23,
 };
 
 enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -621,13 +621,6 @@
 
   // Set the jemalloc decay time to 1.
   mallopt(M_DECAY_TIME, 1);
-
-  // Avoid potentially expensive memory mitigations, mostly meant for system
-  // processes, in apps. These may cause app compat problems, use more memory,
-  // or reduce performance. While it would be nice to have them for apps,
-  // we will have to wait until they are proven out, have more efficient
-  // hardware, and/or apply them only to new applications.
-  process_disable_memory_mitigations();
 }
 
 static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
@@ -1785,9 +1778,22 @@
       break;
   }
   mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, heap_tagging_level);
+
   // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
   runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK;
 
+  // Avoid heap zero initialization for applications without MTE. Zero init may
+  // cause app compat problems, use more memory, or reduce performance. While it
+  // would be nice to have them for apps, we will have to wait until they are
+  // proven out, have more efficient hardware, and/or apply them only to new
+  // applications.
+  if (!(runtime_flags & RuntimeFlags::NATIVE_HEAP_ZERO_INIT)) {
+    mallopt(M_BIONIC_ZERO_INIT, 0);
+  }
+
+  // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime.
+  runtime_flags &= ~RuntimeFlags::NATIVE_HEAP_ZERO_INIT;
+
   bool forceEnableGwpAsan = false;
   switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) {
       default:
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 748b4b4..99fd215 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -16,6 +16,7 @@
 jjaggi@google.com
 roosa@google.com
 per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com
+per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
 
 # Biometrics
 kchyn@google.com
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index e683306..bb39ea8 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -110,6 +110,8 @@
         optional int32 network_security_config_res = 17;
         optional int32 category = 18;
         optional int32 enable_gwp_asan = 19;
+        optional int32 enable_memtag = 20;
+        optional bool native_heap_zero_init = 21;
     }
     optional Detail detail = 17;
 }
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 4809419..2b665c0 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -41,7 +41,6 @@
 import "frameworks/base/core/proto/android/server/location/context_hub.proto";
 import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
 import "frameworks/base/core/proto/android/server/powerstatsservice.proto";
-import "frameworks/base/apex/permission/service/proto/com/android/role/roleservice.proto";
 import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/service/appwidget.proto";
 import "frameworks/base/core/proto/android/service/battery.proto";
@@ -63,6 +62,7 @@
 import "frameworks/base/core/proto/android/section.proto";
 import "frameworks/base/proto/src/ipconnectivity.proto";
 import "frameworks/proto_logging/stats/enums/service/usb.proto";
+import "packages/modules/Permission/service/proto/com/android/role/roleservice.proto";
 
 package android.os;
 
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index f5aa17d..632d372 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -518,6 +518,11 @@
     }
     optional Search search = 48;
 
+    message CameraAutorotate {
+        optional SettingProto enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    }
+    optional CameraAutorotate camera_autorotate = 88;
+
     message SpellChecker {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
@@ -642,5 +647,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 88;
+    // Next tag = 89;
 }
diff --git a/core/proto/android/server/apphibernationservice.proto b/core/proto/android/server/apphibernationservice.proto
new file mode 100644
index 0000000..d341c4b
--- /dev/null
+++ b/core/proto/android/server/apphibernationservice.proto
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+syntax = "proto2";
+package com.android.server.apphibernation;
+
+option java_multiple_files = true;
+
+// Proto for hibernation states for all packages for a user.
+message UserLevelHibernationStatesProto {
+  repeated UserLevelHibernationStateProto hibernation_state = 1;
+}
+
+// Proto for com.android.server.apphibernation.UserLevelState.
+message UserLevelHibernationStateProto {
+  optional string package_name = 1;
+  optional bool hibernated = 2;
+}
+
+// Proto for global hibernation states for all packages.
+message GlobalLevelHibernationStatesProto {
+  repeated GlobalLevelHibernationStateProto hibernation_state = 1;
+}
+
+// Proto for com.android.server.apphibernation.GlobalLevelState
+message GlobalLevelHibernationStateProto {
+  optional string package_name = 1;
+  optional bool hibernated = 2;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/powerstatsservice.proto b/core/proto/android/server/powerstatsservice.proto
index ec9bc11..4b1ee02 100644
--- a/core/proto/android/server/powerstatsservice.proto
+++ b/core/proto/android/server/powerstatsservice.proto
@@ -191,6 +191,14 @@
     optional string name = 4;
 }
 
+message EnergyConsumerAttributionProto {
+    /** Android ID / Linux UID, the accumulated energy should be attributed to. */
+    optional int32 uid = 1;
+
+    /** Accumulated energy since boot in microwatt-seconds (uWs) for this AID. */
+    optional int64 energy_uws = 2;
+}
+
 /**
  * Energy consumer result:
  * An estimate of energy consumption since boot for the subsystem identified
@@ -205,6 +213,9 @@
 
     /** Accumulated energy since device boot in microwatt-seconds (uWs) */
     optional int64 energy_uws = 3;
+
+    /** Optional attribution per UID for this EnergyConsumer. */
+    repeated EnergyConsumerAttributionProto attribution = 4;
 }
 
 /**
@@ -220,6 +231,9 @@
 
     /** Name of the energy meter channel */
     optional string name = 2;
+
+    /** Name of the subsystem associated with this Channel. Opaque to framework */
+    optional string subsystem = 3;
 }
 
 /**
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 610e0e0..c882431 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -68,7 +68,7 @@
     // Whether or not the home activity is the recents activity. This is needed for the CTS tests to
     // know what activity types to check for when invoking splitscreen multi-window.
     optional bool is_home_recents_component = 6;
-    repeated IdentifierProto pending_activities = 7;
+    repeated IdentifierProto pending_activities = 7 [deprecated=true];
 }
 
 message BarControllerProto {
@@ -307,6 +307,7 @@
     optional bool animating_bounds = 26 [deprecated = true];
     optional float minimize_amount = 27;
     optional bool created_by_organizer = 28;
+    optional string affinity = 29;
 }
 
 /* represents ActivityRecordProto */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3975529..827bf7b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -93,6 +93,7 @@
     <protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
     <protected-broadcast android:name="android.intent.action.USER_INITIALIZE" />
     <protected-broadcast android:name="android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION" />
+    <protected-broadcast android:name="android.intent.action.DOMAINS_NEED_VERIFICATION" />
     <protected-broadcast android:name="android.intent.action.OVERLAY_ADDED" />
     <protected-broadcast android:name="android.intent.action.OVERLAY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.OVERLAY_REMOVED" />
@@ -689,10 +690,6 @@
     <!-- Made protected in S (was added in R) -->
     <protected-broadcast android:name="com.android.internal.intent.action.BUGREPORT_REQUESTED" />
 
-    <!-- Added in S -->
-    <protected-broadcast android:name="android.app.action.MANAGED_PROFILE_CREATED" />
-    <protected-broadcast android:name="android.app.action.PROVISIONED_MANAGED_DEVICE" />
-
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
@@ -1744,6 +1741,12 @@
     <permission android:name="android.permission.REQUEST_NETWORK_SCORES"
         android:protectionLevel="signature|setup" />
 
+    <!-- Allows applications to restart the Wi-Fi subsystem.
+     @SystemApi
+     <p>Not for use by third-party applications. @hide -->
+    <permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows applications to toggle airplane mode.
          <p>Not for use by third-party or privileged applications.
     -->
@@ -2532,10 +2535,10 @@
     <permission android:name="android.permission.REAL_GET_TASKS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows an application to start a task from a ActivityManager#RecentTaskInfo.
+    <!-- @TestApi Allows an application to start a task from a ActivityManager#RecentTaskInfo.
          @hide -->
     <permission android:name="android.permission.START_TASKS_FROM_RECENTS"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|recents" />
 
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to do interactions
          across the users on the device, using singleton services and
@@ -2577,6 +2580,10 @@
     <permission android:name="android.permission.CREATE_USERS"
         android:protectionLevel="signature" />
 
+    <!-- @TestApi @hide Allows an application to query user info for all users on the device. -->
+    <permission android:name="android.permission.QUERY_USERS"
+                android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to set the profile owners and the device owner.
          This permission is not available to third party applications.-->
     <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
@@ -2600,7 +2607,7 @@
 
     <!-- @SystemApi @TestApi @hide Allows an application to change to remove/kill tasks -->
     <permission android:name="android.permission.REMOVE_TASKS"
-        android:protectionLevel="signature|documenter" />
+        android:protectionLevel="signature|documenter|recents" />
 
     <!-- @deprecated Use MANAGE_ACTIVITY_TASKS instead.
          @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
@@ -2609,7 +2616,7 @@
 
     <!-- @SystemApi @TestApi @hide Allows an application to create/manage/remove tasks -->
     <permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- @SystemApi @TestApi @hide Allows an application to embed other activities -->
     <permission android:name="android.permission.ACTIVITY_EMBEDDING"
@@ -2691,24 +2698,24 @@
          The app can check whether it has this authorization by calling
          {@link android.provider.Settings#canDrawOverlays
          Settings.canDrawOverlays()}.
-         <p>Protection level: signature|appop|preinstalled|pre23|development -->
+         <p>Protection level: signature|appop|installer|recents|appPredictor|pre23|development -->
     <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
         android:label="@string/permlab_systemAlertWindow"
         android:description="@string/permdesc_systemAlertWindow"
-        android:protectionLevel="signature|appop|preinstalled|pre23|development" />
+        android:protectionLevel="signature|appop|installer|recents|appPredictor|pre23|development" />
 
     <!-- @SystemApi @hide Allows an application to create windows using the type
          {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
          shown on top of all other apps.
 
          Allows an application to use
-         {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_SYSTEM_APPLICATION_OVERLAY}
+         {@link android.view.WindowManager.LayoutsParams#setSystemApplicationOverlay(boolean)}
          to create overlays that will stay visible, even if another window is requesting overlays to
          be hidden through {@link android.view.Window#setHideOverlayWindows(boolean)}.
 
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SYSTEM_APPLICATION_OVERLAY"
-                android:protectionLevel="signature|wellbeing"/>
+                android:protectionLevel="signature|recents|wellbeing"/>
 
     <!-- @deprecated Use {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
          @hide
@@ -3278,7 +3285,7 @@
          and its icons.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.STATUS_BAR"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|recents" />
 
     <!-- Allows an application to trigger bugreport via shell using the bugreport API.
         <p>Not for use by third-party applications.
@@ -3445,7 +3452,7 @@
          critical UI such as the home screen.
          @hide -->
     <permission android:name="android.permission.STOP_APP_SWITCHES"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|recents" />
 
     <!-- @SystemApi Allows an application to retrieve private information about
          the current top activity, such as any assist context it can provide.
@@ -3830,7 +3837,7 @@
          @hide
     -->
     <permission android:name="android.permission.SET_ORIENTATION"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- @SystemApi Allows low-level access to setting the pointer speed.
          <p>Not for use by third-party applications.
@@ -4094,7 +4101,7 @@
           @hide
           @removed -->
     <permission android:name="android.permission.READ_FRAME_BUFFER"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|recents" />
 
     <!-- Allows an application to use InputFlinger's low level features.
          @hide -->
@@ -4127,11 +4134,6 @@
     <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
         android:protectionLevel="signature" />
 
-    <!-- @SystemApi Allows an application to use background blur.
-         @hide -->
-    <permission android:name="android.permission.USE_BACKGROUND_BLUR"
-        android:protectionLevel="signature|privileged" />
-
     <!-- Allows an application to control the lights on the device.
          @hide
          @SystemApi
@@ -4701,6 +4703,26 @@
     <permission android:name="android.permission.BIND_INTENT_FILTER_VERIFIER"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @hide Domain verification agent package needs to have this permission before the
+         system will trust it to verify domains.
+
+         TODO(159952358): STOPSHIP: This must be updated to the new "internal" protectionLevel
+    -->
+    <permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi @hide Must be required by the domain verification agent's intent
+         BroadcastReceiver, to ensure that only the system can interact with it.
+    -->
+    <permission android:name="android.permission.BIND_DOMAIN_VERIFICATION_AGENT"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi @hide Allows an app like Settings to update the user's grants to what domains
+         an app is allowed to automatically open.
+    -->
+    <permission android:name="android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows applications to access serial ports via the SerialManager.
          @hide -->
     <permission android:name="android.permission.SERIAL_PORT"
@@ -5162,6 +5184,11 @@
     <permission android:name="android.permission.MANAGE_SEARCH_UI"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manage the smartspace service.
+     @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_SMARTSPACE"
+        android:protectionLevel="signature" />
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -5271,7 +5298,7 @@
     <!-- @SystemApi Allows modifying accessibility state.
          @hide -->
     <permission android:name="android.permission.MANAGE_ACCESSIBILITY"
-        android:protectionLevel="signature|setup" />
+        android:protectionLevel="signature|setup|recents" />
 
     <!-- @SystemApi Allows an app to grant a profile owner access to device identifiers.
          <p>Not for use by third-party applications.
@@ -5287,7 +5314,8 @@
                 android:protectionLevel="signature" />
 
     <!-- Allows financial apps to read filtered sms messages.
-         Protection level: signature|appop  -->
+         Protection level: signature|appop
+         @deprecated The API that used this permission is no longer functional.  -->
     <permission android:name="android.permission.SMS_FINANCIAL_TRANSACTIONS"
         android:protectionLevel="signature|appop" />
 
@@ -5399,17 +5427,18 @@
     <permission android:name="android.permission.INPUT_CONSUMER"
                 android:protectionLevel="signature" />
 
-    <!-- @hide Allows an application to control the system's device state managed by the
+    <!-- @hide @TestApi Allows an application to control the system's device state managed by the
          {@link android.service.devicestate.DeviceStateManagerService}. For example, on foldable
          devices this would grant access to toggle between the folded and unfolded states. -->
     <permission android:name="android.permission.CONTROL_DEVICE_STATE"
                 android:protectionLevel="signature" />
 
-    <!-- Must be required by an {@link android.service.attestation.ImpressionAttestationService}
-         to ensure that only the system can bind to it.
-         @hide This is not a third-party API (intended for OEMs and system apps).
+    <!-- Must be required by a
+        {@link android.service.screenshot.ScreenshotHasherService}
+        to ensure that only the system can bind to it.
+        @hide This is not a third-party API (intended for OEMs and system apps).
     -->
-    <permission android:name="android.permission.BIND_IMPRESSION_ATTESTATION_SERVICE"
+    <permission android:name="android.permission.BIND_SCREENSHOT_HASHER_SERVICE"
         android:protectionLevel="signature" />
 
     <!-- @hide @TestApi Allows an application to enable/disable toast rate limiting.
@@ -5906,7 +5935,7 @@
 
         <!-- AOSP configures a default secondary LocationTimeZoneProvider that uses an on-device
              data set from the com.android.geotz APEX. -->
-        <service android:name="com.android.timezone.geotz.provider.OfflineLocationTimeZoneProviderService"
+        <service android:name="com.android.timezone.location.provider.OfflineLocationTimeZoneProviderService"
                  android:enabled="@bool/config_enableSecondaryLocationTimeZoneProvider"
                  android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
                  android:exported="false">
diff --git a/core/res/OWNERS b/core/res/OWNERS
index a30111b..9d739b9 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -6,9 +6,12 @@
 dupin@google.com
 hackbod@android.com
 hackbod@google.com
+ilyamaty@google.com
+jaggies@google.com
 jsharkey@android.com
 jsharkey@google.com
 juliacr@google.com
+kchyn@google.com
 michaelwr@google.com
 nandana@google.com
 narayan@google.com
diff --git a/core/res/res/color-car/car_borderless_button_text_color.xml b/core/res/res/color-car/car_borderless_button_text_color.xml
index 1cdd6cd..0a86e40 100644
--- a/core/res/res/color-car/car_borderless_button_text_color.xml
+++ b/core/res/res/color-car/car_borderless_button_text_color.xml
@@ -16,5 +16,6 @@
 <!-- Default text colors for car buttons when enabled/disabled. -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:color="@*android:color/car_grey_700" android:state_enabled="false"/>
+    <item android:color="@*android:color/car_grey_700" android:state_ux_restricted="true"/>
     <item android:color="?android:attr/colorButtonNormal"/>
 </selector>
diff --git a/core/res/res/color-car/car_switch_track.xml b/core/res/res/color-car/car_switch_track.xml
new file mode 100644
index 0000000..8ca67dd
--- /dev/null
+++ b/core/res/res/color-car/car_switch_track.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<!-- copy of switch_track_material, but with a ux restricted state -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+          android:color="?attr/colorForeground"
+          android:alpha="?attr/disabledAlpha" />
+    <item android:state_ux_restricted="true"
+          android:color="?attr/colorForeground"
+          android:alpha="?attr/disabledAlpha" />
+    <item android:state_checked="true"
+          android:color="?attr/colorControlActivated" />
+    <item android:color="?attr/colorForeground" />
+</selector>
diff --git a/core/res/res/drawable-car/car_button_background.xml b/core/res/res/drawable-car/car_button_background.xml
index e568aeb..13b0ec1 100644
--- a/core/res/res/drawable-car/car_button_background.xml
+++ b/core/res/res/drawable-car/car_button_background.xml
@@ -25,6 +25,22 @@
                     android:color="#0059B3"/>
         </shape>
     </item>
+    <item android:state_focused="true" android:state_pressed="true" android:state_ux_restricted="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="@*android:dimen/car_button_radius"/>
+            <solid android:color="@*android:color/car_grey_300"/>
+            <stroke android:width="4dp"
+                    android:color="#0059B3"/>
+        </shape>
+    </item>
+    <item android:state_focused="true" android:state_ux_restricted="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="@*android:dimen/car_button_radius"/>
+            <solid android:color="@*android:color/car_grey_300"/>
+            <stroke android:width="8dp"
+                    android:color="#0059B3"/>
+        </shape>
+    </item>
     <item android:state_focused="true" android:state_pressed="true">
         <shape android:shape="rectangle">
             <corners android:radius="@*android:dimen/car_button_radius"/>
@@ -47,6 +63,12 @@
             <solid android:color="@*android:color/car_grey_300"/>
         </shape>
     </item>
+    <item android:state_ux_restricted="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="@*android:dimen/car_button_radius"/>
+            <solid android:color="@*android:color/car_grey_300"/>
+        </shape>
+    </item>
     <item>
         <ripple android:color="?android:attr/colorControlHighlight">
             <item>
diff --git a/core/res/res/drawable-car/car_switch_track.xml b/core/res/res/drawable-car/car_switch_track.xml
index cb0b9be..51e9f7e 100644
--- a/core/res/res/drawable-car/car_switch_track.xml
+++ b/core/res/res/drawable-car/car_switch_track.xml
@@ -41,7 +41,7 @@
       android:right="@dimen/car_switch_track_margin_size">
     <shape
         android:shape="rectangle"
-        android:tint="@color/switch_track_material">
+        android:tint="@color/car_switch_track">
       <corners android:radius="7dp" />
       <solid android:color="@color/white_disabled_material" />
       <size android:height="14dp" />
diff --git a/core/res/res/layout/work_widget_mask_view.xml b/core/res/res/layout/work_widget_mask_view.xml
index 39e1bbb..9e1692f 100644
--- a/core/res/res/layout/work_widget_mask_view.xml
+++ b/core/res/res/layout/work_widget_mask_view.xml
@@ -22,7 +22,8 @@
     android:importantForAccessibility="noHideDescendants"
     android:clickable="true">
 
-    <ImageView android:id="@+id/work_widget_app_icon"
+    <com.android.internal.widget.DisableImageView
+        android:id="@+id/work_widget_app_icon"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index eb45a6a..55eaaf6 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Net Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g>-oorkruis-SIM-oproepe"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-rugsteunoproepe"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nie aangestuur nie"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> sekondes"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensorkennisgewingdiens"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Skemerdiens"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Tydsonebespeurder (geen konnektiwiteit nie)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS-tydopdateringdiens"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Jou toestel sal uitgevee word"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Die administrasieprogram kan nie gebruik word nie. Jou toestel sal nou uitgevee word.\n\nKontak jou organisasie se administrateur as jy vrae het."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Druk is gedeaktiveer deur <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Jy kan nou jou hele skerm of \'n deel daarvan vergroot"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Skakel aan in Instellings"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Maak toe"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Om voort te gaan, moet &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; toegang tot jou toestel se mikrofoon hê."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Om voort te gaan, moet &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; toegang tot jou toestel se kamera hê."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Skakel aan"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensorprivaatheid"</string>
 </resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 43a14dc..3161226 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi ብቻ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> የሲም መካከል የሚደረግ ጥሪ"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> ምትኬ ጥሪ"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>፡አልተላለፈም"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>፡<xliff:g id="DIALING_NUMBER">{1}</xliff:g> ከ<xliff:g id="TIME_DELAY">{2}</xliff:g> ሰከንዶች በኋላ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 4469117..a2d3671 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -152,7 +152,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"‏Wi-Fi فقط"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"‏<xliff:g id="SPN">%s</xliff:g> الاتصال عبر شرائح SIM"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: لم تتم إعادة التوجيه"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> بعد <xliff:g id="TIME_DELAY">{2}</xliff:g> ثانية"</string>
@@ -211,8 +212,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"خدمة إشعارات جهاز الاستشعار"</string>
     <string name="twilight_service" msgid="8964898045693187224">"خدمة الغسق"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"أداة التعرّف على المنطقة الزمنية (ليس هناك حاجة للاتصال بالشبكة)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"‏خدمة تعديل وقت GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"سيتم محو بيانات جهازك."</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"تعذّر استخدام تطبيق المشرف. سيتم محو بيانات جهازك الآن.\n\nإذا كانت لديك أسئلة، اتصل بمشرف مؤسستك."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"تم إيقاف الطباعة بواسطة <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2341,12 +2341,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"يمكنك الآن تكبير الشاشة كلها أو جزء منها."</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"التفعيل من خلال \"الإعدادات\""</string>
     <string name="dismiss_action" msgid="1728820550388704784">"إغلاق"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"‏للمتابعة، يحتاج &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; إلى الوصول إلى ميكروفون الجهاز."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"‏للمتابعة، يحتاج تطبيق &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; إلى الوصول إلى كاميرا الجهاز."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"تفعيل"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"الخصوصية في جهاز الاستشعار"</string>
 </resources>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index b6eb959..0a9ef97 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"কোৱল ৱাই-ফাই"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ক্ৰছ-ছিম কলিং"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ফৰৱাৰ্ড কৰা নহ\'ল"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> ছেকেণ্ডৰ পাছত"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index f7e4169..e91e2b7 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Yalnız Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Çarpaz SİM Zəngi"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Yedək Zəng"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönləndirilmədi"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> saniyə sonra"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensor Bildiriş Xidməti"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Alaqaranlıq Xidməti"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Saat Qurşağı Aşkarlayıcısı (Bağlantı yoxdur)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS Zaman Güncəlləmə Xidməti"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Cihazınız təmizlənəcəkdir"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Admin tətbiqini istifadə etmək mümkün deyil. Cihaz indi təmizlənəcək.\n\nSualınız varsa, təşkilatın admini ilə əlaqə saxlayın."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Çap <xliff:g id="OWNER_APP">%s</xliff:g> tərəfindən deaktiv edildi."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"İndi ekranı qismən və ya tam şəkildə böyüdə bilərsiniz"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Ayarlarda aktiv edin"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Qapadın"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Davam etmək üçün &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; tətbiqi cihazın mikrofonuna giriş tələb edir."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Davam etmək üçün &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; tətbiqi cihazın kamerasına giriş tələb edir."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aktiv edin"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensor Məxfiliyi"</string>
 </resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 76a278a..a490a6c 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -149,7 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Pozivi sa više SIM kartica za operatera <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> rezervni način za pozivanje"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunde/i"</string>
@@ -205,8 +205,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Usluga obaveštenja senzora"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Usluga Sumrak"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detektor vremenske zone (nema internet veze)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS usluga za ažuriranje vremena"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Uređaj će biti obrisan"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Ne možete da koristite ovu aplikaciju za administratore. Uređaj će sada biti obrisan.\n\nAko imate pitanja, kontaktirajte administratora organizacije."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Štampanje je onemogućila aplikacija <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2239,12 +2238,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Možete da uvećate deo ekrana ili ceo ekran"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Uključite u Podešavanjima"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Odbaci"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"&lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; zahteva pristup mikrofonu uređaja radi nastavljanja."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; zahteva pristup kameri uređaja radi nastavljanja."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Uključi"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privatnost senzora"</string>
 </resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index deb9218..80f67d0 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -150,7 +150,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Толькі Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Тэлефанія паміж SIM-картамі"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не пераадрасоўваецца"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> праз <xliff:g id="TIME_DELAY">{2}</xliff:g> с."</string>
@@ -207,8 +208,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Служба апавяшчэнняў датчыка"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Служба Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Дэтэктар часавога пояса (няма падключэння)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Служба абнаўлення часу GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Даныя вашай прылады будуць сцерты"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Немагчыма выкарыстоўваць праграму адміністравання. Звесткі на вашай прыладзе будуць выдалены.\n\nКалі ў вас ёсць пытанні, звярніцеся да адміністратара арганізацыі."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Друк адключаны ўладальнікам праграмы <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2273,12 +2273,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Цяпер можна павялічваць увесь экран ці яго частку."</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Уключыць у Наладах"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Адхіліць"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Каб працягнуць, дайце праграме &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ да мікрафона прылады."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Каб працягнуць, дайце праграме &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ да камеры прылады."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Уключыць"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Прыватнасць інфармацыі з датчыка"</string>
 </resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 9ee0d99..20342e0 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Обаждания през друга SIM карта от <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Обаждания през друга SIM карта от <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Не е пренасочено"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> след <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Услуга за известия за сензорите"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Услуга Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Инструмент за установяване на часовата зона (няма връзка)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Услуга на GNSS за актуализиране на часа"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Данните на устройството ви ще бъдат изтрити"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Приложението за администриране не може да се използва. Сега данните на устройството ви ще бъдат изтрити.\n\nАко имате въпроси, свържете се с администратора на организацията си."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Отпечатването е деактивиранo от <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Можете да увеличите целия екран или част от него"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Включете от настройките"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Отхвърляне"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"За да продължите, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; се нуждае от достъп до микрофона на устройството ви."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"За да продължите, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; се нуждае от достъп до камерата на устройството ви."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Включване"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Поверителност на сензорните данни"</string>
 </resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 3ce96c7..51d09b2 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"শুধুমাত্র ওয়াই-ফাই"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ক্রস সিম কল করা"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ফরওয়ার্ড করা হয়নি"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> সেকেন্ড পরে"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"সেন্সর বিজ্ঞপ্তি পরিষেবা"</string>
     <string name="twilight_service" msgid="8964898045693187224">"গোধূলি পরিষেবা"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"টাইম জোন ডিটেক্টর (কানেকশন নেই)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS সময় আপডেট পরিষেবা"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"আপনার ডিভাইসটি মুছে ফেলা হবে"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"অ্যাডমিন অ্যাপটি ব্যবহার করা যাবে না। আপনার ডিভাইসে থাকা সবকিছু এখন মুছে ফেলা হবে।\n\nকোনও প্রশ্ন থাকলে আপনার প্রতিষ্ঠানের অ্যাডমিনের সাথে যোগাযোগ করুন।"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> প্রিন্টিং বন্ধ রেখেছে।"</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"এখন আপনি কিছু বা সবকটি স্ক্রিন বড় করে দেখতে পারেন"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"সেটিংস থেকে চালু করুন"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"বাতিল করুন"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"চালিয়ে যেতে, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; আপনার ডিভাইসের মাইক্রোফোন অ্যাক্সেস করতে চায়।"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"চালিয়ে যেতে, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; আপনার ডিভাইসের ক্যামেরা অ্যাক্সেস করতে চায়।"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"চালু করুন"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"সেন্সর গোপনীয়তা"</string>
 </resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 6c54eb1..989e2bb 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -149,7 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo WiFi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> – pozivanje na različitim SIM-ovima"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – pomoćno pozivanje"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđen"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> za <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 6d81104..e9c55d7 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Només Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Trucades entre targetes SIM de l\'operador <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Trucades alternatives (<xliff:g id="SPN">%s</xliff:g>)"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no s\'ha desviat"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> després de <xliff:g id="TIME_DELAY">{2}</xliff:g> segons"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Servei de notificacions de sensor"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Servei Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detector de zona horària (sense connectivitat)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Servei GNSS d\'actualització horària"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"El contingut del dispositiu s\'esborrarà"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"No es pot utilitzar l\'aplicació d\'administració. S\'esborraran les dades del dispositiu.\n\nSi tens cap dubte, contacta amb l\'administrador de la teva organització."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ha desactivat la impressió."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Ara pots ampliar la pantalla completa o una part"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activa a Configuració"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Ignora"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Per continuar, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necessita accedir al micròfon del dispositiu."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Per continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necessita accedir a la càmera del dispositiu."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Activa"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privadesa dels sensors"</string>
 </resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 00b607c..071bbfd 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -150,7 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Pouze Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> – volání napříč SIM kartami"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Záložní volání"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
@@ -207,8 +207,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Služba oznámení ze senzoru"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Služba detekce soumraku"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detektor časového pásma (bez připojení)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS – služba pro aktualizaci času"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Zařízení bude vymazáno"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Aplikaci pro správu nelze použít. Zařízení teď bude vymazáno.\n\nV případě dotazů vám pomůže administrátor organizace."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Aplikace <xliff:g id="OWNER_APP">%s</xliff:g> tisk zakazuje."</string>
@@ -2273,12 +2272,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Nyní můžete zvětšit celou obrazovku nebo její část"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Zapnout v Nastavení"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Zavřít"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Než budete pokračovat, udělte aplikaci &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; přístup k mikrofonu na zařízení."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Než budete pokračovat, udělte aplikaci &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; přístup k fotoaparátu na zařízení."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Zapnout"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Ochrana soukromí – senzor"</string>
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 6e9a7e7..b760f5b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Kun Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Opkald på tværs af SIM-kort"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Alternativ løsning til opkald leveret af <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Tjenesten Sensor Notification"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Tjenesten Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Tidszoneregistrering (ingen forbindelse)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Tjeneste til opdatering af GNSS-tid"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Enheden slettes"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Administrationsappen kan ikke bruges. Enheden vil nu blive ryddet. \n\nKontakt din organisations administrator, hvis du har spørgsmål."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Udskrivning er deaktiveret af <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2207,12 +2206,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Du kan nu forstørre dele af eller hele skærmen"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Aktivér i Indstillinger"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Luk"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"&lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; skal have adgang til din enheds mikrofon, før den kan fortsætte."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; skal have adgang til din enheds kamera, før den kan fortsætte."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aktivér"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Beskyttelse af sensoroplysninger"</string>
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ef7e628..f8066e2 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Nur WLAN"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> SIM-übergreifende Anrufe"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nicht weitergeleitet"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> nach <xliff:g id="TIME_DELAY">{2}</xliff:g> Sekunden."</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensor Notification Service"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight Service"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Zeitzonen-Erkennung (keine Verbindung)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS-Zeitaktualisierungsdienst"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Die Daten auf deinem Gerät werden gelöscht."</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Die Admin-App kann nicht verwendet werden. Die Daten auf deinem Gerät werden nun gelöscht.\n\nBitte wende dich bei Fragen an den Administrator deiner Organisation."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Drucken wurde von <xliff:g id="OWNER_APP">%s</xliff:g> deaktiviert."</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Du kannst das Display teilweise oder ganz vergrößern"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"In den Einstellungen aktivieren"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Schließen"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Zum Fortfahren benötigt, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; Zugriff auf das Mikrofon deines Geräts."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Zum Fortfahren benötigt &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; Zugriff auf die Kamera deines Geräts."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aktivieren"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Datenschutz für Sensoren"</string>
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 614fc95..4f3c8a9 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Μόνο Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Κλήση με πολλές SIM <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Δημιουργία αντιγράφων ασφαλείας κλήσεων"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Δεν προωθήθηκε"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> μετά από <xliff:g id="TIME_DELAY">{2}</xliff:g> δευτερόλεπτα"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Υπηρεσία ειδοποίησης αισθητήρα"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Υπηρεσία Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Εντοπισμός ζώνης ώρας (χωρίς συνδεσιμότητα)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Υπηρεσία ενημέρωσης ώρας GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Η συσκευή σας θα διαγραφεί"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Δεν είναι δυνατή η χρήση της εφαρμογής διαχειριστή. Η συσκευή σας θα διαγραφεί.\n\nΕάν έχετε ερωτήσεις, επικοινωνήστε με τον διαχειριστή του οργανισμού σας."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Η εκτύπωση απενεργοποιήθηκε από τον χρήστη <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Μεγεθύνετε μέρος ή ολόκληρη την οθόνη σας"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Ενεργοποίηση στις Ρυθμίσεις"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Παράβλεψη"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Για να συνεχίσετε, η εφαρμογή &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; χρειάζεται πρόσβαση στο μικρόφωνο της συσκευής σας."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Για να συνεχίσετε, η εφαρμογή &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; χρειάζεται πρόσβαση στην κάμερα της συσκευής σας."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Ενεργοποίηση"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Απόρρητο αισθητήρα"</string>
 </resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 6d760d0..9f3bc7d 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi only"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Cross-SIM calling"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup calling"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index c529338..766e372 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi only"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Cross-SIM calling"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup calling"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 1d4a614..16da211 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi only"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Cross-SIM calling"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup calling"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 0b7e11a..150830e 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi only"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Cross-SIM calling"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup calling"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 0fbfcc3..e952cac 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‏‎‎‏‎Wi-Fi only‎‏‎‎‏‎"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="SPN">%s</xliff:g>‎‏‎‎‏‏‏‎ Cross-SIM Calling‎‏‎‎‏‎"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="SPN">%s</xliff:g>‎‏‎‎‏‏‏‎ Backup Calling‎‏‎‎‏‎"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‏‎<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>‎‏‎‎‏‏‏‎: Not forwarded‎‏‎‎‏‎"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>‎‏‎‎‏‏‏‎: ‎‏‎‎‏‏‎<xliff:g id="DIALING_NUMBER">{1}</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‎‏‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>‎‏‎‎‏‏‏‎: ‎‏‎‎‏‏‎<xliff:g id="DIALING_NUMBER">{1}</xliff:g>‎‏‎‎‏‏‏‎ after ‎‏‎‎‏‏‎<xliff:g id="TIME_DELAY">{2}</xliff:g>‎‏‎‎‏‏‏‎ seconds‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 75973ae..c5ff193 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Llamadas entre tarjetas SIM de <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Llamada de copia de seguridad de <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> después de <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index e84fd00..108c3db 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Llamadas entre tarjetas SIM de <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Llamadas de reserva de <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> transcurridos <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Servicio de notificación de sensor"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Servicio de Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detector de zona horaria (sin conexión)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Servicio de actualización de tiempo GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Tu dispositivo se borrará"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"No se puede utilizar la aplicación de administración. Se borrarán todos los datos del dispositivo.\n\nSi tienes alguna pregunta, ponte en contacto con el administrador de tu organización."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ha inhabilitado la impresión."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Ahora puedes ampliar toda la pantalla o una parte"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activar en Ajustes"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Cerrar"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Para continuar, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necesita tener acceso al micrófono del dispositivo."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Para continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necesita tener acceso a la cámara del dispositivo."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Activar"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privacidad del sensor"</string>
 </resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index f7d053d..70862d4 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Ainult WiFi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> – helistamine mitme SIM-kaardi kaudu"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – helistamise varuviis"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole suunatud"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi pärast"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Anduri märguande teenus"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Teenus Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Ajavööndi tuvastaja (ühenduvus puudub)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS-i aja värskendamise teenus"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Seade kustutatakse"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Administraatori rakendust ei saa kasutada. Teie seade tühjendatakse nüüd.\n\nKui teil on küsimusi, võtke ühendust organisatsiooni administraatoriga."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Rakendus <xliff:g id="OWNER_APP">%s</xliff:g> on printimise keelanud."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Nüüd saab suurendada kogu ekraanikuva või osa sellest"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Lülitage sisse menüüs Seaded"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Loobu"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Jätkamiseks vajab rakendus &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; juurdepääsu teie seadme mikrofonile."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Jätkamiseks vajab rakendus &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; juurdepääsu teie seadme kaamerale."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Lülita sisse"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Anduri privaatsus"</string>
 </resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index b22a777..bc691a5 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wifi-sarea soilik"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> operadorearen beste SIM txartel batetik deitzeko aukera"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ez da desbideratu"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> zenbakira <xliff:g id="TIME_DELAY">{2}</xliff:g> segundotan"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sentsorearen jakinarazpen-zerbitzua"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Ilunabarreko zerbitzua"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Ordu-zonaren hautemailea (ez zaude konektatuta sarera)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS ordua eguneratzeko zerbitzua"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Gailuko datuak ezabatu egingo dira"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Ezin da erabili administratzeko aplikazioa. Ezabatu egingo da gailuko eduki guztia.\n\nZalantzarik baduzu, jarri erakundeko administratzailearekin harremanetan."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> aplikazioak desgaitu egin du inprimatzeko aukera."</string>
@@ -1589,7 +1589,7 @@
     <string name="display_manager_built_in_display_name" msgid="1015775198829722440">"Pantaila integratua"</string>
     <string name="display_manager_hdmi_display_name" msgid="1022758026251534975">"HDMI pantaila"</string>
     <string name="display_manager_overlay_display_name" msgid="5306088205181005861">"<xliff:g id="ID">%1$d</xliff:g>. gainjartzea"</string>
-    <string name="display_manager_overlay_display_title" msgid="1480158037150469170">"<xliff:g id="NAME">%1$s</xliff:g>: <xliff:g id="WIDTH">%2$d</xliff:g> x <xliff:g id="HEIGHT">%3$d</xliff:g>, <xliff:g id="DPI">%4$d</xliff:g> dpi"</string>
+    <string name="display_manager_overlay_display_title" msgid="1480158037150469170">"<xliff:g id="NAME">%1$s</xliff:g>: <xliff:g id="WIDTH">%2$d</xliff:g> × <xliff:g id="HEIGHT">%3$d</xliff:g>, <xliff:g id="DPI">%4$d</xliff:g> dpi"</string>
     <string name="display_manager_overlay_display_secure_suffix" msgid="2810034719482834679">", segurua"</string>
     <string name="kg_forgot_pattern_button_text" msgid="406145459223122537">"Eredua ahaztu zaizu"</string>
     <string name="kg_wrong_pattern" msgid="1342812634464179931">"Eredu okerra"</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Orain, pantaila osoa edo haren zati bat handi dezakezu"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Aktibatu ezarpenetan"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Baztertu"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Aurrera egiteko, gailuaren mikrofonoa atzitzeko baimena behar du &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; aplikazioak."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Aurrera egiteko, gailuaren kamera atzitzeko baimena behar du &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; aplikazioak."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aktibatu"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sentsoreen pribatutasuna"</string>
 </resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 98d715b..6a0e454 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"‏فقط Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"تماس بین سیم‌کارت <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> تماس پشتیبان"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: هدایت نشده"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> پس از <xliff:g id="TIME_DELAY">{2}</xliff:g> ثانیه"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 159e1b4..8e5d9b4 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Vain Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"SIM-korttien väliset puhelut: <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Puheluiden varavaihtoehto"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ei siirretty"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunnin päästä"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Anturin ilmoituspalvelu"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight-palvelu"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Aikavyöhykkeen tunnistin (ei yhteyttä)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS-ajanpäivityspalvelu"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Laitteen tiedot poistetaan"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Hallintasovellusta ei voi käyttää. Laitteen tiedot pyyhitään.\n\nPyydä ohjeita järjestelmänvalvojaltasi."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> on poistanut tulostuksen käytöstä."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Voit nyt suurentaa näytön osittain tai kokonaan"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Laita päälle asetuksista"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Hylkää"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Jotta voit jatkaa, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; tarvitsee pääsyn laitteesi mikrofoniin."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Jotta voit jatkaa, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; tarvitsee pääsyn laitteesi kameraan."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Laita päälle"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Anturin tietosuoja"</string>
 </resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index e34bf8b..849f9594 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi seulement"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Appels multiSIM avec <xliff:g id="SPN">%s</xliff:g>"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : <xliff:g id="DIALING_NUMBER">{1}</xliff:g> au bout de <xliff:g id="TIME_DELAY">{2}</xliff:g> secondes"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index b6eade2..60138c4 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi uniquement"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Appels par cartes SIM croisées <xliff:g id="SPN">%s</xliff:g>"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : <xliff:g id="DIALING_NUMBER">{1}</xliff:g> au bout de <xliff:g id="TIME_DELAY">{2}</xliff:g> secondes"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Service de notification du capteur"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Service Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Outil de détection du fuseau horaire (aucune connectivité)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Service de mise à jour de l\'heure GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Les données de votre appareil vont être effacées"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Impossible d\'utiliser l\'application d\'administration. Les données de votre appareil vont maintenant être effacées.\n\nSi vous avez des questions, contactez l\'administrateur de votre organisation."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Impression désactivée par <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Vous pouvez agrandir tout ou partie de l\'écran"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activer dans les paramètres"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Fermer"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Pour continuer, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; a besoin d\'accéder au micro de votre appareil."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Pour continuer, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; a besoin d\'accéder à l\'appareil photo de votre appareil."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Activer"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Confidentialité du capteur"</string>
 </resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 04b4236..eadac9e 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Só por wifi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Chamadas doutra SIM a través desta (<xliff:g id="SPN">%s</xliff:g>)"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas de <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: non desviada"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> tras <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Servizo de notificacións dos sensores"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Servizo Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detector de fuso horario (non require conexión)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Servizo de actualización horaria mediante o GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Borrarase o teu dispositivo"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Non se pode utilizar a aplicación de administración. Borrarase o teu dispositivo.\n\nSe tes preguntas, contacta co administrador da organización."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> desactivou a impresión."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Agora podes ampliar toda a pantalla ou parte dela"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activar en Configuración"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Ignorar"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Para continuar, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; precisa acceder ao micrófono do dispositivo."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Para continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; precisa acceder á cámara do dispositivo."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Activar"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privacidade do sensor"</string>
 </resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 7bab2ad..86416bc 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ફક્ત વાઇ-ફાઇ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ક્રૉસ સિમ કૉલિંગ"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ફોરવર્ડ કર્યો નથી"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="TIME_DELAY">{2}</xliff:g> સેકન્ડ પછી <xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"સેન્સર નોટિફિકેશન સેવા"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ટ્વાઇલાઇટ સેવા"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"સમય ઝોન શોધવાની સુવિધા (કનેક્ટિવિટી જરૂરી નથી)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS સમય અપડેટ કરવાની સેવા"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"તમારું ઉપકરણ કાઢી નાખવામાં આવશે"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"વ્યવસ્થાપક ઍપનો ઉપયોગ કરી શકાશે નહીં. તમારું ઉપકરણ હવે કાઢી નાખવામાં આવશે.\n\nજો તમને પ્રશ્નો હોય, તો તમારી સંસ્થાના વ્યવસ્થાપકનો સંપર્ક કરો."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> દ્વારા પ્રિન્ટ કરવાનું બંધ કરાયું છે."</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"હવે તમે તમારી કેટલીક કે આખી સ્ક્રીનને મોટી કરી શકો છો"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"સેટિંગમાં ચાલુ કરો"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"છોડી દો"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"ચાલુ રાખવા માટે, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;ને તમારા ડિવાઇસના માઇક્રોફોનના ઍક્સેસની જરૂર છે."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"ચાલુ રાખવા માટે, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;ને તમારા ડિવાઇસના કૅમેરાના ઍક્સેસની જરૂર છે."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ચાલુ કરો"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"સેન્સર પ્રાઇવસી સંબંધિત નોટિફિકેશન"</string>
 </resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index dbdfd4a..5f0a8f8 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -57,7 +57,7 @@
     <string name="imei" msgid="2157082351232630390">"आईएमईआई"</string>
     <string name="meid" msgid="3291227361605924674">"MEID"</string>
     <string name="ClipMmi" msgid="4110549342447630629">"इनकमिंग कॉलर आईडी"</string>
-    <string name="ClirMmi" msgid="6752346475055446417">"आउटगोइंग कॉल करने पर अपना कॉलर आईडी छिपाएं"</string>
+    <string name="ClirMmi" msgid="6752346475055446417">"आउटगोइंग कॉल करने पर अपना नाम और नंबर छिपाएं"</string>
     <string name="ColpMmi" msgid="4736462893284419302">"कनेक्ट किया गया लाइन आईडी"</string>
     <string name="ColrMmi" msgid="5889782479745764278">"कनेक्ट किया गया लाइन आईडी प्रतिबंध"</string>
     <string name="CfMmi" msgid="8390012691099787178">"कॉल आगे भेजना"</string>
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"केवल वाई-फ़ाई"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> क्रॉस सिम कॉलिंग"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> बैक अप कॉलिंग"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> सेकंड के बाद"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"सेंसर से जुड़ी सूचना सेवा"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ट्वाइलाइट समय बताने वाली सेवा"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"समय क्षेत्र का पता लगाने वाली सुविधा (ऑफ़लाइन होने पर)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS समय अपडेट सेवा"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"आपके डिवाइस को मिटा दिया जाएगा"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"एडमिन ऐप्लिकेशन का इस्तेमाल नहीं किया जा सकता. आपके डिवाइस पर मौजूद डेटा अब मिटा दिया जाएगा.\n\nअगर आप कुछ पूछना चाहते हैं तो, अपने संगठन के एडमिन से संपर्क करें."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ने प्रिंटिंग सुविधा बंद कर दी है."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"अब अपनी पूरी स्क्रीन या कुछ हिस्से को ज़ूम करके देख सकते हैं"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"सेटिंग में जाकर, इस सुविधा को चालू करें"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"खारिज करें"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"जारी रखने के लिए, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; को आपके डिवाइस का माइक्रोफ़ोन ऐक्सेस करने की ज़रूरत है."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"जारी रखने के लिए, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; को आपके डिवाइस का कैमरा ऐक्सेस करने की ज़रूरत है."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"चालू करें"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"सेंसर से जुड़ी निजता के बारे में सूचना"</string>
 </resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 56f3978..e1ae1e2 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -149,7 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Preusmjeravanje poziva na drugi SIM"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Rezervni način telefoniranja"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđeno"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string>
@@ -205,8 +205,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Usluga Obavijesti senzora"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Usluga Sumrak"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detektor vremenske zone (nije povezan)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS – usluga ažuriranja vremena"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Uređaj će se izbrisati"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Administratorska aplikacija ne može se upotrebljavati. Uređaj će se izbrisati.\n\nAko imate pitanja, obratite se administratoru organizacije."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Ispis je onemogućila aplikacija <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2239,12 +2238,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Sad možete povećati dio zaslona ili cijeli zaslon"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Uključite u Postavkama"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Odbaci"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Da bi nastavila s radom, aplikacija &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; treba pristupiti mikrofonu vašeg uređaja."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Da bi nastavila s radom, aplikacija &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; treba pristupiti fotoaparatu vašeg uređaja."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Uključi"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privatnost senzora"</string>
 </resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 96997c0..3c04bec 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Csak Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> SIM-eken keresztüli hívás"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> másodlagos hívási lehetőség"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nincs átirányítva"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> másodperc után"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Szenzoros értesítési szolgáltatás"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight szolgáltatás"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Időzóna-felismerő (Offline)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS időfrissítési szolgáltatás"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"A rendszer törölni fogja eszközét"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"A rendszergazdai alkalmazás nem használható. A rendszer most törli az eszközt.\n\nKérdéseivel forduljon szervezete rendszergazdájához."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"A(z) <xliff:g id="OWNER_APP">%s</xliff:g> letiltotta a nyomtatást."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Ezután nagyíthatja a képernyőt vagy egy részét"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Bekapcsolás a Beállításokban"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Elvetés"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"A folytatáshoz a(z) &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; alkalmazásnak hozzáférésre van szüksége az eszköze mikrofonjához."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"A folytatáshoz a(z) &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; alkalmazásnak hozzáférésre van szüksége az eszköze kamerájához."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Bekapcsolás"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Érzékelőkkel kapcsolatos adatvédelem"</string>
 </resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 996454a..bdd2387 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Միայն Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> SIM քարտերով խաչաձև զանգեր"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> զանգելու պահեստային տարբերակ"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> վայրկյանից"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Տվիչների ծանուցումների մշակման ծառայություն"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Մթնշաղի սկիզբը որոշող ծառայություն"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Ժամային գոտու դետեկտոր (աշխատում է առանց ինտերնետի)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Ժամանակի թարմացման GNSS ծառայություն"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Ձեր սարքը ջնջվելու է"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Հնարավոր չէ օգտագործել ադմինիստրատորի հավելվածը։ Ձեր սարքից բոլոր տվյալները կջնջվեն։\n\nՀարցեր ունենալու դեպքում դիմեք ձեր կազմակերպության ադմինիստրատորին։"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Տպումն անջատված է <xliff:g id="OWNER_APP">%s</xliff:g> հավելվածի կողմից։"</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Այժմ կարող եք խոշորացնել ամբողջ էկրանը կամ դրա մի մասը"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Միացնել կարգավորումներում"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Փակել"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Շարունակելու համար &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; հավելվածին անհրաժեշտ է սարքի խոսափողի օգտագործման թույլտվություն։"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Շարունակելու համար &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; հավելվածին անհրաժեշտ է ձեր սարքի տեսախցիկի օգտագործման թույլտվություն։"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Միացնել"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Տվիչների գաղտնիություն"</string>
 </resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index bb91a6c..c84bc13 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Khusus Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Panggilan Lintas-SIM <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Panggilan Cadangan <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak diteruskan"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> setelah <xliff:g id="TIME_DELAY">{2}</xliff:g> detik"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Layanan Notifikasi Sensor"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Layanan Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Pendeteksi Zona Waktu (Tidak ada konektivitas)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Layanan Pembaruan Waktu GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Perangkat akan dihapus"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Aplikasi admin tidak dapat digunakan. Perangkat Anda kini akan dihapus.\n\nJika ada pertanyaan, hubungi admin organisasi."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Fitur pencetakan dinonaktifkan oleh <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Anda bisa memperbesar sebagian atau seluruh layar"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Aktifkan di Setelan"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Tutup"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Untuk melanjutkan, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; memerlukan akses ke mikrofon perangkat."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Untuk melanjutkan, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; memerlukan akses ke kamera perangkat."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aktifkan"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privasi Sensor"</string>
 </resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index ec11ad2..84611a2 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi eingöngu"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Skipt á milli SIM-korta"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Varasímtöl (<xliff:g id="SPN">%s</xliff:g>)"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ekki áframsent"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> eftir <xliff:g id="TIME_DELAY">{2}</xliff:g> sekúndur"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Tilkynningaþjónusta nema"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Ljósaskiptaþjónusta"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Tímabeltisgreinir (engin tenging)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Tímastillingarþjónusta hnattræna gervihnattaleiðsögukerfisins (GNSS)"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Tækið verður hreinsað"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Ekki er hægt að nota stjórnunarforritið. Tækinu verður eytt.\n\nEf spurningar vakna skaltu hafa samband við kerfisstjóra fyrirtækisins."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> lokaði á prentun."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Nú geturðu stækkað allan skjáinn eða hluta hans"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Kveikja á í stillingum"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Hunsa"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Til að halda áfram þarf &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; aðgang að hljóðnema tækisins."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Til að halda áfram þarf &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; aðgang að myndavél tækisins."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Kveikja"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Persónuvernd skynjara"</string>
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index df35881..2fcc273 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -57,7 +57,7 @@
     <string name="imei" msgid="2157082351232630390">"IMEI"</string>
     <string name="meid" msgid="3291227361605924674">"MEID"</string>
     <string name="ClipMmi" msgid="4110549342447630629">"ID chiamante in entrata"</string>
-    <string name="ClirMmi" msgid="6752346475055446417">"Nascondi ID chiamante in uscita"</string>
+    <string name="ClirMmi" msgid="6752346475055446417">"Nascondi ID chiamante per le chiamate in uscita"</string>
     <string name="ColpMmi" msgid="4736462893284419302">"ID linea connessa"</string>
     <string name="ColrMmi" msgid="5889782479745764278">"Limitazione ID linea connessa"</string>
     <string name="CfMmi" msgid="8390012691099787178">"Deviazione chiamate"</string>
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Solo Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Chiamate tramite più SIM <xliff:g id="SPN">%s</xliff:g>"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: inoltro non effettuato"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> dopo <xliff:g id="TIME_DELAY">{2}</xliff:g> secondi"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Servizio di notifica dei sensori"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Servizio Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Rilevatore di fuso orario (connessione a Internet non necessaria)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Servizio di aggiornamento dell\'orario GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Il dispositivo verrà resettato"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Impossibile usare l\'app di amministrazione. Il dispositivo verrà resettato.\n\nPer eventuali domande, contatta l\'amministratore della tua organizzazione."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Stampa disattivata da <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Puoi ingrandire lo schermo in parte o per intero"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Attiva nelle Impostazioni"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Ignora"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Per continuare, l\'app &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; deve accedere al microfono del dispositivo."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Per continuare, l\'app &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; deve accedere alla videocamera del dispositivo."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Attiva"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privacy relativa ai sensori"</string>
 </resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index f80750a..f104d44 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -150,7 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"‏Wi-Fi בלבד"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"‏העברת שיחות בין כרטיסי SIM של <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"אמצעי גיבוי להתקשרות באמצעות <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ללא העברה"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> כעבור <xliff:g id="TIME_DELAY">{2}</xliff:g> שניות"</string>
@@ -207,8 +207,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"שירות להתראות מחיישנים"</string>
     <string name="twilight_service" msgid="8964898045693187224">"שירות דמדומים"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"מזהה אזור זמן (ללא צורך בקישוריות)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"‏שירות עדכון הזמן של GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"תתבצע מחיקה של המכשיר"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"לא ניתן להשתמש באפליקציה של מנהל המערכת.\n\nאם יש לך שאלות, יש ליצור קשר עם מנהל המערכת של הארגון."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"ההדפסה הושבתה על ידי <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2273,12 +2272,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"עכשיו אפשר להגדיל את המסך או חלקים ממנו"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"הפעלה בהגדרות"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"סגירה"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"‏כדי להמשיך, האפליקציה &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; צריכה גישה למיקרופון של המכשיר שלך."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"‏כדי להמשיך, האפליקציה &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; צריכה גישה למצלמה של המכשיר שלך."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"הפעלה"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"פרטיות חיישנים"</string>
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 04a0058..69822ac 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fiのみ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Cross-SIM 通話"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> 通話のバックアップ"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g> (<xliff:g id="TIME_DELAY">{2}</xliff:g>秒後)"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"センサー通知サービス"</string>
     <string name="twilight_service" msgid="8964898045693187224">"トワイライト サービス"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Time Zone Detector(未接続)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS Time Update Service"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"デバイスのデータが消去されます"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"管理アプリを使用できません。デバイスのデータはこれから消去されます。\n\nご不明な点がある場合は、組織の管理者にお問い合わせください。"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"「<xliff:g id="OWNER_APP">%s</xliff:g>」により印刷は無効にされています。"</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"画面の一部または全体を拡大できるようになりました"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"[設定] で ON にする"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"閉じる"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"続行するには、&lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; にデバイスのマイクへのアクセスを許可する必要があります。"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"続行するには、&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; にデバイスのカメラへのアクセスを許可する必要があります。"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ON にする"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"センサー プライバシー"</string>
 </resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 6a128ab..6a7d563 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"მხოლოდ Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> SIM-თაშორისი დარეკვა"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> დარეკვის სარეზერვო ხერხი"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: არ არის გადამისამართებული"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> წამის შემდეგ"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"სენსორის შეტყობინების სერვისი"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight სერვისი"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"სასაათო სარტყლის დეტექტორი (კავშირის გარეშე)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS დროის განახლების სერვისი"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"თქვენი მოწყობილობა წაიშლება"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"ადმინისტრატორის აპის გამოყენება ვერ მოხერხდება. თქვენი მოწყობილობა ახლა ამოიშლება.\n\nთუ შეკითხვები გაქვთ, დაუკავშირდით თქვენი ორგანიზაციის ადმინისტრატორს."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"ბეჭდვა გათიშულია <xliff:g id="OWNER_APP">%s</xliff:g>-ის მიერ."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"ახლა შეგიძლიათ, გაადიდოთ ეკრანი ან მისი ნაწილი"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"ჩართვა პარამეტრებში"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"უარყოფა"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"გასაგრძელებლად &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;-ს თქვენი მოწყობილობის მიკროფონზე წვდომა სჭირდება."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"გასაგრძელებლად &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;-ს თქვენი მოწყობილობის კამერაზე წვდომა სჭირდება."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ჩართვა"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"სენსორის კონფიდენციალურობა"</string>
 </resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 8000fc4..aec2312 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Тек Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> SIM карталары арасында қоңырау шалу"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Басқа нөмірге бағытталмады"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>  <xliff:g id="TIME_DELAY">{2}</xliff:g> секундтан кейін"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Датчик хабарландыруы қызметі"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight қызметі"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Уақыт белдеуін анықтағыш (қосылу мүмкіндігі жоқ)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS уақыт жаңарту жүйесі"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Құрылғыңыздағы деректер өшіріледі"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Әкімші қолданбасын пайдалану мүмкін емес. Қазір құрылғыдағы деректер өшіріледі\n\nСұрақтарыңыз болса, ұйым әкімшісіне хабарласыңыз."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Басып шығаруды <xliff:g id="OWNER_APP">%s</xliff:g> өшірді."</string>
@@ -1809,8 +1809,8 @@
     <string name="confirm_battery_saver" msgid="5247976246208245754">"Жарайды"</string>
     <string name="battery_saver_description_with_learn_more" msgid="4424488535318105801">"Батарея жұмысының ұзақтығын арттыру үшін Батареяны үнемдеу режимі:\n\n•қараңғы тақырыпты іске қосады;\n•фондық әрекеттерді, кейбір көрнекі әсерлерді және \"Ok Google\" сияқты басқа да функцияларды өшіреді не шектейді.\n\n"<annotation id="url">"Толығырақ"</annotation></string>
     <string name="battery_saver_description" msgid="6794188153647295212">"Батарея ұзағырақ жұмыс істеуі үшін, Battery Saver:\n\n• қараңғы тақырыпты қосады;\n•фондық жұмысты, кейбір визуалды әсерлерді және \"Ok Google\" сияқты басқа функцияларды өшіреді не шектейді."</string>
-    <string name="data_saver_description" msgid="4995164271550590517">"Дерек шығынын азайту үшін Data Saver функциясы кейбір қолданбаларға деректерді фондық режимде жіберуге және алуға жол бермейді. Ашық тұрған қолданба деректерді пайдаланады, бірақ шектеулі шамада (мысалы, кескіндер оларды түрткенге дейін көрсетілмейді)."</string>
-    <string name="data_saver_enable_title" msgid="7080620065745260137">"Data Saver функциясын қосу керек пе?"</string>
+    <string name="data_saver_description" msgid="4995164271550590517">"Дерек шығынын азайту үшін Трафикті үнемдеу функциясы кейбір қолданбаларға деректерді фондық режимде жіберуге және алуға жол бермейді. Ашық тұрған қолданба деректерді пайдаланады, бірақ шектеулі шамада (мысалы, кескіндер оларды түрткенге дейін көрсетілмейді)."</string>
+    <string name="data_saver_enable_title" msgid="7080620065745260137">"Трафикті үнемдеу функциясын қосу керек пе?"</string>
     <string name="data_saver_enable_button" msgid="4399405762586419726">"Қосу"</string>
     <plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="2877101784123058273">
       <item quantity="other">%1$d минут бойы (<xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g> дейін)</item>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Енді экранның бір бөлігін не барлығын ұлғайта аласыз."</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Параметрлер бөлімінен қосу"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Қабылдамау"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Жалғастыру үшін &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; қолданбасы құрылғыңыздың микрофонына рұқсат алу керек."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Жалғастыру үшін &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; қолданбасы құрылғыңыздың камерасына рұқсат алу керек."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Қосу"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Датчикке қатысты құпиялылық"</string>
 </resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index e6a2fc6..d1400b9 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi តែប៉ុណ្ណោះ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"ការហៅទូរសព្ទ​ឆ្លងស៊ីម​តាមរយៈ <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"ការហៅទូរសព្ទ​បម្រុង <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> ៖ មិន​បាន​បញ្ជូន​បន្ត"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> បន្ទាប់​ពី <xliff:g id="TIME_DELAY">{2}</xliff:g> វិនាទី"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"សេវាកម្ម​ជូនដំណឹង​ឧបករណ៍​ចាប់សញ្ញា"</string>
     <string name="twilight_service" msgid="8964898045693187224">"សេវាកម្ម​ព្រលប់"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"ឧបករណ៍សម្គាល់​ល្វែងម៉ោង (គ្មានការតភ្ជាប់ទេ)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"សេវាកម្ម​ធ្វើបច្ចុប្បន្នភាព​ពេលវេលា GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"ឧបករណ៍របស់អ្នកនឹងត្រូវបានលុប"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"មិនអាច​ប្រើ​កម្មវិធី​អ្នកគ្រប់គ្រង​បានទេ។ ឧបករណ៍​របស់អ្នក​នឹងលុប​ឥឡូវនេះ។\n\nប្រសិនបើ​អ្នកមាន​សំណួរផ្សេងៗ​ សូមទាក់ទង​ទៅអ្នក​គ្រប់គ្រង​ស្ថាប័ន​របស់​អ្នក។"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"ការបោះពុម្ព​ត្រូវបាន​បិទ​ដោយ <xliff:g id="OWNER_APP">%s</xliff:g> ។"</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"ឥឡូវនេះ អ្នកអាចពង្រីកផ្នែកខ្លះ ឬទាំងអស់នៃអេក្រង់របស់អ្នក"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"បើកនៅក្នុងការកំណត់"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ច្រានចោល"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"ដើម្បីបន្ត &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ត្រូវការសិទ្ធិចូលប្រើ​មីក្រូហ្វូន​របស់ឧបករណ៍អ្នក។"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"ដើម្បីបន្ត &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ត្រូវការសិទ្ធិ​ចូលប្រើ​កាមេរ៉ា​របស់ឧបករណ៍អ្នក។"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"បើក"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"ឯកជនភាព​ឧបករណ៍​ចាប់សញ្ញា"</string>
 </resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index f2d91f2..32e3c4d 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ವೈ-ಫೈ ಮಾತ್ರ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ಕ್ರಾಸ್-ಸಿಮ್ ಕರೆ ಮಾಡುವಿಕೆ"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> ಬ್ಯಾಕಪ್ ಕರೆ ಮಾಡುವಿಕೆ"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ಫಾರ್ವರ್ಡ್ ಮಾಡಲಾಗಿಲ್ಲ"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> ಸೆಕೆಂಡುಗಳ ನಂತರ <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"ಸೆನ್ಸರ್ ಅಧಿಸೂಚನೆ ಸೇವೆ"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ಟ್ವಿಲೈಟ್ ಸೇವೆ"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"ಸಮಯವಲಯ  ಡಿಟೆಕ್ಟರ್ (ಯಾವುದೇ ಸಂಪರ್ಕ ಕಲ್ಪಿಸುವಿಕೆ ಇಲ್ಲ)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS ಸಮಯದ ಅಪ್‌ಡೇಟ್ ಸೇವೆ"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"ನಿರ್ವಹಣೆ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಸಾಧನವನ್ನು ಇದೀಗ ಅಳಿಸಲಾಗುತ್ತದೆ.\n\nನಿಮ್ಮಲ್ಲಿ ಪ್ರಶ್ನೆಗಳಿದ್ದರೆ, ನಿಮ್ಮ ಸಂಸ್ಥೆಯ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ಮೂಲಕ ಪ್ರಿಂಟಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"ಈಗ ಕೆಲವು ಅಥವಾ ಎಲ್ಲಾ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಹಿಗ್ಗಿಸಬಹುದು"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಆನ್ ಮಾಡಿ"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ವಜಾಗೊಳಿಸಿ"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"ಮುಂದುವರಿಯಲು, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ಗೆ ನಿಮ್ಮ ಸಾಧನದ ಮೈಕ್ರೋಫೋನ್‌ನ ಪ್ರವೇಶದ ಅಗತ್ಯವಿದೆ."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"ಮುಂದುವರಿಯಲು, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ಗೆ ನಿಮ್ಮ ಸಾಧನದ ಕ್ಯಾಮರಾದ ಪ್ರವೇಶದ ಅಗತ್ಯವಿದೆ."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ಆನ್ ಮಾಡಿ"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"ಸೆನ್ಸರ್ ಗೌಪ್ಯತೆ"</string>
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index ea5de596..3407431 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi에서만"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Cross SIM 통화"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> 백업 전화"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: 착신전환 안됨"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g><xliff:g id="TIME_DELAY">{2}</xliff:g>초 후"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"센서 알림 서비스"</string>
     <string name="twilight_service" msgid="8964898045693187224">"새벽 서비스"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"시간대 감지(연결되지 않음)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS 시간 업데이트 서비스"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"기기가 삭제됩니다."</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"관리자 앱을 사용할 수 없습니다. 곧 기기가 삭제됩니다.\n\n궁금한 점이 있으면 조직의 관리자에게 문의하세요."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g>에 의해 사용 중지되었습니다."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"이제 화면 일부 또는 전체를 확대할 수 있습니다."</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"설정에서 사용 설정"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"닫기"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"계속하려면 &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;에서 기기 마이크에 액세스해야 합니다."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"계속하려면 &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;에서 기기 카메라에 액세스해야 합니다."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"사용"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"센서 개인정보 보호"</string>
 </resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 5137a22..9a84d0a 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi гана"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> SIM карталарынан кайчылаш чалуу"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Кошумча чалуу ыкмасы"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Багытталган эмес"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> секунддан кийин"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Сенсордун билдирмелеринин кызматы"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight кызматы"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Убакыт алкагын аныктагыч (байланыш жок)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS Убакытты жаңыртуу кызматы"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Түзмөгүңүз тазаланат"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Түзмөктү башкаруучу колдонмо жараксыз. Түзмөгүңүз азыр тазаланат.\n\nСуроолоруңуз болсо, ишканаңыздын администраторуна кайрылыңыз."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Басып чыгаруу <xliff:g id="OWNER_APP">%s</xliff:g> тарабынан өчүрүлдү."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Эми толук экранды же анын бөлүгүн чоңойто аласыз"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Жөндөөлөрдөн күйгүзүү"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Жабуу"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Улантуу үчүн &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; колдонмосуна түзмөгүңүздүн микрофонун пайдаланууга уруксат беришиңиз керек."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Улантуу үчүн &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; колдонмосуна түзмөгүңүздүн камерасын пайдаланууга уруксат беришиңиз керек."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Күйгүзүү"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Сенсордун купуялыгы"</string>
 </resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 7de2a91..9977f9f 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi​-Fi ເທົ່າ​ນັ້ນ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ການໂທຂ້າມຊິມ"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ບໍ່ຖືກສົ່ງຕໍ່"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> ຫຼັງຈາກ <xliff:g id="TIME_DELAY">{2}</xliff:g> ວິນາທີ"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"ບໍລິການການແຈ້ງເຕືອນເຊັນເຊີ"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ບໍລິການ Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"ຕົວກວດຫາເຂດເວລາ (ບໍ່ມີການເຊື່ອມຕໍ່)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"ບໍລິການອັບເດດເວລາ GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"ອຸ​ປະ​ກອນ​ຂອງ​ທ່ານ​ຈະ​ຖືກ​ລຶບ"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"ບໍ່ສາມາດໃຊ້ແອັບຜູ້ເບິ່ງແຍງລະບົບໄດ້. ອຸປະກອນຂອງທ່ານຈະຖືກລຶບຂໍ້ມູນໃນຕອນນີ້.\n\nຫາກທ່ານມີຄຳຖາມ, ໃຫ້ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບອົງກອນຂອງທ່ານ."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"ການພິມຖືກປິດໄວ້ໂດຍ <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"ຕອນນີ້ທ່ານສາມາດຂະຫຍາຍບາງສ່ວນ ຫຼື ທັງໝົດຂອງໜ້າຈໍໄດ້"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"ເປີດໃຊ້ໃນການຕັ້ງຄ່າ"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ປິດໄວ້"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"ເພື່ອດຳເນີນການຕໍ່, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ຕ້ອງການສິດເຂົ້າເຖິງໄມໂຄຣໂຟນອຸປະກອນທ່ານ."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"ເພື່ອດຳເນີນການຕໍ່, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ຕ້ອງການສິດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງອຸປະກອນທ່ານ."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ເປີດໃຊ້"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"ຄວາມເປັນສ່ວນຕົວເຊັນເຊີ"</string>
 </resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 155d4cfb..5f72e07 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -150,7 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tik „Wi-Fi“"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"„<xliff:g id="SPN">%s</xliff:g>“: skambinimas per SIM korteles"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – atsarginis skambinimas"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neperadresuota"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string>
@@ -207,8 +207,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Jutiklių pranešimų paslauga"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Paslauga „Twilight“"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Laiko juostos aptikimo priemonė (nėra ryšio)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS laiko atnaujinimo paslauga"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Įrenginys bus ištrintas"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Administratoriaus programos negalima naudoti. Dabar įrenginio duomenys bus ištrinti.\n\nJei turite klausimų, susisiekite su organizacijos administratoriumi."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Neleidžiama spausdinti (<xliff:g id="OWNER_APP">%s</xliff:g>)."</string>
@@ -2273,12 +2272,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Dabar galite padidinti dalį ekrano ar jį visą"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Įjungti nustatymuose"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Atmesti"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Kad būtų galima tęsti, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; reikalinga prieiga prie įrenginio mikrofono."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Kad būtų galima tęsti, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; reikalinga prieiga prie įrenginio fotoaparato."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Įjungti"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Jutiklių privatumas"</string>
 </resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index cd88997..37e417f 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -149,7 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tikai Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g>: zvanīšana, izmantojot dažādas SIM kartes"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>: zvanu rezerves iespēja"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nav pāradresēts"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pēc <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundes(-ēm)"</string>
@@ -205,8 +205,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensoru paziņojumu pakalpojums"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Krēslas noteikšanas pakalpojums"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Laika joslas noteikšanas rīks (nav savienojuma)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS laika atjaunināšanas pakalpojums"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Jūsu ierīces dati tiks dzēsti"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Administratora lietotni nevar izmantot. Ierīcē saglabātie dati tiks dzēsti.\n\nJa jums ir kādi jautājumi, sazinieties ar savas organizācijas administratoru."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Drukāšanu atspējoja <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2239,12 +2238,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Tagad varat palielināt ekrāna daļu vai visu ekrānu"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Ieslēgt sadaļā Iestatījumi"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Nerādīt"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Lai turpinātu, lietotnei &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; nepieciešama piekļuve jūsu ierīces mikrofonam."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Lai turpinātu, lietotnei &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; nepieciešama piekļuve jūsu ierīces kamerai."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Ieslēgt"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensoru konfidencialitāte"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-si/strings.xml b/core/res/res/values-mcc310-mnc950-si/strings.xml
deleted file mode 100644
index 26fe4ac..0000000
--- a/core/res/res/values-mcc310-mnc950-si/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/* //device/apps/common/assets/res/any/strings.xml
-**
-** Copyright 2006, 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.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="615419724607901560">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
-    <string name="mmcc_illegal_ms" msgid="7801541624846497489">"SIM MM#3 ඉඩ නොදේ"</string>
-    <string name="mmcc_illegal_me" msgid="7066936962628406316">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
-</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index d8ecc54..5666cc2 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Повици преку повеќе SIM-картички на <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Резервен начин на повикување преку <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е препратено"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> по <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Услуга за известување од сензорот"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Услуга за самрак"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Откривач на временска зона (не може да се поврзе)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Услуга за ажурирање на времето на GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Уредот ќе се избрише"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Апликацијата на администраторот не може да се користи. Уредот ќе се избрише сега.\n\nАко имате прашања, контактирајте со администраторот на организацијата."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Печатењето е оневозможено од <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Сега може се зголеми целиот екран или само дел"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Вклучи во „Поставки“"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Отфрли"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"За да продолжи, на &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ѝ е потребен пристап до микрофонот на уредот."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"За да продолжи, на &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ѝ е потребен пристап до камерата на уредот."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Вклучи"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Приватност на сензорот"</string>
 </resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index f7a3161..e0d4b01 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"വൈഫൈ മാത്രം"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ക്രോസ് സിം കോളിംഗ്"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: കൈമാറിയില്ല"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> നിമിഷത്തിനുശേഷം <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"സെൻസർ അറിയിപ്പ് സേവനം"</string>
     <string name="twilight_service" msgid="8964898045693187224">"സന്ധ്യാസമയത്തെ സേവനം"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"സമയമേഖല കണ്ടെത്താനുള്ള സംവിധാനം (കണക്റ്റിവിറ്റി ഇല്ല)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS സമയ അപ്ഡേറ്റ് സേവനം"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"നിങ്ങളുടെ ഉപകരണം മായ്‌ക്കും"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"അഡ്‌മിൻ ആപ്പ് ഉപയോഗിക്കാനാകില്ല. നിങ്ങളുടെ ഉപകരണം ഇപ്പോൾ മായ്ക്കപ്പെടും.\n\nനിങ്ങൾക്ക് ചോദ്യങ്ങൾ ഉണ്ടെങ്കിൽ, നിങ്ങളുടെ സ്ഥാപനത്തിന്റെ അഡ്‌മിനെ ബന്ധപ്പെടുക."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> പ്രിന്റിംഗ് പ്രവർത്തനരഹിതമാക്കി."</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"സ്ക്രീനിന്റെ ഭാഗങ്ങളോ മുഴുവനുമോ മാഗ്നിഫൈ ചെയ്യാം"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"ക്രമീകരണത്തിൽ ഓണാക്കുക"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"തുടരാൻ, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ആപ്പിന് നിങ്ങളുടെ ഉപകരണത്തിന്റെ മൈക്രോഫോണിലേക്ക് ആക്‌സസ് നൽകേണ്ടതുണ്ട്."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"തുടരാൻ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ആപ്പിന് നിങ്ങളുടെ ഉപകരണത്തിന്റെ ക്യാമറയിലേക്ക് ആക്‌സസ് നൽകേണ്ടതുണ്ട്."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ഓണാക്കുക"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"സെൻസർ സ്വകാര്യത"</string>
 </resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index cb3f00d..891ac47 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Зөвхөн Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> SIM хоорондын дуудлага"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Дуудлагыг нөөцлөх"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: дамжуулагдаагүй"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> секундын дараа"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Мэдрэгчийн мэдэгдлийн үйлчилгээ"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight үйлчилгээ"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Цагийн бүс илрүүлэгч (Холболт байхгүй)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS Хугацаа шинэчлэлтийн үйлчилгээ"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Таны төхөөрөмж устах болно."</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Админ аппыг ашиглах боломжгүй. Таны төхөөрөмжийг одоо устгана.\n\nХэрэв танд асуулт байгаа бол байгууллагынхаа админтай холбогдоно уу."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> хэвлэх үйлдлийг идэвхгүй болгосон."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Та одоо зарим эсвэл бүх дэлгэцээ томруулж болно"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Тохиргоонд асаана уу"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Үл хэрэгсэх"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Үргэлжлүүлэхийн тулд, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; таны төхөөрөмжийн микрофонд хандах шаардлагатай."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Үргэлжлүүлэхийн тулд &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; таны төхөөрөмжийн камерт хандах шаардлагатай."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Асаах"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Мэдрэгчийн нууцлал"</string>
 </resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 4921f25..d57c4c1 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -57,8 +57,7 @@
     <string name="imei" msgid="2157082351232630390">"IMEI"</string>
     <string name="meid" msgid="3291227361605924674">"MEID"</string>
     <string name="ClipMmi" msgid="4110549342447630629">"येणारा कॉलर आयडी"</string>
-    <!-- no translation found for ClirMmi (6752346475055446417) -->
-    <skip />
+    <string name="ClirMmi" msgid="6752346475055446417">"आउटगोइंग कॉलर आयडी लपवा"</string>
     <string name="ColpMmi" msgid="4736462893284419302">"कनेक्ट केलेला रेखा आयडी"</string>
     <string name="ColrMmi" msgid="5889782479745764278">"कनेक्ट केलेला रेखा आयडी प्रतिबंध"</string>
     <string name="CfMmi" msgid="8390012691099787178">"कॉल फॉरवर्डिंग"</string>
@@ -149,12 +148,13 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"केवळ वाय-फाय"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> क्रॉस सिम कॉलिंग"</string>
-    <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित केला नाही"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
+    <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: फॉरवर्ड केला नाही"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> सेकंदांनंतर <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
-    <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित केला नाही"</string>
-    <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित केला नाही"</string>
+    <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: फॉरवर्ड केला नाही"</string>
+    <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: फॉरवर्ड केला नाही"</string>
     <string name="fcComplete" msgid="1080909484660507044">"वैशिष्ट्य कोड पूर्ण."</string>
     <string name="fcError" msgid="5325116502080221346">"कनेक्शन समस्या किंवा अवैध फीचर कोड."</string>
     <string name="httpErrorOk" msgid="6206751415788256357">"ठीक"</string>
@@ -204,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"सेन्सर सूचना सेवा"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ट्वायलाइट सेवा"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"टाइम झोन डिटेक्टर (कनेक्टिव्हिटी नाही)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS ची वेळ अपडेट करणारी सेवा"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"तुमचे डिव्हाइस मिटविले जाईल"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"प्रशासक अ‍ॅप वापरता येणार नाही. तुमचे डिव्हाइस आता साफ केले जाईल.\n\nतुम्हाला कुठलेही प्रश्न असल्यास, तुमच्या संस्थेच्या प्रशासकाशी संपर्क साधा."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> नी प्रिंट करणे बंद केले आहे."</string>
@@ -2206,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"आता स्क्रीन अंशतः किंवा पूर्ण मॅग्निफाय करू शकता"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"सेटिंग्ज मध्ये सुरू करा"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"डिसमिस करा"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"पुढे सुरू ठेवण्यासाठी, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ला तुमच्या डिव्हाइसचा मायक्रोफोन अ‍ॅक्सेस करण्याची आवश्यकता आहे."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"पुढे सुरू ठेवण्यासाठी, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ला तुमच्या डिव्हाइसचा कॅमेरा अ‍ॅक्सेस करण्याची आवश्यकता आहे."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"सुरू करा"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"सेन्सरशी संबंधित गोपनीयतेबाबत सूचना"</string>
 </resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 557d64d..ec0aa5d 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi sahaja"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Panggilan Silang Sim"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Panggilan Sandaran"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak dimajukan"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> selepas <xliff:g id="TIME_DELAY">{2}</xliff:g> saat"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Perkhidmatan Pemberitahuan Penderia"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Perkhidmatan Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Pengesan Zon Waktu (Tiada kesambungan)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Perkhidmatan Kemaskinian Waktu GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Peranti anda akan dipadam"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Apl pentadbir tidak dapat digunakan. Peranti anda akan dipadamkan sekarang.\n\nJika anda ingin mengemukakan soalan, hubungi pentadbir organisasi anda."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Pencetakan dilumpuhkan oleh <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Besarkan sebahagian atau keseluruhan skrin anda"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Hidupkan dalam Tetapan"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Tolak"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Untuk meneruskan proses, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; memerlukan akses kepada mikrofon peranti anda."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Untuk meneruskan proses, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; memerlukan akses kepada kamera peranti anda."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Hidupkan"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privasi Penderia"</string>
 </resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 49387d8..7b4a430fc 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ကြိုးမဲ့အင်တာနက် သာလျှင်"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Cross-SIM ခေါ်ဆိုမှု"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> အရန် ခေါ်ဆိုမှု"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ထပ်ဆင့်မပို့နိုင်ပါ"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> နောက် <xliff:g id="TIME_DELAY">{2}</xliff:g> စက္ကန့်"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"အာရုံခံကိရိယာ အကြောင်းကြားချက် ဝန်ဆောင်မှု"</string>
     <string name="twilight_service" msgid="8964898045693187224">"နေဝင်ဆည်းဆာ ဝန်ဆောင်မှု"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"ဒေသစံတော်ချိန် ရှာဖွေစနစ် (ချိတ်ဆက်နိုင်မှု မလိုပါ)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS အချိန်အပ်ဒိတ် ဝန်ဆောင်မှု"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"သင့်ကိရိယာအား ပယ်ဖျက်လိမ့်မည်"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"စီမံခန့်ခွဲမှု အက်ပ်ကို သုံး၍မရပါ။ သင်၏ စက်ပစ္စည်းအတွင်းရှိ အရာများကို ဖျက်လိုက်ပါမည်\n\nမေးစရာများရှိပါက သင့်အဖွဲ့အစည်း၏ စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> က ပုံနှိပ်ထုတ်ယူခြင်းကို ပိတ်ထားသည်။"</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"ဖန်သားပြင် တစ်စိတ်တစ်ပိုင်း (သို့) တစ်ခုလုံး ချဲ့နိုင်ပါပြီ"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"\'ဆက်တင်များ\' တွင် ဖွင့်ရန်"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ပယ်ရန်"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"ဆက်လက်လုပ်ဆောင်ရန် &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; က သင့်စက်၏ မိုက်ခရိုဖုန်းကို အသုံးပြုခွင့်ရရန် လိုအပ်သည်။"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"ဆက်လက်လုပ်ဆောင်ရန် &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; က သင့်စက်၏ ကင်မရာကို အသုံးပြုခွင့်ရရန် လိုအပ်သည်။"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ဖွင့်ရန်"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"အာရုံခံကိရိယာ လုံခြုံရေး"</string>
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 2be1dab..4968184 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Bare Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Ringing mellom SIM-kort med <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>-reserve for anrop"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> etter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensor Notification Service"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight Service"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Tidssoneoppdagelse (ingen tilkobling)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS-tjeneste for tidsoppdatering"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Enheten blir slettet"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Administratorappen kan ikke brukes. Enheten din blir nå tømt.\n\nTa kontakt med administratoren for organisasjonen din hvis du har spørsmål."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> har slått av utskrift."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Nå kan du forstørre deler av eller hele skjermen"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Slå på i innstillingene"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Avvis"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"For å fortsette må &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ha tilgang til enhetsmikrofonen."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"For å fortsette må &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ha tilgang til enhetskameraet."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Slå på"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensorpersonvern"</string>
 </resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index b309e4a..541ace8 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi मात्र"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> क्रस SIM कलिङ"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अगाडि पठाइएको छैन"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> पछि <xliff:g id="TIME_DELAY">{2}</xliff:g> सेकेन्ड"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"सेन्सरको सूचनासम्बन्धी सेवा"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ट्वाइलाइट सेवा"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"समय क्षेत्र पत्ता लगाउने सुविधा (नेटवर्क कनेक्सन नहुँदा)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS को समय अपडेट गर्ने सेवा"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"तपाईंको यन्त्र मेटिनेछ"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"प्रशासकको एप प्रयोग गर्न मिल्दैन। तपाईंको यन्त्रको डेटा अब मेटाइने छ।\n\nतपाईंसँग प्रश्नहरू भएका खण्डमा आफ्नो संगठनका प्रशासकसँग सम्पर्क गर्नुहोस्।"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ले छाप्ने कार्यलाई असक्षम पार्यो।"</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"तपाईं अब स्क्रिनको केही वा सबै भाग जुम इन गर्न सक्नुहुन्छ"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"सेटिङमा गई यो सुविधा अन गर्नुहोस्"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"हटाउनुहोस्"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"जारी राख्न &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; लाई तपाईंको यन्त्रको माइक्रोफोन प्रयोग गर्ने अनुमति दिनु पर्ने हुन्छ।"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"जारी राख्न &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; लाई तपाईंको यन्त्रको क्यामेरा प्रयोग गर्ने अनुमति दिनु पर्ने हुन्छ।"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"अन गर्नुहोस्"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"सेन्सरसम्बन्धी गोपनीयता"</string>
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index c8621a1..7f23cbb 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Alleen wifi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Bellen met meerdere simkaarten van <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Reserveoptie voor bellen van <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: niet doorgeschakeld"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> seconden"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Service voor sensormeldingen"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Service voor schemering"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Tijdzonedetector (Geen verbinding)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Updateservice voor GNSS-tijd"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Je apparaat wordt gewist"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"De beheer-app kan niet worden gebruikt. Je apparaat wordt nu gewist.\n\nNeem contact op met de beheerder van je organisatie als je vragen hebt."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Afdrukken uitgeschakeld door <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Je kunt je scherm nu (gedeeltelijk) vergroten"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Inschakelen in Instellingen"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Sluiten"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; heeft toegang tot de microfoon van je apparaat nodig om door te gaan."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; heeft toegang tot de camera van je apparaat nodig om door te gaan."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aanzetten"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensorprivacy"</string>
 </resources>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 6e07d75..f02d157 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"କେବଳ ୱାଇ-ଫାଇ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> କ୍ରସ୍ SIM କଲିଂ"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ଫରୱାର୍ଡ କରାଯାଇନାହିଁ"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> ସେକେଣ୍ଡ ପରେ"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"ସେନ୍ସର୍ ନୋଟିଫିକେସନ୍ ସର୍ଭିସ୍"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ଟ୍ୱିଲାଇଟ୍ ସର୍ଭିସ୍"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"ଟାଇମ୍‍ ଜୋନ୍‍ ଡିଟେକ୍ଟର୍‍ (କୌଣସି ସଂଯୋଗ ନାହିଁ)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS ସମୟ ଅପଡେଟ୍ ସେବା"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"ଆପଣଙ୍କ ଡିଭାଇସ୍‍ ବର୍ତ୍ତମାନ ଲିଭାଯିବ"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"ଆଡମିନ୍‍ ଆପ୍‍‍ ବ୍ୟବହାର କରାଯାଇପାରିବ ନାହିଁ। ଆପଣଙ୍କ ଡିଭାଇସ୍‍‌ର ସମସ୍ତ ଡାଟାକୁ ବର୍ତ୍ତମାନ ଲିଭାଇଦିଆଯିବ। \n\nଯଦି ଆପଣଙ୍କର କୌଣସି ପ୍ରଶ୍ନ ରହିଥାଏ, ଆପଣଙ୍କ ସଂସ୍ଥାର ଆଡମିନ୍‌ଙ୍କ ସହ ଯୋଗାଯୋଗ କରନ୍ତୁ।"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ଦ୍ଵାରା ପ୍ରିଣ୍ଟିଙ୍ଗ ଅକ୍ଷମ କରାଯାଇଛି"</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"ଆପଣ ଏବେ ଆଂଶିକ ବା ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ୍ ବଡ଼ କରିପାରିବେ"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"ସେଟିଂସରେ ଚାଲୁ କରନ୍ତୁ"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ଖାରଜ କରନ୍ତୁ"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"ଜାରି ରଖିବାକୁ, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ଆପଣଙ୍କ ଡିଭାଇସର ମାଇକ୍ରୋଫୋନକୁ ଆକ୍ସେସ୍ ଆବଶ୍ୟକ କରେ।"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"ଜାରି ରଖିବାକୁ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ଆପଣଙ୍କ ଡିଭାଇସର କ୍ୟାମେରାକୁ ଆକ୍ସେସ୍ ଆବଶ୍ୟକ କରେ।"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ଚାଲୁ କରନ୍ତୁ"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"ସେନ୍ସର୍ ଗୋପନୀୟତା"</string>
 </resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 52e124c..a717af6 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -57,8 +57,7 @@
     <string name="imei" msgid="2157082351232630390">"IMEI"</string>
     <string name="meid" msgid="3291227361605924674">"MEID"</string>
     <string name="ClipMmi" msgid="4110549342447630629">"ਇਨਕਮਿੰਗ ਕਾਲਰ ਆਈ.ਡੀ."</string>
-    <!-- no translation found for ClirMmi (6752346475055446417) -->
-    <skip />
+    <string name="ClirMmi" msgid="6752346475055446417">"ਆਊਟਗੋਇੰਗ ਕਾਲਰ ਆਈਡੀ ਲੁਕਾਓ"</string>
     <string name="ColpMmi" msgid="4736462893284419302">"ਕਨੈਕਟ ਕੀਤੀ ਲਾਈਨ ਆਈ.ਡੀ."</string>
     <string name="ColrMmi" msgid="5889782479745764278">"ਕਨੈਕਟ ਕੀਤੀ ਲਾਈਨ ਆਈ.ਡੀ. ਪ੍ਰਤਿਬੰਧ"</string>
     <string name="CfMmi" msgid="8390012691099787178">"ਕਾਲ ਫਾਰਵਰਡਿੰਗ"</string>
@@ -149,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"ਸਿਰਫ਼ ਵਾਈ-ਫਾਈ"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> ਕ੍ਰਾਸ-ਸਿਮ ਕਾਲਿੰਗ"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ਅੱਗੇ ਨਹੀਂ ਭੇਜਿਆ ਗਿਆ"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> ਸਕਿੰਟਾਂ ਬਾਅਦ"</string>
@@ -204,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"ਸੈਂਸਰ ਸੂਚਨਾ ਸੇਵਾ"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ਟਵੀਲਾਈਟ ਸੇਵਾ"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"ਸਮਾਂ ਖੇਤਰ ਦਾ ਪਤਾ ਲਗਾਉਣ ਦੀ ਸੁਵਿਧਾ (ਕੋਈ ਕਨੈਕਟੀਵਿਟੀ ਨਹੀਂ)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS ਸਮਾਂ ਅੱਪਡੇਟ ਸੇਵਾ"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਮਿਟਾਇਆ ਜਾਏਗਾ"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"ਪ੍ਰਸ਼ਾਸਕ ਐਪ ਵਰਤੀ ਨਹੀਂ ਜਾ ਸਕਦੀ। ਹੁਣ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦਾ ਡਾਟਾ ਮਿਟਾਇਆ ਜਾਵੇਗਾ।\n\nਜੇਕਰ ਤੁਹਾਡੇ ਕੋਲ ਕੋਈ ਸਵਾਲ ਹਨ, ਤਾਂ ਆਪਣੀ ਸੰਸਥਾ ਦੇ ਪ੍ਰਸ਼ਾਸਕ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ਵੱਲੋਂ ਪ੍ਰਿੰਟ ਕਰਨਾ ਬੰਦ ਕੀਤਾ ਗਿਆ।"</string>
@@ -2206,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"ਹੁਣ ਤੁਸੀਂ ਕੁਝ ਜਾਂ ਪੂਰੀ ਸਕ੍ਰੀਨ ਵੱਡਦਰਸ਼ੀ ਕਰ ਸਕਦੇ ਹੋ"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਚਾਲੂ ਕਰੋ"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ਖਾਰਜ ਕਰੋ"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"ਜਾਰੀ ਰੱਖਣ ਲਈ, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ਨੂੰ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਤੱਕ ਪਹੁੰਚ ਦੀ ਲੋੜ ਹੈ।"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"ਜਾਰੀ ਰੱਖਣ ਲਈ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ਨੂੰ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਕੈਮਰਾ ਤੱਕ ਪਹੁੰਚ ਦੀ ਲੋੜ ਹੈ।"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ਚਾਲੂ ਕਰੋ"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"ਸੈਂਸਰ ਪਰਦੇਦਾਰੀ"</string>
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 3711cb7..f73b382 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -150,7 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Tylko Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Połączenia przez różne karty SIM z <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Zapasowa metoda wykonywania połączeń <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundach"</string>
@@ -207,8 +207,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Usługa powiadomień czujnika"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Usługa Zmierzch"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Wykrywanie strefy czasowej (brak połączenia)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Usługa synchronizacji czasu na podstawie sygnału GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Twoje urządzenie zostanie wyczyszczone"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Nie można użyć aplikacji administratora. Dane z urządzenia zostaną wykasowane.\n\nJeśli masz pytania, skontaktuj się z administratorem organizacji."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Drukowanie wyłączone przez: <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2273,12 +2272,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Możesz teraz powiększyć część lub całość ekranu"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Włącz w Ustawieniach"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Odrzuć"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Aby kontynuować, musisz przyznać aplikacji „<xliff:g id="APP">%s</xliff:g>” dostęp do mikrofonu urządzenia."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Aby kontynuować, musisz przyznać aplikacji „<xliff:g id="APP">%s</xliff:g>” dostęp do aparatu urządzenia."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Włącz"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Poufność danych z czujników"</string>
 </resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 89b57ef..693af83 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Somente Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Chamadas entre chips da <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas da <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
@@ -203,7 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Serviço de notificações do sensor"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Serviço de crepúsculo"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detector de fuso horário (sem conectividade)"</string>
-    <string name="gnss_time_update_service" msgid="9039489496037616095">"Serviço de atualização de horário do GNSS"</string>
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Serviço de atualização de horário do Sistema Global de Navegação por Satélites (GNSS, na sigla em inglês)"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Seu dispositivo será limpo"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Não é possível usar o aplicativo para administrador. Seu dispositivo passará por uma limpeza agora.\n\nEm caso de dúvidas, entre em contato com o administrador da sua organização."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Impressão desativada por <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 4fe13f8..e00034a 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Apenas Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Chamadas com vários cartões SIM"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Fazer uma cópia de segurança das chamadas <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não reencaminhado"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
@@ -2068,7 +2068,7 @@
     <string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
     <string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"enviou uma imagem"</string>
     <string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Conversa"</string>
-    <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Conversa de grupo"</string>
+    <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Conversa em grupo"</string>
     <string name="unread_convo_overflow" msgid="920517615597353833">"&gt; <xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>"</string>
     <string name="resolver_personal_tab" msgid="2051260504014442073">"Pessoal"</string>
     <string name="resolver_work_tab" msgid="2690019516263167035">"Trabalho"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 89b57ef..693af83 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Somente Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Chamadas entre chips da <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Chamadas alternativas da <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string>
@@ -203,7 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Serviço de notificações do sensor"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Serviço de crepúsculo"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detector de fuso horário (sem conectividade)"</string>
-    <string name="gnss_time_update_service" msgid="9039489496037616095">"Serviço de atualização de horário do GNSS"</string>
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Serviço de atualização de horário do Sistema Global de Navegação por Satélites (GNSS, na sigla em inglês)"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Seu dispositivo será limpo"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Não é possível usar o aplicativo para administrador. Seu dispositivo passará por uma limpeza agora.\n\nEm caso de dúvidas, entre em contato com o administrador da sua organização."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Impressão desativada por <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 38f9854..5fd6f1a 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -149,7 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Numai Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Apelare pe mai multe carduri SIM <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Apelare de rezervă prin <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neredirecționată"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> după <xliff:g id="TIME_DELAY">{2}</xliff:g>   secunde"</string>
@@ -205,8 +205,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Serviciu pentru notificări de la senzori"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Serviciul Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detector de fus orar (fără conexiune)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Serviciul de actualizare a orei bazat pe GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Datele de pe dispozitiv vor fi șterse"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Aplicația de administrare nu poate fi utilizată. Dispozitivul va fi șters.\n\nDacă aveți întrebări, contactați administratorul organizației dvs."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Printare dezactivată de <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2239,12 +2238,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Acum puteți mări o parte sau tot ecranul"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Activați din Setări"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Respingeți"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Pentru a continua, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necesită acces la microfonul dispozitivului."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Pentru a continua, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necesită acces la camera dispozitivului."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Activați"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Confidențialitatea privind senzorii"</string>
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 6408cce..d1cd374 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -150,7 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Только Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Перекрестная работа SIM-карт от \"<xliff:g id="SPN">%s</xliff:g>\""</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Резервный способ связи: <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не переадресовано"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> через <xliff:g id="TIME_DELAY">{2}</xliff:g> с."</string>
@@ -207,8 +207,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Сервис для обработки уведомлений от датчиков"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Сервис для определения наступления сумерек"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Определитель часового пояса (работает без Интернета)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Синхронизация времени с помощью ГНСС"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Все данные с устройства будут удалены"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Невозможно использовать приложение для администрирования. С устройства будут удалены все данные.\n\nЕсли у вас возникли вопросы, обратитесь к администратору."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Функция печати отключена приложением \"<xliff:g id="OWNER_APP">%s</xliff:g>\""</string>
@@ -2273,12 +2272,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Теперь можно увеличивать весь экран или его часть."</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Включить в настройках"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Закрыть"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Чтобы продолжить, предоставьте приложению &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ к микрофону устройства."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Чтобы продолжить, предоставьте приложению &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ к камере устройства."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Включить"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Конфиденциальность датчиков"</string>
 </resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index a6d54f4..d102183 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi පමණයි"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> හරස් SIM ඇමතුම"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> උපස්ථ ඇමතුම"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ඉදිරියට නොයවන ලදි"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: තත්පර <xliff:g id="TIME_DELAY">{2}</xliff:g> ට පසුව <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"සංවේදක දැනුම් දීමේ සේවාව"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ඇඳිරි සේවාව"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"වේලා කලාප අනාවරකය (සම්බන්ධතාවක් නොමැත)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS වේලා යාවත්කාලීන සේවාව"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"ඔබගේ උපාංගය මකා දැමෙනු ඇත"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"පරිපාලක යෙදුම භාවිතා කළ නොහැකිය. ඔබේ උපාංගය දැන් මකා දමනු ඇත.\n\nඔබට ප්‍රශ්න තිබේ නම්, ඔබේ සංවිධානයේ පරිපාලකට අමතන්න."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> විසින් මුද්‍රණය කිරීම අබල කර ඇත."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"ඔබට දැන් තිරයේ සමහර හෝ සියලු දේ විශාලනය කළ හැකිය"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"සැකසීම් තුළ ක්‍රියාත්මක කරන්න"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ඉවත ලන්න"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"දිගටම කර ගෙන යාමට, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; හට ඔබගේ උපාංගයෙහි මයික්‍රෆෝනයට ප්‍රවේශය අවශ්‍යයි."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"දිගටම කර ගෙන යාමට, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; හට ඔබගේ උපාංගයෙහි කැමරාවට ප්‍රවේශය අවශ්‍යයි."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ක්‍රියාත්මක කරන්න"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"සංවේදක පෞද්ගලිකත්වය"</string>
 </resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index a478083..ffa4b26 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -150,7 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Len Wi‑Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Volanie naprieč SIM kartami"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> – záložné volanie"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepresmerované"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string>
@@ -207,8 +207,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Služba upozornení senzora"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Služba stmievania"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detektor časového pásma (bez pripojenia)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Služba na aktualizáciu času globálneho družicového polohového systému"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Vaše zariadenie bude vymazané"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Daná aplikácia na správu sa nedá použiť. Vaše zariadenie bude vymazané.\n\nV prípade otázok kontaktujte správcu organizácie."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Tlač zakázala aplikácia <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2273,12 +2272,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Teraz môžete zväčšiť celú obrazovku alebo jej časť"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Zapnúť v Nastaveniach"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Zavrieť"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Ak chcete pokračovať, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; požaduje prístup k mikrofónu zariadenia."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Ak chcete pokračovať, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; požaduje prístup k fotoaparátu zariadenia."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Zapnúť"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Ochrana súkromia senzorov"</string>
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index b353f45..d09ef04 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -150,7 +150,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Samo Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Klicanje ne glede na kartico SIM operaterja <xliff:g id="SPN">%s</xliff:g>"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ni posredovano"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po toliko sekundah: <xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
@@ -207,8 +208,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Storitev obvestil tipal"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Storitev Somrak"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Zaznavanje časovnega pasu (brez povezave)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Storitev posodobitve ure po sistemu GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Podatki v napravi bodo izbrisani"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Skrbniške aplikacije ni mogoče uporabljati. Podatki v napravi bodo izbrisani.\n\nČe imate vprašanja, se obrnite na skrbnika organizacije."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Tiskanje je onemogočil pravilnik <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2273,12 +2273,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Zdaj lahko povečate del zaslona ali celotni zaslon"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Vklopite v nastavitvah"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Opusti"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Za nadaljevanje potrebuje aplikacija &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; dostop do mikrofona v napravi."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Za nadaljevanje potrebuje aplikacija &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; dostop do fotoaparata v napravi."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Vklopi"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Zasebnost pri uporabi tipal"</string>
 </resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 86f191f..592e54d 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Vetëm Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Telefonatat e kryqëzuara të kartës SIM nga <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Opsioni rezervë i telefonatave të <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nuk u transferua"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pas <xliff:g id="TIME_DELAY">{2}</xliff:g> sekondash"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Shërbimi i njoftimeve të sensorit"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Shërbimi i muzgut"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Zbuluesi i brezit orar (nuk nevojitet lidhja)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Shërbimi i përditësimit të kohës GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Pajisja do të spastrohet"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Aplikacioni i administratorit nuk mund të përdoret. Pajisja jote tani do të fshihet.\n\nNëse ke pyetje, kontakto me administratorin e organizatës."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Printimi është çaktivizuar nga <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Tani mund t\'i zmadhosh një pjesë apo të gjithë ekranin tënd"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Aktivizo te \"Cilësimet\""</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Hiq"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Për të vazhduar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ka nevojë të qaset në mikrofonin e pajisjes sate."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Për të vazhduar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ka nevojë të qaset në kamerën e pajisjes sate."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aktivizo"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privatësia e sensorit"</string>
 </resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 594a2ee..65037a0 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -149,7 +149,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Само WiFi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Позиви са више SIM картица за оператера <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> резервни начин за позивање"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Није прослеђено"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> након <xliff:g id="TIME_DELAY">{2}</xliff:g> секунде/и"</string>
@@ -205,8 +205,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Услуга обавештења сензора"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Услуга Сумрак"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Детектор временске зоне (нема интернет везе)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS услуга за ажурирање времена"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Уређај ће бити обрисан"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Не можете да користите ову апликацију за администраторе. Уређај ће сада бити обрисан.\n\nАко имате питања, контактирајте администратора организације."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Штампање је онемогућила апликација <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2239,12 +2238,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Можете да увећате део екрана или цео екран"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Укључите у Подешавањима"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Одбаци"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"&lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; захтева приступ микрофону уређаја ради настављања."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; захтева приступ камери уређаја ради настављања."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Укључи"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Приватност сензора"</string>
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index bf498ab..ef7a102 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Endast Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> – samtal mellan SIM-kort"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> används som reserv för samtal"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Vidarebefordras inte"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensor Notification Service"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight Service"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Tidszondetektering (ingen anslutning)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Tjänst för uppdatering av GNSS-tid"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Enheten kommer att rensas"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Det går inte att använda administratörsappen. Enheten rensas.\n\nKontakta organisationens administratör om du har några frågor."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Utskrift har inaktiverats av <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Nu kan du förstora delar av eller hela skärmen"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Aktivera i inställningarna"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Stäng"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"&lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; behöver behörighet till enhetens mikrofon för att fortsätta."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; behöver behörighet till enhetens kamera för att fortsätta."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aktivera"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensorintegritet"</string>
 </resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 93f0ed9..cc82f4e 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi pekee"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Kipengele cha Kupiga Simu Kupitia SIM Tofauti"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>Kupiga Simu Kupitia Mtandao Mbadala"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Haijatumiwa mwingine"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> baada ya sekunde <xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Huduma ya Arifa ya Kitambuzi"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight Service"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Kitambua Saa za Eneo (Hakuna muunganisho)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Huduma ya Kusasisha Saa za GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Data iliyomo kwenye kifaa chako itafutwa"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Huwezi kutumia programu ya msimamizi. Sasa data iliyo kwenye kifaa chako itafutwa.\n\nIkiwa una maswali yoyote, wasiliana na msimamizi wa shirika lako."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Kipengele cha kuchapisha kimezimwa na <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Sasa unaweza kukuza sehemu ya au skrini yako yote"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Washa katika Mipangilio"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Ondoa"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Ili uendelee, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; inahitaji ruhusa ya kufikia maikrofoni ya kifaa chako."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Ili uendelee, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; inahitaji ruhusa ya kufikia kamera ya kifaa chako."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Washa"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Faragha ya Kitambuzi"</string>
 </resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 527c50f..ca90171 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"வைஃபை மட்டும்"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> கிராஸ்-சிம் அழைப்பு"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> காப்புப் பிரதி அழைப்பு"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: பகிரப்படவில்லை"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> வினாடிகளுக்குப் பிறகு <xliff:g id="DIALING_NUMBER">{1}</xliff:g> ஐப் பகிர்"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"சென்சார் அறிவிப்புச் சேவை"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight சேவை"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"நேர மண்டல டிடெக்டர் (இணைப்பு இல்லை)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS நேரப் புதுப்பிப்புச் சேவை"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"சாதனத் தரவு அழிக்கப்படும்"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"நிர்வாகி ஆப்ஸை உபயோகிக்க முடியாது. இப்போது, உங்கள் சாதனம் ஆரம்ப நிலைக்கு மீட்டமைக்கப்படும்.\n\nஏதேனும் கேள்விகள் இருப்பின், உங்கள் நிறுவனத்தின் நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"பிரிண்ட் செய்வதை <xliff:g id="OWNER_APP">%s</xliff:g> தடுத்துள்ளது."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"திரை முழுவதையுமோ ஒரு பகுதியையோ பெரிதாக்கலாம்"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"அமைப்புகளில் ஆன் செய்க"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"மூடுக"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"தொடர, உங்கள் சாதனத்தின் மைக்ரோஃபோனை அணுகுவதற்கு &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ஆப்ஸுக்கு அனுமதி வேண்டும்."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"தொடர, உங்கள் சாதனத்தின் கேமராவை அணுகுவதற்கு &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ஆப்ஸுக்கு அனுமதி வேண்டும்."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ஆன் செய்"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"சென்சார் தனியுரிமை"</string>
 </resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 2de91ba..b0726e8 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi మాత్రమే"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> క్రాస్-SIM కాలింగ్"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ఫార్వార్డ్ చేయబడలేదు"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> సెకన్ల తర్వాత <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"సెన్సార్ నోటిఫికేషన్ సర్వీస్"</string>
     <string name="twilight_service" msgid="8964898045693187224">"ట్విలైట్ సర్వీస్"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"టైమ్ జోన్ డిటెక్టర్ (కనెక్టివిటీ లేదు)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS సమయ అప్‌డేట్ సర్వీస్"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"మీ పరికరంలోని డేటా తొలగించబడుతుంది"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"నిర్వాహక యాప్‌ ఉపయోగించడం సాధ్యపడదు. మీ పరికరంలోని డేటా ఇప్పుడు తొలగించబడుతుంది.\n\nమీకు ప్రశ్నలు ఉంటే, మీ సంస్థ యొక్క నిర్వాహకులను సంప్రదించండి."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"ముద్రణ <xliff:g id="OWNER_APP">%s</xliff:g> ద్వారా నిలిపివేయబడింది."</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"స్క్రీన్ మొత్తం లేదా కొంత భాగాన్ని జూమ్ చేయవచ్చు"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"సెట్టింగ్‌లలో ఆన్ చేయండి"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"విస్మరించు"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"కొనసాగించడానికి, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;కు మీ పరికరం యొక్క మైక్రోఫోన్ యాక్సెస్ అవసరం."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"కొనసాగించడానికి, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&amp;gtకు మీ పరికరం యొక్క కెమెరా యాక్సెస్ అవసరం."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"ఆన్ చేయి"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"సెన్సార్ గోప్యత"</string>
 </resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index aa3331a..5079f23 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi เท่านั้น"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"การโทรข้ามซิม <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"การสำรองข้อมูลการโทรจาก <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ไม่ได้โอนสาย"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> หลังผ่านไป <xliff:g id="TIME_DELAY">{2}</xliff:g> วินาที"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"บริการแจ้งเตือนเกี่ยวกับเซ็นเซอร์"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight Service"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"ตัวตรวจจับเขตเวลา (ไม่มีการเชื่อมต่อ)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"บริการอัปเดตเวลาของ GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"ระบบจะลบข้อมูลในอุปกรณ์ของคุณ"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"ใช้แอปผู้ดูแลระบบนี้ไม่ได้ ขณะนี้ระบบจะลบข้อมูลในอุปกรณ์ของคุณ\n\nโปรดติดต่อผู้ดูแลระบบขององค์กรหากมีคำถาม"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ปิดใช้การพิมพ์แล้ว"</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"คุณสามารถขยายหน้าจอบางส่วนหรือทั้งหมดได้แล้วตอนนี้"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"เปิดในการตั้งค่า"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"ปิด"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"&lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ต้องได้รับสิทธิ์เข้าถึงไมโครโฟนของอุปกรณ์เพื่อดำเนินการต่อ"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ต้องได้รับสิทธิ์เข้าถึงกล้องของอุปกรณ์เพื่อดำเนินการต่อ"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"เปิด"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"ความเป็นส่วนตัวสำหรับเซ็นเซอร์"</string>
 </resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index d3a1fe7..81bfe52 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi lang"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"Cross-SIM na Pagtawag sa <xliff:g id="SPN">%s</xliff:g>"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup na Pagtawag"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Hindi naipasa"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pagkatapos ng <xliff:g id="TIME_DELAY">{2}</xliff:g> (na) segundo"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Serbisyo ng Notification ng Sensor"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Serbisyo ng Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Detector ng Time Zone (Walang koneksyon)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Serbisyo sa Pag-update ng Oras ng GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Buburahin ang iyong device"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Hindi magamit ang admin app. Mabubura na ang iyong device.\n\nKung mayroon kang mga tanong, makipag-ugnayan sa admin ng iyong organisasyon."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Na-disable ng <xliff:g id="OWNER_APP">%s</xliff:g> ang pag-print."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Mama-magnify na ang bahagi o kabuuan ng screen mo"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"I-on sa Mga Setting"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"I-dismiss"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Para magpatuloy, kailangan ng &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ng access sa mikropono ng iyong device."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Para magpatuloy, kailangan ng &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ng access sa camera ng iyong device."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"I-on"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Privacy ng Sensor"</string>
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 26108fb..7095bb8 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Yalnızca kablosuz"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Çapraz SIM Araması"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Yedek Arama"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönlendirilmedi"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> saniye sonra <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensör Bildirim Hizmeti"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Alacakaranlık Hizmeti"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Zaman Dilimi Algılayıcı (Bağlantı yok)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS Zaman Güncelleme Hizmeti"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Cihazınız silinecek"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Yönetim uygulaması kullanılamıyor. Cihazınız şimdi silinecek.\n\nSorularınız varsa kuruluşunuzun yöneticisine başvurun."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Yazdırma işlemi <xliff:g id="OWNER_APP">%s</xliff:g> tarafından devre dışı bırakıldı."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Artık ekranınızın bir kısmını veya tamamını büyütebilirsiniz"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Ayarlar\'da aç"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Kapat"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Devam etmek için &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; uygulamasının cihazınızın mikrofonuna erişmesi gerekiyor."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Devam etmek için &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; uygulamasının cihazınızın kamerasına erişmesi gerekiyor."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Aç"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensör Gizliliği"</string>
 </resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index abb1b7d..9864b4b 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -150,7 +150,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Лише Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> – виклики між SIM-картами"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>: резервний спосіб здійснення викликів"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не переслано"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> після <xliff:g id="TIME_DELAY">{2}</xliff:g> сек."</string>
@@ -207,8 +207,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Сервіс \"Сповіщення датчика\""</string>
     <string name="twilight_service" msgid="8964898045693187224">"Сервіс \"Сутінки\""</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Визначення часового поясу (без Інтернету)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Сервіс оновлення часу GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"З вашого пристрою буде стерто всі дані"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Не можна запускати додаток для адміністраторів. Буде відновлено заводські налаштування пристрою.\n\nЯкщо у вас є запитання, зв’яжіться з адміністратором своєї організації."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Додаток <xliff:g id="OWNER_APP">%s</xliff:g> вимкнув друк."</string>
@@ -2273,12 +2272,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Тепер можна збільшити весь екран або його частину"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Увімкнути в налаштуваннях"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Закрити"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Щоб продовжити, надайте додатку &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ до мікрофона пристрою."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Щоб продовжити, надайте додатку &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ до камери пристрою."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Увімкнути"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Конфіденційність датчиків"</string>
 </resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index f247889..64fb2fc 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -148,7 +148,8 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"‏صرف Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"‏<xliff:g id="SPN">%s</xliff:g> کراس sim کالنگ"</string>
+    <!-- no translation found for crossSimFormat_spn_cross_sim_calling (5620807020002879057) -->
+    <skip />
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : فارورڈ نہیں کی گئی"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> بعد از <xliff:g id="TIME_DELAY">{2}</xliff:g> سیکنڈ"</string>
@@ -203,8 +204,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"سینسر نوٹیفکیشن سروس"</string>
     <string name="twilight_service" msgid="8964898045693187224">"شفقی سروس"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"ٹائم زون ڈیٹیکٹر (کوئی کنیکٹوٹی نہیں ہے)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"‏GNSS کی ٹائم اپ ڈیٹ سروس"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"آپ کا آلہ صاف کر دیا جائے گا"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"منتظم کی ایپ استعمال نہیں کی جا سکتی۔ آپ کا آلہ اب مٹا دیا جائے گا۔\n\nاگر آپ کے سوالات ہیں تو اپنی تنظیم کے منتظم سے رابطہ کریں۔"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> نے پرنٹنگ کو غیر فعال کر دیا ہے۔"</string>
@@ -2205,12 +2205,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"اب آپ اپنی تمام یا کچھ اسکرین کو بڑا کر سکتے ہیں"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"ترتیبات میں آن کریں"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"برخاست کریں"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"‏جاری رکھنے کیلئے ‎<xliff:g id="APP">%s</xliff:g>&lt;b&gt;‎‏‎‎‏‏‎&lt;b&gt;‎ کو آپ کے آلے کے مائیکروفون تک رسائی درکار ہے۔"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"‏جاری رکھنے کیلئے ‎&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;‎ کو آپ کے آلے کے کیمرے تک رسائی درکار ہے۔"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"آن کریں"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"سینسر کی رازداری"</string>
 </resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index e72114a..6612cc9 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -57,7 +57,7 @@
     <string name="imei" msgid="2157082351232630390">"IMEI"</string>
     <string name="meid" msgid="3291227361605924674">"MEID"</string>
     <string name="ClipMmi" msgid="4110549342447630629">"Kiruvchi raqami"</string>
-    <string name="ClirMmi" msgid="6752346475055446417">"Chaqiruvchi raqamini yashirish"</string>
+    <string name="ClirMmi" msgid="6752346475055446417">"Abonent raqamini berkitish"</string>
     <string name="ColpMmi" msgid="4736462893284419302">"Qo‘ng‘iroq qiluvchining raqami"</string>
     <string name="ColrMmi" msgid="5889782479745764278">"Qo‘ng‘iroq qiluvchining raqamini cheklash"</string>
     <string name="CfMmi" msgid="8390012691099787178">"Chaqiruvlarni uzatish"</string>
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Faqat Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Kross-SIM chaqiruvlar"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Zaxiraviy chaqiruv"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yo‘naltirilmadi"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>  <xliff:g id="TIME_DELAY">{2}</xliff:g> soniyadan so‘ng"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Sensorli bildirishnoma xizmati"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight xizmati"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Vaqt mintaqasini aniqlagich (Oflayn)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS yordamida vaqtni yangilash xizmati"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Qurilmangizdagi ma’lumotlar o‘chirib tashlanadi"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Administrator ilovasini ishlatib bo‘lmaydi. Qurilmada barcha ma’lumotlar o‘chirib tashlanadi.\n\nSavollaringiz bo‘lsa, administrator bilan bog‘laning."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Chop etish funksiyasi <xliff:g id="OWNER_APP">%s</xliff:g> tomonidan faolsizlantirilgan."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Ekranni toʻliq yoki qisman kattalashtirish mumkin"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Sozlamalar orqali yoqish"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Yopish"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Davom etish uchun &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; mikrofoningizdan foydalanishi kerak."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Davom etish uchun &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; qurilmangiz kamerasiga kirishi kerak."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Yoqish"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensorlar maxfiyligi"</string>
 </resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 55f5f19..8f8e0bc 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Chỉ Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> Gọi bằng nhiều SIM"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"Gọi điện dự phòng <xliff:g id="SPN">%s</xliff:g>"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> sau <xliff:g id="TIME_DELAY">{2}</xliff:g> giây"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Dịch vụ Thông báo của cảm biến"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Dịch vụ Twilight"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Trình phát hiện múi giờ (Không có kết nối)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Dịch vụ cập nhật thời gian GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Thiết bị của bạn sẽ bị xóa"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Không thể sử dụng ứng dụng quản trị. Thiết bị của bạn sẽ bị xóa ngay bây giờ.\n\nHãy liên hệ với quản trị viên của tổ chức nếu bạn có thắc mắc."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> đã tắt tính năng in."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Giờ đây, bạn có thể phóng to một phần hoặc toàn màn hình"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Bật trong phần Cài đặt"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Đóng"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Để tiếp tục, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; cần quyền truy cập vào micrô trên thiết bị của bạn."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Để tiếp tục, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; cần quyền truy cập vào máy ảnh trên thiết bị của bạn."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Bật"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Quyền riêng tư khi sử dụng cảm biến"</string>
 </resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 347ad56..af5f846 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"仅限 WLAN"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> 跨 SIM 卡通话"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g>备用通话"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g>秒后<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"传感器通知服务"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight 服务"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"时区检测器(无网络连接)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS 时间更新服务"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"系统将清空您的设备"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"无法使用管理应用,系统现在将清空您的设备。\n\n如有疑问,请与您所在单位的管理员联系。"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"“<xliff:g id="OWNER_APP">%s</xliff:g>”已停用打印功能。"</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"您现在可以放大屏幕上的部分或所有内容"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"在“设置”中开启"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"关闭"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"如要继续操作,请向&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;授予设备的麦克风使用权。"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"如要继续操作,请向&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;授予设备的相机使用权。"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"开启"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"传感器隐私权"</string>
 </resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 79ce664..65586f7 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"只限 Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g> 跨 SIM 卡通話"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"「<xliff:g id="SPN">%s</xliff:g>」備用通話網路"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:尚未轉接"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> 於 <xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後轉接"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"感應器通知服務"</string>
     <string name="twilight_service" msgid="8964898045693187224">"暮光服務"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"時區偵測器 (沒有連線)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS 時間更新服務"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"您的裝置將被清除"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"無法使用管理員應用程式。系統會現在清除您的裝置。\n\n如有任何疑問,請聯絡您的機構管理員。"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"「<xliff:g id="OWNER_APP">%s</xliff:g>」暫停了列印。"</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"您現在可以放大部分或整個畫面"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"在「設定」中開啟"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"關閉"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"如要繼續,&lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; 需要裝置的麥克風存取權。"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"如要繼續,&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; 需要裝置的相機存取權。"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"開啟"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"感應器私隱"</string>
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 15a1201..6facceb 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"只限 Wi-Fi"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"「<xliff:g id="SPN">%s</xliff:g>」跨 SIM 卡通話"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"「<xliff:g id="SPN">%s</xliff:g>」備用通話網路"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:未轉接"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後 <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"感應器通知服務"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Twilight 服務"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"時區偵測器 (不必連上網路)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS 時間更新服務"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"你的裝置資料將遭到清除"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"無法使用管理應用程式,系統現在將清除你裝置中的資料。\n\n如有任何問題,請與貴機構的管理員聯絡。"</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"「<xliff:g id="OWNER_APP">%s</xliff:g>」已停用列印功能。"</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"你可以放大局部或整個畫面"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"在「設定」中開啟"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"關閉"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"如要繼續操作,請將裝置的麥克風存取權授予「<xliff:g id="APP">%s</xliff:g>」&lt;b&gt;&lt;/b&gt;。"</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"如要繼續操作,請將裝置的相機存取權授予「<xliff:g id="APP">%s</xliff:g>」&lt;b&gt;&lt;/b&gt;。"</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"開啟"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"感應器隱私權"</string>
 </resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index b18afe7..3b22e94 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -148,7 +148,7 @@
     <string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"I-Wi-Fi kuphela"</string>
     <!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
     <skip />
-    <string name="crossSimFormat_spn_cross_sim_calling" msgid="779976494687695991">"<xliff:g id="SPN">%s</xliff:g>Ukwenza ikholi kwe-Cross Sim"</string>
+    <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Ukushaya Ikholi Kwekhopi Yasenqolobaneni"</string>
     <string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Akudlulisiwe"</string>
     <string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
     <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> emuva kwamasekhondi angu-<xliff:g id="TIME_DELAY">{2}</xliff:g>"</string>
@@ -203,8 +203,7 @@
     <string name="sensor_notification_service" msgid="7474531979178682676">"Isevisi Yesaziso Senzwa"</string>
     <string name="twilight_service" msgid="8964898045693187224">"Isevisi Yangovivi"</string>
     <string name="offline_location_time_zone_detection_service_attribution" msgid="303754195048744816">"Isitholi Sezoni Yesikhathi (Akukho ukuxhumana)"</string>
-    <!-- no translation found for gnss_time_update_service (9039489496037616095) -->
-    <skip />
+    <string name="gnss_time_update_service" msgid="9039489496037616095">"Isevisi Ebuyekeziwe Yesikhathi se-GNSS"</string>
     <string name="factory_reset_warning" msgid="6858705527798047809">"Idivayisi yakho izosulwa"</string>
     <string name="factory_reset_message" msgid="2657049595153992213">"Uhlelo lokusebenza lomlawuli alikwazi ukusetshenziswa. Idivayisi yakho manje izosuswa.\n\nUma unemibuzo, xhumana nomlawuli wezinhlangano zakho."</string>
     <string name="printing_disabled_by" msgid="3517499806528864633">"Ukuphrinta kukhutshazwe nge-<xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -2205,12 +2204,8 @@
     <string name="window_magnification_prompt_content" msgid="4166711383253283838">"Manje usungakwazi ukukhulisa esinye noma sonke isikrini sakho"</string>
     <string name="turn_on_magnification_settings_action" msgid="8521433346684847700">"Vula Kumasethingi"</string>
     <string name="dismiss_action" msgid="1728820550388704784">"Cashisa"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_notification_content (8063355861118105607) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_notification_content (4738005643315863736) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (7921147002346108119) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_notification_channel_label (936036783155261349) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_notification_content" msgid="8063355861118105607">"Ukuze uqhubeke, &lt;b&gt;‎‏‎‎‏‏‎i-<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; idinga ukufinyelela imakrofoni yedivayisi yakho."</string>
+    <string name="sensor_privacy_start_use_camera_notification_content" msgid="4738005643315863736">"Ukuze uqhubeke, &lt;b&gt;i-<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; idinga ukufinyelela ikhamera yakho."</string>
+    <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7921147002346108119">"Vula"</string>
+    <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Ubumfihlo Benzwa"</string>
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 243c244..14df775 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -87,14 +87,12 @@
              theme does not set this value, meaning it is based on whether the
              window is floating. -->
         <attr name="backgroundDimEnabled" format="boolean" />
-        <!-- When windowBackgroundBlurEnabled is set, this is the amount of blur to apply
-             behind the window. The range is from 0, which means no blur, to 150.
-             @hide @SystemApi -->
-        <attr name="windowBackgroundBlurRadius" format="dimension"/>
-        <!-- If set, the area behind the window will be blurred with radius
-             windowBackgroundBlurRadius.
-             @hide @SystemApi -->
-        <attr name="windowBackgroundBlurEnabled" format="boolean" />
+        <!-- When windowBlurBehindEnabled is set, this is the amount of blur to apply
+             behind the window. The range is from 0, which means no blur, to 150.  -->
+        <attr name="windowBlurBehindRadius" format="dimension"/>
+        <!-- If set, everything behind the window will be blurred with radius
+             windowBackgroundBlurRadius. -->
+        <attr name="windowBlurBehindEnabled" format="boolean" />
 
 
         <!-- Color of background imagery used for popup windows. -->
@@ -1974,8 +1972,8 @@
         <attr name="textColor" />
         <attr name="backgroundDimEnabled" />
         <attr name="backgroundDimAmount" />
-        <attr name="windowBackgroundBlurEnabled" />
-        <attr name="windowBackgroundBlurRadius" />
+        <attr name="windowBlurBehindEnabled" />
+        <attr name="windowBlurBehindRadius" />
         <attr name="windowActionBar" />
         <attr name="windowActionModeOverlay" />
         <attr name="windowActionBarOverlay" />
@@ -4069,6 +4067,13 @@
         <attr name="dial" format="reference"/>
         <attr name="hand_hour" format="reference"/>
         <attr name="hand_minute" format="reference"/>
+        <attr name="hand_second" format="reference"/>
+        <!-- Specifies the time zone to use. When this attribute is specified, the
+             TextClock will ignore the time zone of the system. To use the user's
+             time zone, do not specify this attribute. The default value is the
+             user's time zone. Please refer to {@link java.util.TimeZone} for more
+             information about time zone ids. -->
+        <attr name="timeZone" format="string"/>
     </declare-styleable>
     <declare-styleable name="Button">
     </declare-styleable>
@@ -8288,6 +8293,23 @@
     </declare-styleable>
 
     <!-- =============================== -->
+    <!-- System Speech Recognition attributes -->
+    <!-- =============================== -->
+    <eat-comment />
+
+    <!-- Use <code>on-device-recognition-service</code> as the root tag of the XML resource that
+         describes a {@link android.service.speech.RecognitionService}, which is referenced
+         from its {@link android.service.speech.RecognitionService#SERVICE_META_DATA} meta-data
+         entry.
+         @hide @SystemApi
+    -->
+    <declare-styleable name="OnDeviceRecognitionService">
+        <!-- Fully qualified class name of an activity that allows the user to modify
+             the settings for this service. -->
+        <attr name="settingsActivity" />
+    </declare-styleable>
+
+    <!-- =============================== -->
     <!-- Content Capture attributes -->
     <!-- =============================== -->
     <eat-comment />
@@ -8418,6 +8440,9 @@
         <attr name="errorColor" format="color|reference" />
         <!-- The success color -->
         <attr name="successColor" format="color|reference"/>
+        <!-- The dot color -->
+        <attr name="dotColor" format="color|reference"/>
+
     </declare-styleable>
 
     <!-- =============================== -->
@@ -8475,6 +8500,9 @@
              interaction requests from an Activity. This flag is new in
              {@link android.os.Build.VERSION_CODES#N} and not used in previous versions. -->
         <attr name="supportsLocalInteraction" format="boolean" />
+        <!-- The service that provides {@link android.service.voice.HotwordDetectionService}.
+             @hide @SystemApi -->
+        <attr name="hotwordDetectionService" format="string" />
     </declare-styleable>
 
     <!-- Use <code>voice-enrollment-application</code>
@@ -9176,6 +9204,9 @@
         <!-- @hide From Theme.colorBackground, used for the TaskDescription background
                    color. -->
         <attr name="colorBackground" />
+        <!-- @hide From Theme.colorBackgroundFloating, used for the TaskDescription background
+                   color floating. -->
+        <attr name="colorBackgroundFloating" />
         <!-- @hide From Theme.statusBarColor, used for the TaskDescription status bar color. -->
         <attr name="statusBarColor"/>
         <!-- @hide From Theme.navigationBarColor, used for the TaskDescription navigation bar
@@ -9259,6 +9290,12 @@
              {@deprecated Use app:fontProviderCerts with Jetpack Core library instead.}
          -->
         <attr name="fontProviderCerts" format="reference" />
+        <!-- Provides the system font family name to check before downloading the font. For example
+        if the fontProviderQuery asked for "Sans Serif", it is possible to define
+        fontProviderSystemFontFamily as "sans-serif" to tell the system to use "sans-serif" font
+        family if it exists on the system.
+         -->
+        <attr name="fontProviderSystemFontFamily" format="string" />
     </declare-styleable>
 
     <!-- Attributes that are read when parsing a  tag. -->
diff --git a/core/res/res/values/attrs_car.xml b/core/res/res/values/attrs_car.xml
new file mode 100644
index 0000000..6bfea97
--- /dev/null
+++ b/core/res/res/values/attrs_car.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+     the documentation output. To suppress comment lines from the documentation
+     output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+     <attr name="state_ux_restricted" format="boolean"/>
+</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c16588c..b4e580a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1580,6 +1580,13 @@
        <enum name="always" value="1" />
     </attr>
 
+    <attr name="memtagMode">
+       <enum name="default" value="-1" />
+       <enum name="off" value="0" />
+       <enum name="async" value="1" />
+       <enum name="sync" value="2" />
+    </attr>
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -1847,6 +1854,10 @@
 
         <attr name="gwpAsanMode" />
 
+        <attr name="memtagMode" />
+
+        <attr name="nativeHeapZeroInit" format="boolean" />
+
         <!-- @hide no longer used, kept to preserve padding -->
         <attr name="allowAutoRevokePermissionsExemption" format="boolean" />
 
@@ -2417,6 +2428,8 @@
         <!-- Required name of the process that is allowed -->
         <attr name="process" />
         <attr name="gwpAsanMode" />
+        <attr name="memtagMode" />
+        <attr name="nativeHeapZeroInit" />
     </declare-styleable>
 
     <!-- The <code>deny-permission</code> tag specifies that a permission is to be denied
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 8940776..20a5d37 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -240,34 +240,34 @@
     <color name="system_main_0">#ffffff</color>
     <!-- Shade of the main system color at 95% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_50">#ebf1f8</color>
+    <color name="system_main_50">#f0f0f0</color>
     <!-- Shade of the main system color at 90% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_100">#dde3ea</color>
+    <color name="system_main_100">#e2e2e2</color>
     <!-- Shade of the main system color at 80% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_200">#c1c7cd</color>
+    <color name="system_main_200">#c6c6c6</color>
     <!-- Shade of the main system color at 70% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_300">#a6acb2</color>
+    <color name="system_main_300">#ababab</color>
     <!-- Shade of the main system color at 60% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_400">#8b9197</color>
+    <color name="system_main_400">#909090</color>
     <!-- Shade of the main system color at 50% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_500">#72787d</color>
+    <color name="system_main_500">#757575</color>
     <!-- Shade of the main system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_600">#595f64</color>
+    <color name="system_main_600">#5e5e5e</color>
     <!-- Shade of the main system color at 30% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_700">#42474d</color>
+    <color name="system_main_700">#464646</color>
     <!-- Shade of the main system color at 20% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_800">#2c3136</color>
+    <color name="system_main_800">#303030</color>
     <!-- Shade of the main system color at 10% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_main_900">#171c21</color>
+    <color name="system_main_900">#1b1b1b</color>
     <!-- Darkest shade of the main color used by the system. Black.
      This value can be overlaid at runtime by OverlayManager RROs. -->
     <color name="system_main_1000">#000000</color>
@@ -292,7 +292,7 @@
     <color name="system_accent_400">#1fa293</color>
     <!-- Shade of the accent system color at 50% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
-    <color name="system_accent_500">#00877a</color>
+    <color name="system_accent_500">#008377</color>
     <!-- Shade of the accent system color at 40% lightness.
      This value can be overlaid at runtime by OverlayManager RROs. -->
     <color name="system_accent_600">#006d61</color>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index e46147e..c8cccc4 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -17,14 +17,14 @@
 <!-- Colors specific to DeviceDefault themes. These are mostly pass-throughs to enable
      overlaying new theme colors. -->
 <resources>
-    <color name="primary_device_default_dark">@color/primary_material_dark</color>
-    <color name="primary_device_default_light">@color/primary_material_light</color>
-    <color name="primary_device_default_settings">@color/primary_material_settings</color>
-    <color name="primary_device_default_settings_light">@color/primary_material_settings_light</color>
-    <color name="primary_dark_device_default_dark">@color/primary_dark_material_dark</color>
-    <color name="primary_dark_device_default_light">@color/primary_dark_material_light</color>
-    <color name="primary_dark_device_default_settings">@color/primary_dark_material_settings</color>
-    <color name="primary_dark_device_default_settings_light">@color/primary_dark_material_settings_light</color>
+    <color name="primary_device_default_dark">@color/system_main_800</color>
+    <color name="primary_device_default_light">@color/system_main_50</color>
+    <color name="primary_device_default_settings">@color/system_main_800</color>
+    <color name="primary_device_default_settings_light">@color/primary_device_default_light</color>
+    <color name="primary_dark_device_default_dark">@color/primary_device_default_dark</color>
+    <color name="primary_dark_device_default_light">@color/primary_device_default_light</color>
+    <color name="primary_dark_device_default_settings">@color/primary_device_default_dark</color>
+    <color name="primary_dark_device_default_settings_light">@color/primary_device_default_light</color>
 
     <color name="navigation_bar_divider_device_default_settings">#1f000000</color>
 
@@ -37,17 +37,24 @@
     <color name="accent_device_default_dark">@color/system_accent_200</color>
     <color name="accent_device_default">@color/accent_device_default_light</color>
 
-    <color name="background_device_default_dark">@color/system_main_900</color>
+    <color name="background_device_default_dark">@color/system_main_800</color>
     <color name="background_device_default_light">@color/system_main_50</color>
-    <color name="background_floating_device_default_dark">@color/system_main_800</color>
+    <color name="background_floating_device_default_dark">@color/system_main_900</color>
     <color name="background_floating_device_default_light">@color/system_main_100</color>
 
+    <color name="text_color_primary_device_default_light">@color/system_main_900</color>
+    <color name="text_color_primary_device_default_dark">@color/system_main_50</color>
+    <color name="text_color_secondary_device_default_light">@color/system_main_700</color>
+    <color name="text_color_secondary_device_default_dark">@color/system_main_200</color>
+    <color name="text_color_tertiary_device_default_light">@color/system_main_500</color>
+    <color name="text_color_tertiary_device_default_dark">@color/system_main_400</color>
+
     <!-- Error color -->
     <color name="error_color_device_default_dark">@color/error_color_material_dark</color>
     <color name="error_color_device_default_light">@color/error_color_material_light</color>
 
-    <color name="list_divider_color_light">#ffdadce0</color>
-    <color name="list_divider_color_dark">#85ffffff</color>
+    <color name="list_divider_color_light">@color/system_main_500</color>
+    <color name="list_divider_color_dark">@color/system_main_400</color>
     <color name="list_divider_opacity_device_default_light">@android:color/white</color>
     <color name="list_divider_opacity_device_default_dark">@android:color/white</color>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4e272f1..3790aa4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -517,6 +517,9 @@
     <!-- Flag indicating whether we should enable smart battery. -->
     <bool name="config_smart_battery_available">false</bool>
 
+    <!-- Flag indicating whether we should enable camera-based autorotate -->
+    <bool name="config_camera_autorotate">false</bool>
+
     <!-- Fast brightness animation ramp rate in brightness units per second-->
     <integer translatable="false" name="config_brightness_ramp_rate_fast">180</integer>
 
@@ -660,9 +663,15 @@
          The default is false. -->
     <bool name="config_lidControlsSleep">false</bool>
 
-    <!-- The device state (supplied by DeviceStateManager) that should be treated as folded by the
-         display fold controller. Default is DeviceStateManager.INVALID_DEVICE_STATE. -->
-    <integer name="config_foldedDeviceState">-1</integer>
+    <!-- The device states (supplied by DeviceStateManager) that should be treated as folded by the
+         display fold controller. Default is empty. -->
+    <integer-array name="config_foldedDeviceStates">
+        <!-- Example:
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        -->
+    </integer-array>
 
     <!-- Indicate the display area rect for foldable devices in folded state. -->
     <string name="config_foldedArea"></string>
@@ -822,28 +831,21 @@
         <!-- B y-intercept --> <item>-0.198650895</item>
     </string-array>
 
-    <string-array name="config_reduceBrightColorsCoefficientsNative">
-        <!-- R a-coefficient --> <item>-0.691218457</item>
-        <!-- R b-coefficient --> <item>0.050135153</item>
-        <!-- R y-intercept --> <item>0.917684143</item>
-        <!-- G a-coefficient --> <item>-0.691218457</item>
-        <!-- G b-coefficient --> <item>0.050135153</item>
-        <!-- G y-intercept --> <item>0.917684143</item>
-        <!-- B a-coefficient --> <item>-0.691218457</item>
-        <!-- B b-coefficient --> <item>0.050135153</item>
-        <!-- B y-intercept --> <item>0.917684143</item>
+    <!-- Control whether bright color reduction is available. This should only be enabled on devices
+         that have a HWC implementation that can apply the matrix passed to setColorTransform
+         without impacting power, performance, and app compatibility (e.g. protected content). -->
+    <bool name="config_reduceBrightColorsAvailable">@bool/config_setColorTransformAccelerated</bool>
+
+    <string-array name="config_reduceBrightColorsCoefficientsNonlinear">
+        <!-- a-coefficient --> <item>-0.4429953456</item>
+        <!-- b-coefficient --> <item>-0.2434077725</item>
+        <!-- y-intercept --> <item>0.9809063061</item>
     </string-array>
 
     <string-array name="config_reduceBrightColorsCoefficients">
-        <!-- R a-coefficient --> <item>0.00000000000000154</item>
-        <!-- R b-coefficient --> <item>-1.0</item>
-        <!-- R y-intercept --> <item>1.045977011</item>
-        <!-- G a-coefficient --> <item>0.00000000000000224</item>
-        <!-- G b-coefficient --> <item>-1.0</item>
-        <!-- G y-intercept --> <item>1.045977011</item>
-        <!-- B a-coefficient --> <item>0.0000000000000022</item>
-        <!-- B b-coefficient --> <item>-1.0</item>
-        <!-- B y-intercept --> <item>1.045977011</item>
+        <!-- a-coefficient --> <item>-0.000000000000001</item>
+        <!-- b-coefficient --> <item>-0.955555555555554</item>
+        <!-- y-intercept --> <item>1.000000000000000</item>
     </string-array>
 
     <!-- Boolean indicating whether display white balance is supported. -->
@@ -3800,6 +3802,21 @@
 -->
     <string name="config_defaultSearchUiService" translatable="false"></string>
 
+    <!-- The package name for the system's smartspace service.
+     This service returns smartspace results.
+
+     This service must be trusted, as it can be activated without explicit consent of the user.
+     If no service with the specified name exists on the device, smartspace will be disabled.
+     Example: "com.android.intelligence/.SmartspaceService"
+-->
+    <string name="config_defaultSmartspaceService" translatable="false"></string>
+
+    <!-- The package name for the system's speech recognition service.
+         This service must be trusted, as it can be activated without explicit consent of the user.
+         Example: "com.android.speech/.RecognitionService"
+    -->
+    <string name="config_defaultOnDeviceSpeechRecognitionService" translatable="false"></string>
+
     <string name="config_defaultMusicRecognitionService" translatable="false"></string>
 
     <!-- The package name for the default retail demo app.
@@ -4389,6 +4406,15 @@
     <!--If true, allows the device to load udfps components on older HIDL implementations -->
     <bool name="allow_test_udfps" translatable="false" >false</bool>
 
+    <!-- The properties of a UDFPS sensor in pixels, in the order listed below: -->
+    <integer-array name="config_udfps_sensor_props" translatable="false" >
+      <!--
+        <item>sensorLocationX</item>
+        <item>sensorLocationY</item>
+        <item>sensorRadius</item>
+      -->
+    </integer-array>
+
     <!-- Messages that should not be shown to the user during face auth enrollment. This should be
          used to hide messages that may be too chatty or messages that the user can't do much about.
          Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
@@ -4626,6 +4652,27 @@
          maximum screen area that can be occupied by the app in the letterbox mode. -->
     <item name="config_taskLetterboxAspectRatio" format="float" type="dimen">0.0</item>
 
+    <!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and
+         corners of the activity won't be rounded. -->
+    <integer name="config_letterboxActivityCornersRadius">0</integer>
+
+    <!-- Corners appearance of the letterbox background.
+            0 - Solid background using color specified in R.color.config_letterboxBackgroundColor.
+            1 - Color specified in R.attr.colorBackground for the letterboxed application.
+            2 - Color specified in R.attr.colorBackgroundFloating for the letterboxed application.
+        If given value is outside of this range, the option 0 will be assummed. -->
+    <integer name="config_letterboxBackgroundType">0</integer>
+
+    <!-- Color of the letterbox background if one following conditions is true
+            - Option 0 is selected for R.integer.config_letterboxBackgroundType.
+            - Option 1 is selected for R.integer.config_letterboxBackgroundType and
+            R.attr.colorBackground isn't specified for the app.
+            - Option 2 is selected for R.integer.config_letterboxBackgroundType and
+            R.attr.colorBackgroundFloating isn't specified for the app.
+        Defaults to black if not specified.
+     -->
+    <color name="config_letterboxBackgroundColor">#000</color>
+
     <!-- If true, hide the display cutout with display area -->
     <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
 
@@ -4657,4 +4704,10 @@
     <bool name="config_telephony5gStandalone">false</bool>
     <!-- Whether the device enable the non-standalone (NSA) mode of 5G NR.-->
     <bool name="config_telephony5gNonStandalone">false</bool>
+
+    <!-- Whether to select voice/data/sms preference without user confirmation -->
+    <bool name="config_voice_data_sms_auto_fallback">false</bool>
+
+    <!-- Whether to enable the one-handed keyguard on the lock screen for wide-screen devices. -->
+    <bool name="config_enableOneHandedKeyguard">false</bool>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 3ae2131..cb16af5 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -622,9 +622,9 @@
          aliasing effects). This is only used on circular displays. -->
     <dimen name="circular_display_mask_thickness">1px</dimen>
 
-    <dimen name="lock_pattern_dot_line_width">3dp</dimen>
-    <dimen name="lock_pattern_dot_size">12dp</dimen>
-    <dimen name="lock_pattern_dot_size_activated">28dp</dimen>
+    <dimen name="lock_pattern_dot_line_width">22dp</dimen>
+    <dimen name="lock_pattern_dot_size">14dp</dimen>
+    <dimen name="lock_pattern_dot_size_activated">30dp</dimen>
 
     <dimen name="text_handle_min_size">40dp</dimen>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ddf3c5f..30cfb89 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3048,16 +3048,19 @@
     <public name="allowClickWhenDisabled" />
     <public name="windowLayoutAffinity" />
     <public name="canPauseRecording" />
-    <!-- @hide -->
-    <!-- @hide @SystemApi -->
-    <public name="windowBackgroundBlurRadius"/>
-    <!-- @hide @SystemApi -->
-    <public name="windowBackgroundBlurEnabled"/>
+    <public name="windowBlurBehindRadius"/>
+    <public name="windowBlurBehindEnabled"/>
     <public name="requireDeviceScreenOn" />
     <public name="pathSuffix" />
     <public name="sspSuffix" />
     <public name="pathAdvancedPattern" />
     <public name="sspAdvancedPattern" />
+    <public name="fontProviderSystemFontFamily" />
+    <public name="hand_second" />
+    <public name="memtagMode" />
+    <public name="nativeHeapZeroInit" />
+    <!-- @hide @SystemApi -->
+    <public name="hotwordDetectionService" />
   </public-group>
 
   <public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 996fbb3..f6d1b7d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -298,10 +298,10 @@
         <item>@string/crossSimFormat_spn_cross_sim_calling</item>
     </string-array>
 
-    <!-- Spn during Cross-SIM Calling: "<operator> " [CHAR LIMIT=NONE] -->
+    <!-- Spn during Backup Calling: "<operator> " [CHAR LIMIT=NONE] -->
     <string name="crossSimFormat_spn"><xliff:g id="spn" example="Operator">%s</xliff:g></string>
-    <!-- Spn during Cross SIM Calling: "<operator> Cross-SIM Calling" [CHAR LIMIT=NONE] -->
-    <string name="crossSimFormat_spn_cross_sim_calling"><xliff:g id="spn" example="Operator">%s</xliff:g> Cross-SIM Calling</string>
+    <!-- Spn during Backup Calling: "<operator> Backup Calling" [CHAR LIMIT=NONE] -->
+    <string name="crossSimFormat_spn_cross_sim_calling"><xliff:g id="spn" example="Operator">%s</xliff:g> Backup Calling</string>
 
     <!--
         {0} is one of "bearerServiceCode*"
@@ -1585,6 +1585,8 @@
 
     <!-- Template to be used to name enrolled fingerprints by default. -->
     <string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string>
+    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with their fingerprint. [CHAR LIMIT=70] -->
+    <string name="fingerprint_dialog_default_subtitle">Use your fingerprint to continue</string>
 
     <!-- Array containing custom error messages from vendor.  Vendor is expected to add and translate these strings -->
     <string-array name="fingerprint_error_vendor">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e92eb97..ae5e2f5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1881,6 +1881,7 @@
   <java-symbol type="bool" name="config_enableServerNotificationEffectsForAutomotive" />
   <java-symbol type="bool" name="config_useAttentionLight" />
   <java-symbol type="bool" name="config_adaptive_sleep_available" />
+  <java-symbol type="bool" name="config_camera_autorotate"/>
   <java-symbol type="bool" name="config_animateScreenLights" />
   <java-symbol type="bool" name="config_automatic_brightness_available" />
   <java-symbol type="bool" name="config_smart_battery_available" />
@@ -2487,6 +2488,7 @@
   <java-symbol type="string" name="fingerprint_error_lockout" />
   <java-symbol type="string" name="fingerprint_error_lockout_permanent" />
   <java-symbol type="string" name="fingerprint_name_template" />
+  <java-symbol type="string" name="fingerprint_dialog_default_subtitle" />
   <java-symbol type="string" name="fingerprint_authenticated" />
   <java-symbol type="string" name="fingerprint_error_no_fingerprints" />
   <java-symbol type="string" name="fingerprint_error_hw_not_present" />
@@ -2540,6 +2542,7 @@
   <java-symbol type="string" name="config_biometric_prompt_ui_package" />
   <java-symbol type="array" name="config_biometric_sensors" />
   <java-symbol type="bool" name="allow_test_udfps" />
+  <java-symbol type="array" name="config_udfps_sensor_props" />
 
   <java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
   <java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
@@ -3191,8 +3194,9 @@
   <java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" />
+  <java-symbol type="bool" name="config_reduceBrightColorsAvailable" />
   <java-symbol type="array" name="config_reduceBrightColorsCoefficients" />
-  <java-symbol type="array" name="config_reduceBrightColorsCoefficientsNative" />
+  <java-symbol type="array" name="config_reduceBrightColorsCoefficientsNonlinear" />
   <java-symbol type="array" name="config_availableColorModes" />
   <java-symbol type="array" name="config_mappedColorModes" />
   <java-symbol type="string" name="config_vendorColorModesRestoreHint" />
@@ -3489,6 +3493,7 @@
   <java-symbol type="string" name="notification_channel_do_not_disturb" />
   <java-symbol type="string" name="notification_channel_accessibility_magnification" />
   <java-symbol type="string" name="config_defaultAutofillService" />
+  <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
   <java-symbol type="string" name="config_defaultTextClassifierPackage" />
   <java-symbol type="string" name="config_defaultWellbeingPackage" />
   <java-symbol type="string" name="config_defaultContentCaptureService" />
@@ -3497,6 +3502,7 @@
   <java-symbol type="string" name="config_defaultAppPredictionService" />
   <java-symbol type="string" name="config_defaultContentSuggestionsService" />
   <java-symbol type="string" name="config_defaultSearchUiService" />
+  <java-symbol type="string" name="config_defaultSmartspaceService" />
   <java-symbol type="string" name="config_defaultMusicRecognitionService" />
   <java-symbol type="string" name="config_defaultAttentionService" />
   <java-symbol type="string" name="config_defaultRotationResolverService" />
@@ -3727,7 +3733,7 @@
   <java-symbol type="string" name="config_customCountryDetector" />
 
   <!-- For Foldables -->
-  <java-symbol type="integer" name="config_foldedDeviceState" />
+  <java-symbol type="array" name="config_foldedDeviceStates" />
   <java-symbol type="string" name="config_foldedArea" />
 
   <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
@@ -4131,6 +4137,9 @@
   <java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
 
   <java-symbol type="dimen" name="config_taskLetterboxAspectRatio" />
+  <java-symbol type="integer" name="config_letterboxActivityCornersRadius" />
+  <java-symbol type="integer" name="config_letterboxBackgroundType" />
+  <java-symbol type="color" name="config_letterboxBackgroundColor" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
 
@@ -4165,4 +4174,8 @@
 
   <java-symbol type="bool" name="config_telephony5gStandalone" />
   <java-symbol type="bool" name="config_telephony5gNonStandalone" />
+
+  <java-symbol type="bool" name="config_voice_data_sms_auto_fallback" />
+
+  <java-symbol type="bool" name="config_enableOneHandedKeyguard" />
 </resources>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index dff6e87..ee17f6f 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -219,6 +219,9 @@
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
         <item name="colorPopupBackground">?attr/colorBackgroundFloating</item>
         <item name="panelColorBackground">?attr/colorBackgroundFloating</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
     </style>
 
     <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" />
@@ -230,6 +233,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -258,6 +264,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -288,6 +297,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -317,6 +329,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -363,6 +378,9 @@
         <item name="colorError">@color/error_color_device_default_dark</item>
         <item name="colorBackground">@color/background_device_default_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -384,6 +402,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -411,6 +432,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -439,6 +463,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -483,6 +510,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -512,6 +542,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -539,6 +572,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -568,6 +604,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -596,6 +635,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -624,6 +666,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -652,6 +697,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -680,6 +728,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -712,6 +763,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Text styles -->
         <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
@@ -741,6 +795,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -767,6 +824,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -949,6 +1009,9 @@
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
         <item name="colorPopupBackground">?attr/colorBackgroundFloating</item>
         <item name="panelColorBackground">?attr/colorBackgroundFloating</item>
     </style>
@@ -961,6 +1024,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -988,6 +1054,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1016,6 +1085,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1046,6 +1118,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1075,6 +1150,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1123,6 +1201,9 @@
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorBackground">@color/background_device_default_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Progress bar attributes -->
         <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item>
@@ -1143,6 +1224,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1173,6 +1257,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1204,6 +1291,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1236,6 +1326,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
     </style>
 
     <!-- Variant of Theme.DeviceDefault.Dialog.NoActionBar that has a fixed size. -->
@@ -1250,6 +1343,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
     </style>
 
     <!-- DeviceDefault light theme for a window that will be displayed either full-screen on smaller
@@ -1263,6 +1359,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1295,6 +1394,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1325,6 +1427,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1354,6 +1459,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1382,6 +1490,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1410,6 +1521,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1436,6 +1550,9 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1552,6 +1669,9 @@
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1579,6 +1699,9 @@
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
@@ -1616,6 +1739,9 @@
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
@@ -1646,6 +1772,9 @@
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
+        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
+        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
+        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
 
         <!-- Dialog attributes -->
         <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
diff --git a/core/sysprop/WatchdogProperties.sysprop b/core/sysprop/WatchdogProperties.sysprop
index 1bcc773..93e8b78 100644
--- a/core/sysprop/WatchdogProperties.sysprop
+++ b/core/sysprop/WatchdogProperties.sysprop
@@ -16,7 +16,7 @@
 owner: Platform
 
 # To escape the watchdog timeout loop, fatal reboot the system when
-# watchdog timed out 'fatal_count' times in 'fatal_window_second'
+# watchdog timed out 'fatal_count' times in 'fatal_window_seconds'
 # seconds, if both values are not 0. Default value of both is 0.
 prop {
     api_name: "fatal_count"
@@ -26,8 +26,9 @@
     access: Readonly
 }
 
+# See 'fatal_count' for documentation.
 prop {
-    api_name: "fatal_window_second"
+    api_name: "fatal_window_seconds"
     type: Integer
     prop_name: "framework_watchdog.fatal_window.second"
     scope: Internal
@@ -35,9 +36,9 @@
 }
 
 # The fatal counting can be disabled by setting property
-# 'is_fatal_ignore' to true.
+# 'should_ignore_fatal_count' to true.
 prop {
-    api_name: "is_fatal_ignore"
+    api_name: "should_ignore_fatal_count"
     type: Boolean
     prop_name: "persist.debug.framework_watchdog.fatal_ignore"
     scope: Internal
diff --git a/core/sysprop/api/com.android.sysprop.watchdog-latest.txt b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
index d901aef..c846211 100644
--- a/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
+++ b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
@@ -7,13 +7,13 @@
     prop_name: "framework_watchdog.fatal_count"
   }
   prop {
-    api_name: "fatal_window_second"
+    api_name: "fatal_window_seconds"
     type: Integer
     scope: Internal
     prop_name: "framework_watchdog.fatal_window.second"
   }
   prop {
-    api_name: "is_fatal_ignore"
+    api_name: "should_ignore_fatal_count"
     scope: Internal
     prop_name: "persist.debug.framework_watchdog.fatal_ignore"
   }
diff --git a/core/tests/GameManagerTests/Android.bp b/core/tests/GameManagerTests/Android.bp
new file mode 100644
index 0000000..e178776
--- /dev/null
+++ b/core/tests/GameManagerTests/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 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.
+
+android_test {
+    name: "FrameworksCoreGameManagerTests",
+    // Include all test java files
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "frameworks-base-testutils",
+        "junit",
+        "truth-prebuilt",
+    ],
+    libs: ["android.test.runner"],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml
new file mode 100644
index 0000000..6c28607
--- /dev/null
+++ b/core/tests/GameManagerTests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.app.gamemanagertests"
+          android:sharedUserId="android.uid.system" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.app.gamemanagertests"
+                     android:label="Game Manager Tests"/>
+
+</manifest>
diff --git a/core/tests/GameManagerTests/AndroidTest.xml b/core/tests/GameManagerTests/AndroidTest.xml
new file mode 100644
index 0000000..43729923
--- /dev/null
+++ b/core/tests/GameManagerTests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<configuration description="Runs Game Manager Tests.">
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-suite-tag" value="apct-instrumentation"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="FrameworksCoreGameManagerTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.app.gamemanagertests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
new file mode 100644
index 0000000..8f50051
--- /dev/null
+++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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 android.app;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.app.GameManager.GameMode;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link android.app.GameManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class GameManagerTests {
+    private static final String PACKAGE_NAME_0 = "com.android.app0";
+    private static final String PACKAGE_NAME_1 = "com.android.app1";
+
+    private TestGameManagerService mService;
+    private GameManager mGameManager;
+
+    @Before
+    public void setUp() {
+        mService = new TestGameManagerService();
+        mGameManager = new GameManager(
+                InstrumentationRegistry.getContext(), mService);
+    }
+
+    @Test
+    public void testGameModeGetterSetter() {
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                mGameManager.getGameMode(PACKAGE_NAME_0));
+
+        mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                mGameManager.getGameMode(PACKAGE_NAME_1));
+
+        mGameManager.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                mGameManager.getGameMode(PACKAGE_NAME_1));
+    }
+
+    private final class TestGameManagerService extends IGameManagerService.Stub {
+        private final ArrayMap<Pair<String, Integer>, Integer> mGameModes = new ArrayMap<>();
+
+        @Override
+        public @GameMode int getGameMode(String packageName, int userId) {
+            final Pair key = Pair.create(packageName, userId);
+            if (mGameModes.containsKey(key)) {
+                return mGameModes.get(key);
+            }
+            return GameManager.GAME_MODE_UNSUPPORTED;
+        }
+
+        @Override
+        public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
+            mGameModes.put(Pair.create(packageName, userId), gameMode);
+        }
+    }
+}
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index e0d159b..9e6827c 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -63,7 +63,9 @@
     private final List<Entry> mEntries = new ArrayList<>();
 
     public BatteryConsumerData(Context context, BatteryStatsHelper batteryStatsHelper,
-            BatteryUsageStats batteryUsageStats, String batteryConsumerId) {
+            List<BatteryUsageStats> batteryUsageStatsList, String batteryConsumerId) {
+        BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
+        BatteryUsageStats powerProfileModeledUsageStats = batteryUsageStatsList.get(1);
         List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
         BatteryStats batteryStats = batteryStatsHelper.getStats();
 
@@ -142,16 +144,22 @@
         }
 
         BatteryConsumer requestedBatteryConsumer = null;
-        double totalModeledCpuPowerMah = 0;
 
         for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
             if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
                 requestedBatteryConsumer = consumer;
             }
+        }
 
-            totalModeledCpuPowerMah += consumer.getConsumedPowerForCustomComponent(
-                    BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                            + BatteryConsumer.POWER_COMPONENT_CPU);
+        double totalModeledCpuPowerMah = 0;
+        BatteryConsumer requestedBatteryConsumerPowerProfileModeled = null;
+        for (BatteryConsumer consumer : powerProfileModeledUsageStats.getUidBatteryConsumers()) {
+            if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
+                requestedBatteryConsumerPowerProfileModeled = consumer;
+            }
+
+            totalModeledCpuPowerMah += consumer.getConsumedPower(
+                    BatteryConsumer.POWER_COMPONENT_CPU);
         }
 
         if (requestedBatterySipper == null) {
@@ -196,11 +204,10 @@
             addEntry("CPU", EntryType.POWER,
                     requestedBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU),
                     totalCpuPowerMah);
-            if (totalModeledCpuPowerMah != 0) {
+            if (requestedBatteryConsumerPowerProfileModeled != null) {
                 addEntry("CPU (modeled)", EntryType.POWER,
-                        requestedBatteryConsumer.getConsumedPowerForCustomComponent(
-                                BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                                        + BatteryConsumer.POWER_COMPONENT_CPU),
+                        requestedBatteryConsumerPowerProfileModeled.getConsumedPower(
+                                BatteryConsumer.POWER_COMPONENT_CPU),
                         totalModeledCpuPowerMah);
             }
         } else {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index 87a175a..4ead8ee 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -68,7 +68,7 @@
     private ActivityResultLauncher<Void> mStartAppPicker = registerForActivityResult(
             BatteryConsumerPickerActivity.CONTRACT, this::onApplicationSelected);
     private BatteryStatsHelper mBatteryStatsHelper;
-    private BatteryUsageStats mBatteryUsageStats;
+    private List<BatteryUsageStats> mBatteryUsageStats;
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -188,7 +188,8 @@
         }
     }
 
-    private static class BatteryUsageStatsLoader extends AsyncLoaderCompat<BatteryUsageStats> {
+    private static class BatteryUsageStatsLoader extends
+            AsyncLoaderCompat<List<BatteryUsageStats>> {
         private final BatteryStatsManager mBatteryStatsManager;
 
         BatteryUsageStatsLoader(Context context) {
@@ -197,33 +198,38 @@
         }
 
         @Override
-        public BatteryUsageStats loadInBackground() {
-            final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
-                    .includeModeled()
-                    .build();
-            return mBatteryStatsManager.getBatteryUsageStats(query);
+        public List<BatteryUsageStats> loadInBackground() {
+            final BatteryUsageStatsQuery queryDefault =
+                    new BatteryUsageStatsQuery.Builder().build();
+            final BatteryUsageStatsQuery queryPowerProfileModeledOnly =
+                    new BatteryUsageStatsQuery.Builder()
+                            .powerProfileModeledOnly()
+                            .build();
+            return mBatteryStatsManager.getBatteryUsageStats(
+                    List.of(queryDefault, queryPowerProfileModeledOnly));
         }
 
         @Override
-        protected void onDiscardResult(BatteryUsageStats result) {
+        protected void onDiscardResult(List<BatteryUsageStats> result) {
         }
     }
 
-    private class BatteryUsageStatsLoaderCallbacks implements LoaderCallbacks<BatteryUsageStats> {
+    private class BatteryUsageStatsLoaderCallbacks
+            implements LoaderCallbacks<List<BatteryUsageStats>> {
         @NonNull
         @Override
-        public Loader<BatteryUsageStats> onCreateLoader(int id, Bundle args) {
+        public Loader<List<BatteryUsageStats>> onCreateLoader(int id, Bundle args) {
             return new BatteryUsageStatsLoader(BatteryStatsViewerActivity.this);
         }
 
         @Override
-        public void onLoadFinished(@NonNull Loader<BatteryUsageStats> loader,
-                BatteryUsageStats batteryUsageStats) {
+        public void onLoadFinished(@NonNull Loader<List<BatteryUsageStats>> loader,
+                List<BatteryUsageStats> batteryUsageStats) {
             onBatteryUsageStatsLoaded(batteryUsageStats);
         }
 
         @Override
-        public void onLoaderReset(@NonNull Loader<BatteryUsageStats> loader) {
+        public void onLoaderReset(@NonNull Loader<List<BatteryUsageStats>> loader) {
         }
     }
 
@@ -232,7 +238,7 @@
         onBatteryStatsDataLoaded();
     }
 
-    private void onBatteryUsageStatsLoaded(BatteryUsageStats batteryUsageStats) {
+    private void onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats) {
         mBatteryUsageStats = batteryUsageStats;
         onBatteryStatsDataLoaded();
     }
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index bb826de..f31233b 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -147,6 +147,9 @@
     <!-- WindowMetricsTest permissions -->
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
+    <!-- WindowContextTest permissions -->
+    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+
     <!-- Allow use of PendingIntent.getIntent() -->
     <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />
 
diff --git a/core/tests/coretests/assets/fonts/OWNERS b/core/tests/coretests/assets/fonts/OWNERS
new file mode 100644
index 0000000..b0e0b9d
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/text/OWNERS
diff --git a/core/tests/coretests/assets/fonts_for_family_selection/OWNERS b/core/tests/coretests/assets/fonts_for_family_selection/OWNERS
new file mode 100644
index 0000000..b0e0b9d
--- /dev/null
+++ b/core/tests/coretests/assets/fonts_for_family_selection/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/text/OWNERS
diff --git a/core/tests/coretests/res/font/OWNERS b/core/tests/coretests/res/font/OWNERS
new file mode 100644
index 0000000..b0e0b9d
--- /dev/null
+++ b/core/tests/coretests/res/font/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/text/OWNERS
diff --git a/core/tests/coretests/res/font/samplexmldownloadedfont.xml b/core/tests/coretests/res/font/samplexmldownloadedfont.xml
index f1bdc47..9c32ffb 100644
--- a/core/tests/coretests/res/font/samplexmldownloadedfont.xml
+++ b/core/tests/coretests/res/font/samplexmldownloadedfont.xml
@@ -2,5 +2,6 @@
 <font-family xmlns:android="http://schemas.android.com/apk/res/android"
         android:fontProviderAuthority="com.example.test.fontprovider.authority"
         android:fontProviderPackage="com.example.test.fontprovider.package"
-        android:fontProviderQuery="MyRequestedFont">
+        android:fontProviderQuery="MyRequestedFont"
+        android:fontProviderSystemFontFamily="my-request-font">
 </font-family>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS
index bd7da0c..b3f3993 100644
--- a/core/tests/coretests/src/android/app/OWNERS
+++ b/core/tests/coretests/src/android/app/OWNERS
@@ -1 +1,6 @@
 per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS
+
+# Notification, DND, Status bar
+per-file *Notification* = file:/packages/SystemUI/OWNERS
+per-file *Zen* = file:/packages/SystemUI/OWNERS
+per-file *StatusBar* = file:/packages/SystemUI/OWNERS
diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/app/WindowContextTest.java
index dcf5e02..48b58c6 100644
--- a/core/tests/coretests/src/android/app/WindowContextTest.java
+++ b/core/tests/coretests/src/android/app/WindowContextTest.java
@@ -18,29 +18,38 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.display.DisplayManager;
+import android.os.Binder;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerImpl;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Tests for {@link WindowContext}
  *
@@ -54,20 +63,14 @@
 @SmallTest
 @Presubmit
 public class WindowContextTest {
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityRule =
+            new ActivityTestRule<>(EmptyActivity.class, false /* initialTouchMode */,
+                    false /* launchActivity */);
+
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private final WindowContext mWindowContext = createWindowContext();
-
-    @Test
-    public void testWindowContextRelease_doRemoveWindowToken() throws Throwable {
-        final IBinder token = mWindowContext.getWindowContextToken();
-        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
-
-        assertTrue("Token must be registered to WMS", wms.isWindowToken(token));
-
-        mWindowContext.release();
-
-        assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token));
-    }
+    private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService();
 
     @Test
     public void testCreateWindowContextWindowManagerAttachClientToken() {
@@ -83,12 +86,134 @@
         assertEquals(mWindowContext.getWindowContextToken(), params.mWindowContextToken);
     }
 
+    /**
+     * Test the {@link WindowContext} life cycle behavior to add a new window token:
+     * <ul>
+     *  <li>The window token is created before adding the first view.</li>
+     *  <li>The window token is registered after adding the first view.</li>
+     *  <li>The window token is removed after {@link WindowContext}'s release.</li>
+     * </ul>
+     */
+    @Test
+    public void testCreateWindowContextNewTokenFromClient() throws Throwable {
+        final IBinder token = mWindowContext.getWindowContextToken();
+
+        // Test that the window token is not created yet.
+        assertFalse("Token must not be registered until adding the first window",
+                mWms.isWindowToken(token));
+
+        final WindowManager.LayoutParams params =
+                new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        final View testView = new View(mWindowContext);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        testView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewAttachedToWindow(View v) {
+                latch.countDown();
+            }
+
+            @Override
+            public void onViewDetachedFromWindow(View v) {}
+        });
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowContext.getSystemService(WindowManager.class).addView(testView, params);
+
+            assertEquals(token, params.mWindowContextToken);
+        });
+
+
+        assertTrue(latch.await(4, TimeUnit.SECONDS));
+
+
+        // Verify that the window token of the window context is created after first addView().
+        assertTrue("Token must exist after adding the first view.",
+                mWms.isWindowToken(token));
+
+        mWindowContext.release();
+
+        // After the window context's release, the window token is also removed.
+        assertFalse("Token must be removed after release.", mWms.isWindowToken(token));
+    }
+
+    /**
+     * Verifies the behavior when window context attaches an {@link Activity} by override
+     * {@link WindowManager.LayoutParams#token}.
+     *
+     * The window context token should be overridden to
+     * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must
+     * not be removed regardless of the release of window context.
+     */
+    @Test
+    public void testCreateWindowContext_AttachActivity_TokenNotRemovedAfterRelease()
+            throws Throwable {
+        mActivityRule.launchActivity(new Intent());
+        final Activity activity = mActivityRule.getActivity();
+        final WindowManager.LayoutParams params = activity.getWindow().getAttributes();
+
+        final WindowContext windowContext = createWindowContext(params.type);
+        final IBinder token = windowContext.getWindowContextToken();
+
+        final View testView = new View(windowContext);
+
+        mInstrumentation.runOnMainSync(() -> {
+            windowContext.getSystemService(WindowManager.class).addView(testView, params);
+
+            assertEquals(token, params.mWindowContextToken);
+        });
+        windowContext.release();
+
+        // Even if the window context is released, the activity should still exist.
+        assertTrue("Token must exist even if the window context is released.",
+                mWms.isWindowToken(activity.getActivityToken()));
+    }
+
+    /**
+     * Verifies the behavior when window context attaches an existing token by override
+     * {@link WindowManager.LayoutParams#token}.
+     *
+     * The window context token should be overridden to
+     * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must not be
+     * removed regardless of release of window context.
+     */
+    @Test
+    public void testCreateWindowContext_AttachWindowToken_TokenNotRemovedAfterRelease()
+            throws Throwable {
+        final WindowContext windowContext = createWindowContext(TYPE_INPUT_METHOD);
+        final IBinder token = windowContext.getWindowContextToken();
+
+        final IBinder existingToken = new Binder();
+        mWms.addWindowToken(existingToken, TYPE_INPUT_METHOD, windowContext.getDisplayId(),
+                null /* options */);
+
+        final WindowManager.LayoutParams params =
+                new WindowManager.LayoutParams(TYPE_INPUT_METHOD);
+        params.token = existingToken;
+        final View testView = new View(windowContext);
+
+        mInstrumentation.runOnMainSync(() -> {
+            windowContext.getSystemService(WindowManager.class).addView(testView, params);
+
+            assertEquals(token, params.mWindowContextToken);
+        });
+        windowContext.release();
+
+        // Even if the window context is released, the existing token should still exist.
+        assertTrue("Token must exist even if the window context is released.",
+                mWms.isWindowToken(existingToken));
+
+        mWms.removeWindowToken(existingToken, DEFAULT_DISPLAY);
+    }
+
     private WindowContext createWindowContext() {
+        return createWindowContext(TYPE_APPLICATION_OVERLAY);
+    }
+
+    private WindowContext createWindowContext(@WindowType int type) {
         final Context instContext = mInstrumentation.getTargetContext();
         final Display display = instContext.getSystemService(DisplayManager.class)
                 .getDisplay(DEFAULT_DISPLAY);
-        final Context context = instContext.createDisplayContext(display);
-        return new WindowContext(context, TYPE_APPLICATION_OVERLAY,
-                null /* options */);
+        return (WindowContext) instContext.createWindowContext(display, type,  null /* options */);
     }
 }
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 0c351d1..81eb213 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -132,7 +132,8 @@
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
                 10,                      // minWidth
-                20                       // minHeight
+                20,                      // minHeight
+                0                        // colorBackgroundFloating
         );
 
         TaskDescription td2 = new TaskDescription();
@@ -155,7 +156,8 @@
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
                 10,                        // minWidth
-                20                         // minHeight
+                20,                        // minHeight
+                0                          // colorBackgroundFloating
         );
 
         TaskDescription td2 = new TaskDescription(
@@ -169,7 +171,8 @@
                 true,                    // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_RESIZEABLE,  // resizeMode
                 102,                     // minWidth
-                202                      // minHeight
+                202,                     // minHeight
+                0                        // colorBackgroundFloating
         );
 
         // Must overwrite all public and hidden fields, since other has all fields set.
@@ -198,7 +201,8 @@
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
                 10,                        // minWidth
-                20                         // minHeight
+                20,                        // minHeight
+                0                          // colorBackgroundFloating
         );
 
         // Normal parceling should keep everything the same.
@@ -219,7 +223,8 @@
                 false,                     // ensureNavigationBarContrastWhenTransparent
                 RESIZE_MODE_UNRESIZEABLE,  // resizeMode
                 10,                        // minWidth
-                20                         // minHeight
+                20,                        // minHeight
+                0                          // colorBackgroundFloating
         );
         // Recycled bitmap will be ignored while parceling.
         tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapRecycled));
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
new file mode 100644
index 0000000..af77b6c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/GenericDocumentTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 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 android.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+
+public class GenericDocumentTest {
+    @Test
+    public void testRecreateFromParcel() {
+        GenericDocument inDoc =
+                new GenericDocument.Builder<>("uri1", "schema1")
+                        .setScore(42)
+                        .setPropertyString("propString", "Hello")
+                        .setPropertyBytes("propBytes", new byte[][] {{1, 2}})
+                        .setPropertyDocument(
+                                "propDocument",
+                                new GenericDocument.Builder<>("uri2", "schema2")
+                                        .setPropertyString("propString", "Goodbye")
+                                        .setPropertyBytes("propBytes", new byte[][] {{3, 4}})
+                                        .build())
+                        .build();
+
+        // Serialize the document
+        Parcel inParcel = Parcel.obtain();
+        inParcel.writeBundle(inDoc.getBundle());
+        byte[] data = inParcel.marshall();
+        inParcel.recycle();
+
+        // Deserialize the document
+        Parcel outParcel = Parcel.obtain();
+        outParcel.unmarshall(data, 0, data.length);
+        outParcel.setDataPosition(0);
+        Bundle outBundle = outParcel.readBundle();
+        outParcel.recycle();
+
+        // Compare results
+        GenericDocument outDoc = new GenericDocument(outBundle);
+        assertThat(inDoc).isEqualTo(outDoc);
+        assertThat(outDoc.getPropertyString("propString")).isEqualTo("Hello");
+        assertThat(outDoc.getPropertyBytesArray("propBytes")).isEqualTo(new byte[][] {{1, 2}});
+        assertThat(outDoc.getPropertyDocument("propDocument").getPropertyString("propString"))
+                .isEqualTo("Goodbye");
+        assertThat(outDoc.getPropertyDocument("propDocument").getPropertyBytesArray("propBytes"))
+                .isEqualTo(new byte[][] {{3, 4}});
+    }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
index 986079f..7d175d9 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/PutDocumentsRequestTest.java
@@ -34,9 +34,9 @@
                         new AppSearchEmail.Builder("test1").build(),
                         new AppSearchEmail.Builder("test2").build());
         PutDocumentsRequest request =
-                new PutDocumentsRequest.Builder().addGenericDocument(emails).build();
+                new PutDocumentsRequest.Builder().addGenericDocuments(emails).build();
 
-        assertThat(request.getDocuments().get(0).getUri()).isEqualTo("test1");
-        assertThat(request.getDocuments().get(1).getUri()).isEqualTo("test2");
+        assertThat(request.getGenericDocuments().get(0).getUri()).isEqualTo("test1");
+        assertThat(request.getGenericDocuments().get(1).getUri()).isEqualTo("test2");
     }
 }
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
index c8cee85..0fe44630 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SearchSpecTest.java
@@ -33,8 +33,8 @@
         SearchSpec searchSpec =
                 new SearchSpec.Builder()
                         .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
-                        .addNamespace("namespace1", "namespace2")
-                        .addSchemaType("schemaTypes1", "schemaTypes2")
+                        .addFilterNamespaces("namespace1", "namespace2")
+                        .addFilterSchemas("schemaTypes1", "schemaTypes2")
                         .addFilterPackageNames("package1", "package2")
                         .setSnippetCount(5)
                         .setSnippetCountPerProperty(10)
@@ -49,7 +49,7 @@
                 .isEqualTo(SearchSpec.TERM_MATCH_PREFIX);
         assertThat(bundle.getStringArrayList(SearchSpec.NAMESPACE_FIELD))
                 .containsExactly("namespace1", "namespace2");
-        assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_TYPE_FIELD))
+        assertThat(bundle.getStringArrayList(SearchSpec.SCHEMA_FIELD))
                 .containsExactly("schemaTypes1", "schemaTypes2");
         assertThat(bundle.getStringArrayList(SearchSpec.PACKAGE_NAME_FIELD))
                 .containsExactly("package1", "package2");
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
index aab9229..bf6b07f 100644
--- a/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/SetSchemaRequestTest.java
@@ -75,12 +75,12 @@
         AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
 
         // By default, the schema is visible.
-        SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
+        SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
         assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
 
         request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         .setSchemaTypeVisibilityForSystemUi("Schema", true)
                         .build();
         assertThat(request.getSchemasNotVisibleToSystemUi()).isEmpty();
@@ -91,7 +91,7 @@
         AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
         SetSchemaRequest request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         .setSchemaTypeVisibilityForSystemUi("Schema", false)
                         .build();
         assertThat(request.getSchemasNotVisibleToSystemUi()).containsExactly("Schema");
@@ -102,7 +102,7 @@
         AppSearchSchema schema = new AppSearchSchema.Builder("Schema").build();
 
         // By default, the schema is not visible.
-        SetSchemaRequest request = new SetSchemaRequest.Builder().addSchema(schema).build();
+        SetSchemaRequest request = new SetSchemaRequest.Builder().addSchemas(schema).build();
         assertThat(request.getSchemasVisibleToPackages()).isEmpty();
 
         PackageIdentifier packageIdentifier =
@@ -112,7 +112,7 @@
 
         request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         .setSchemaTypeVisibilityForPackage(
                                 "Schema", /*visible=*/ true, packageIdentifier)
                         .build();
@@ -126,7 +126,7 @@
 
         SetSchemaRequest request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         .setSchemaTypeVisibilityForPackage(
                                 "Schema",
                                 /*visible=*/ false,
@@ -147,7 +147,7 @@
 
         SetSchemaRequest request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         // Set it visible for "Schema"
                         .setSchemaTypeVisibilityForPackage(
                                 "Schema", /*visible=*/ true, packageIdentifier)
@@ -165,7 +165,7 @@
 
         SetSchemaRequest request =
                 new SetSchemaRequest.Builder()
-                        .addSchema(schema)
+                        .addSchemas(schema)
                         // First set it as visible
                         .setSchemaTypeVisibilityForPackage(
                                 "Schema",
diff --git a/core/tests/coretests/src/android/app/people/OWNERS b/core/tests/coretests/src/android/app/people/OWNERS
new file mode 100644
index 0000000..6ec8e6a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/people/OWNERS
@@ -0,0 +1 @@
+file:/core/java/android/app/people/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index 912db1e..c61a4b5 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -1,3 +1,4 @@
+per-file AssetTest.java = file:/core/java/android/content/res/OWNERS
 per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS
-per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
 per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
+per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
diff --git a/core/tests/coretests/src/android/content/integrity/OWNERS b/core/tests/coretests/src/android/content/integrity/OWNERS
new file mode 100644
index 0000000..4ffc704
--- /dev/null
+++ b/core/tests/coretests/src/android/content/integrity/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/content/integrity/OWNERS
diff --git a/core/tests/coretests/src/android/content/pm/OWNERS b/core/tests/coretests/src/android/content/pm/OWNERS
index 711f5f0..8673365 100644
--- a/core/tests/coretests/src/android/content/pm/OWNERS
+++ b/core/tests/coretests/src/android/content/pm/OWNERS
@@ -1,2 +1,5 @@
-per-file AppSearchPersonTest.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+include /core/java/android/content/pm/OWNERS
 
+per-file AppSearchPersonTest.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+per-file SigningDetailsTest.java = cbrubaker@google.com
+per-file SigningDetailsTest.java = mpgroover@google.com
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 88faa0a..57c0be5 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -78,6 +78,7 @@
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -334,7 +335,8 @@
     private ParsingPackage parsePackage(Uri packageURI) {
         final String archiveFilePath = packageURI.getPath();
         ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(
-                new File(archiveFilePath), 0 /*flags*/, false /*collectCertificates*/);
+                new File(archiveFilePath), 0 /*flags*/, Collections.emptyList(),
+                false /*collectCertificates*/);
         if (result.isError()) {
             throw new IllegalStateException(result.getErrorMessage(), result.getException());
         }
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index f0b4af6..19458da 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -54,6 +54,32 @@
         assertFalse(Signature.areEffectiveMatch(asArray(A, M), asArray(A, B)));
     }
 
+    public void testHashCode_doesNotIncludeFlags() throws Exception {
+        // Some classes rely on the hash code not including the flags / capabilities for the signer
+        // to verify Set membership. This test verifies two signers with the same signature but
+        // different flags have the same hash code.
+        Signature signatureAWithAllCaps = new Signature(A.toCharsString());
+        // There are currently 5 capabilities that can be assigned to a previous signer, although
+        // for the purposes of this test all that matters is that the two flag values are distinct.
+        signatureAWithAllCaps.setFlags(31);
+        Signature signatureAWithNoCaps = new Signature(A.toCharsString());
+        signatureAWithNoCaps.setFlags(0);
+
+        assertEquals(signatureAWithAllCaps.hashCode(), signatureAWithNoCaps.hashCode());
+    }
+
+    public void testEquals_doesNotIncludeFlags() throws Exception {
+        // Similar to above some classes rely on equals only comparing the signature arrays
+        // for equality without including the flags. This test verifies two signers with the
+        // same signature but different flags are still considered equal.
+        Signature signatureAWithAllCaps = new Signature(A.toCharsString());
+        signatureAWithAllCaps.setFlags(31);
+        Signature signatureAWithNoCaps = new Signature(A.toCharsString());
+        signatureAWithNoCaps.setFlags(0);
+
+        assertEquals(signatureAWithAllCaps, signatureAWithNoCaps);
+    }
+
     private static Signature[] asArray(Signature... s) {
         return s;
     }
diff --git a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
index 24f45a5..bffd1e4 100644
--- a/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
+++ b/core/tests/coretests/src/android/content/pm/SigningDetailsTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.fail;
 
 import android.content.pm.PackageParser.SigningDetails;
+import android.util.ArraySet;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -35,6 +36,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Set;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SigningDetailsTest {
@@ -208,8 +211,8 @@
         SigningDetails result1 = noLineageDetails.mergeLineageWith(lineageDetails);
         SigningDetails result2 = lineageDetails.mergeLineageWith(noLineageDetails);
 
-        assertTrue(result1 == lineageDetails);
-        assertTrue(result2 == lineageDetails);
+        assertSigningDetailsContainsLineage(result1, FIRST_SIGNATURE, SECOND_SIGNATURE);
+        assertSigningDetailsContainsLineage(result2, FIRST_SIGNATURE, SECOND_SIGNATURE);
     }
 
     @Test
@@ -271,8 +274,10 @@
         SigningDetails result1 = singleSignerDetails.mergeLineageWith(fullLineageDetails);
         SigningDetails result2 = fullLineageDetails.mergeLineageWith(singleSignerDetails);
 
-        assertTrue(result1 == fullLineageDetails);
-        assertTrue(result2 == fullLineageDetails);
+        assertSigningDetailsContainsLineage(result1, FIRST_SIGNATURE, SECOND_SIGNATURE,
+                THIRD_SIGNATURE);
+        assertSigningDetailsContainsLineage(result2, FIRST_SIGNATURE, SECOND_SIGNATURE,
+                THIRD_SIGNATURE);
     }
 
     @Test
@@ -605,6 +610,213 @@
         assertTrue(secondLineageDetails.hasCommonAncestor(firstLineageDetails));
     }
 
+    @Test
+    public void hasCommonSignerWithCapabilities_singleMatchingSigner_returnsTrue()
+            throws Exception {
+        // The hasCommonSignerWithCapabilities method is intended to grant the specified
+        // capabilities to a requesting package that has a common signer in the lineage (or as the
+        // current signer) even if their signing identities have diverged. This test verifies if the
+        // two SigningDetails have the same single signer then the requested capability can be
+        // granted since the current signer always has all capabilities granted.
+        SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE);
+        SigningDetails secondSignerDetails = createSigningDetails(FIRST_SIGNATURE);
+
+        assertTrue(firstDetails.hasCommonSignerWithCapability(secondSignerDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_singleDifferentSigners_returnsFalse()
+            throws Exception {
+        // If each package is signed by a single different signer then the method should return
+        // false since there is no shared signer.
+        SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE);
+        SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE);
+
+        assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+        assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_oneWithMultipleSigners_returnsFalse()
+            throws Exception {
+        // If one of the packages is signed with multiple signers and the other only a single signer
+        // this method should return false since all signers must match exactly for multiple signer
+        // cases.
+        SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        SigningDetails secondDetails = createSigningDetails(FIRST_SIGNATURE);
+
+        assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+        assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_multipleMatchingSigners_returnsTrue()
+            throws Exception {
+        // if both packages are signed by the same multiple signers then this method should return
+        // true since the current signer is granted all capabilities.
+        SigningDetails firstDetails = createSigningDetails(FIRST_SIGNATURE, SECOND_SIGNATURE);
+        SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE, FIRST_SIGNATURE);
+
+        assertTrue(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+        assertTrue(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_singleSignerInLineage_returnsTrue()
+            throws Exception {
+        // if a single signer is in the lineage and that previous signer has the requested
+        // capability then this method should return true.
+        SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
+                new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
+                new int[]{DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
+        SigningDetails singleSignerDetails = createSigningDetails(FIRST_SIGNATURE);
+
+        assertTrue(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_singleSignerInLineageWOCapability_returnsFalse()
+            throws Exception {
+        // If a single signer is in the lineage and that previous signer does not have the requested
+        // capability then this method should return false.
+        SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
+                new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
+                new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES});
+        SigningDetails singleSignerDetails = createSigningDetails(FIRST_SIGNATURE);
+
+        assertFalse(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_singleSignerMatchesCurrentSigner_returnsTrue()
+            throws Exception {
+        // If a requesting app is signed by the same current signer as an app with a lineage the
+        // method should return true since the current signer is granted all capabilities.
+        SigningDetails lineageDetails = createSigningDetailsWithLineageAndCapabilities(
+                new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE},
+                new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES});
+        SigningDetails singleSignerDetails = createSigningDetails(SECOND_SIGNATURE);
+
+        assertTrue(lineageDetails.hasCommonSignerWithCapability(singleSignerDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_divergingSignersWithCommonSigner_returnsTrue()
+            throws Exception {
+        // This method is intended to allow granting a capability to another app that has a common
+        // signer in the lineage with the capability still granted; this test verifies when the
+        // current signers diverge but a common ancestor has the requested capability this method
+        // returns true.
+        SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+                new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+                new int[]{DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
+        SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+                SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+        assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+                PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_divergingSignersOneGrantsCapability_returnsTrue()
+            throws Exception {
+        // If apps have multiple common signers in the lineage with one denying the requested
+        // capability but the other granting it this method should return true.
+        SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+                new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+                new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
+        SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+                SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+        assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+                PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_divergingSignersNoneGrantCapability_returnsFalse()
+            throws Exception {
+        // If apps have multiple common signers in the lineage with all denying the requested
+        // capability this method should return false.
+        SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+                new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+                new int[]{SHARED_USER_ID, AUTH, DEFAULT_CAPABILITIES});
+        SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+                SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+        assertFalse(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+                PERMISSION));
+    }
+
+    @Test
+    public void
+            hasCommonSignerWithCapabilities_divergingSignersNoneGrantsAllCapabilities_returnsTrue()
+            throws Exception {
+        // If an app has multiple common signers in the lineage, each granting one of the requested
+        // capabilities but neither granting all this method should return false since a single
+        // common ancestor must grant all requested capabilities.
+        SigningDetails firstLineageDetails = createSigningDetailsWithLineageAndCapabilities(
+                new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+                new int[]{SHARED_USER_ID, PERMISSION, DEFAULT_CAPABILITIES});
+        SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+                SECOND_SIGNATURE, FOURTH_SIGNATURE);
+
+        assertFalse(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+                PERMISSION | SHARED_USER_ID));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_currentSignerInLineageOfRequestingApp_returnsTrue()
+            throws Exception {
+        // If the current signer of an app is in the lineage of the requesting app then this method
+        // should return true since the current signer is granted all capabilities.
+        SigningDetails firstLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+                SECOND_SIGNATURE);
+        SigningDetails secondLineageDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+                SECOND_SIGNATURE, THIRD_SIGNATURE);
+
+        assertTrue(firstLineageDetails.hasCommonSignerWithCapability(secondLineageDetails,
+                PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_currentSignerInLineageOfDeclaringApp_returnsTrue()
+            throws Exception {
+        // If the current signer of a requesting app with a lineage is in the lineage of the
+        // declaring app and that previous signature is granted the requested capability the method
+        // should return true.
+        SigningDetails declaringDetails = createSigningDetailsWithLineageAndCapabilities(
+                new String[]{FIRST_SIGNATURE, SECOND_SIGNATURE, THIRD_SIGNATURE},
+                new int[]{SHARED_USER_ID, DEFAULT_CAPABILITIES, DEFAULT_CAPABILITIES});
+        SigningDetails requestingDetails = createSigningDetailsWithLineage(FIRST_SIGNATURE,
+                SECOND_SIGNATURE);
+
+        assertTrue(declaringDetails.hasCommonSignerWithCapability(requestingDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_oneSignerNullLineage_returns() throws Exception {
+        // While the pastSigningCertificates should only be null in the case of multiple current
+        // signers there are instances where this can be null with a single signer; verify that a
+        // null pastSigningCertificates array in either SigningDetails does not result in a
+        // NullPointerException.
+        SigningDetails firstDetails = createSigningDetails(true, FIRST_SIGNATURE);
+        SigningDetails secondDetails = createSigningDetails(SECOND_SIGNATURE);
+
+        assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+        assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+    }
+
+    @Test
+    public void hasCommonSignerWithCapabilities_unknownSigner_returnsFalse() throws Exception {
+        // An unknown SigningDetails for either instance should immediately result in false being
+        // returned.
+        SigningDetails firstDetails = SigningDetails.UNKNOWN;
+        SigningDetails secondDetails = createSigningDetails(FIRST_SIGNATURE);
+
+        assertFalse(firstDetails.hasCommonSignerWithCapability(secondDetails, PERMISSION));
+        assertFalse(secondDetails.hasCommonSignerWithCapability(firstDetails, PERMISSION));
+    }
+
     private SigningDetails createSigningDetailsWithLineage(String... signers) throws Exception {
         int[] capabilities = new int[signers.length];
         for (int i = 0; i < capabilities.length; i++) {
@@ -629,10 +841,34 @@
     }
 
     private SigningDetails createSigningDetails(String... signers) throws Exception {
+        return createSigningDetails(false, signers);
+    }
+
+    private SigningDetails createSigningDetails(boolean useNullPastSigners, String... signers)
+            throws Exception {
         Signature[] currentSignatures = new Signature[signers.length];
         for (int i = 0; i < signers.length; i++) {
             currentSignatures[i] = new Signature(signers[i]);
         }
-        return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
+        // If there are multiple signers then the pastSigningCertificates should be set to null, but
+        // if there is only a single signer both the current signer and the past signers should be
+        // set to that one signer.
+        if (signers.length > 1) {
+            return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, null);
+        }
+        return new SigningDetails(currentSignatures, SIGNING_BLOCK_V3, currentSignatures);
+    }
+
+    private void assertSigningDetailsContainsLineage(SigningDetails details,
+            String... pastSigners) {
+        // This method should only be invoked for results that contain a single signer.
+        assertEquals(1, details.signatures.length);
+        assertTrue(details.signatures[0].toCharsString().equalsIgnoreCase(
+                pastSigners[pastSigners.length - 1]));
+        Set<String> signatures = new ArraySet<>(pastSigners);
+        for (Signature pastSignature : details.pastSigningCertificates) {
+            assertTrue(signatures.remove(pastSignature.toCharsString()));
+        }
+        assertEquals(0, signatures.size());
     }
 }
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 7ab9d7f..57f01e9 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -105,6 +105,7 @@
         assertEquals("com.example.test.fontprovider.authority", providerEntry.getAuthority());
         assertEquals("com.example.test.fontprovider.package", providerEntry.getPackage());
         assertEquals("MyRequestedFont", providerEntry.getQuery());
+        assertEquals("my-request-font", providerEntry.getSystemFontFamilyName());
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/content/res/OWNERS b/core/tests/coretests/src/android/content/res/OWNERS
new file mode 100644
index 0000000..3e79d8f
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/content/res/OWNERS
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 65ea2a8..3df2e90 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -171,7 +171,8 @@
         FontConfig fontConfig;
         try {
             fontConfig = FontListParser.parse(
-                    TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap);
+                    TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap, 0,
+                    0);
         } catch (IOException | XmlPullParserException e) {
             throw new RuntimeException(e);
         }
@@ -199,7 +200,7 @@
         FontConfig fontConfig;
         try {
             fontConfig = FontListParser.parse(
-                    SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null);
+                    SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null, 0, 0);
         } catch (IOException | XmlPullParserException e) {
             throw new RuntimeException(e);
         }
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
index b319886..341ee37 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java
@@ -149,7 +149,7 @@
                 0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */,
                 0 /* sources */, 0 /* keyboardType */, null /* keyCharacterMap */,
                 false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */,
-                true /* hasSensor */);
+                true /* hasSensor */, false /* hasBattery */);
         assertTrue(d.hasSensor());
         return d;
     }
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index 6955ca8..564103e 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -117,6 +117,61 @@
     }
 
     @Test
+    public void testDurationMono() {
+        assertEquals(1, CombinedVibrationEffect.createSynced(
+                VibrationEffect.createOneShot(1, 1)).getDuration());
+        assertEquals(-1, CombinedVibrationEffect.createSynced(
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration());
+        assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.createSynced(
+                VibrationEffect.createWaveform(
+                        new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0)).getDuration());
+    }
+
+    @Test
+    public void testDurationStereo() {
+        assertEquals(6, CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.createOneShot(1, 1))
+                .addVibrator(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+                .combine()
+                .getDuration());
+        assertEquals(-1, CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+                .combine()
+                .getDuration());
+        assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
+                .combine()
+                .getDuration());
+    }
+
+    @Test
+    public void testDurationSequential() {
+        assertEquals(26, CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.createOneShot(10, 10), 10)
+                .addNext(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+                .combine()
+                .getDuration());
+        assertEquals(-1, CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, -1))
+                .combine()
+                .getDuration());
+        assertEquals(Long.MAX_VALUE, CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addNext(2,
+                        VibrationEffect.createWaveform(new long[]{1, 2, 3}, new int[]{1, 2, 3}, 0))
+                .combine()
+                .getDuration());
+    }
+
+    @Test
     public void testSerializationMono() {
         CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
 
diff --git a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
new file mode 100644
index 0000000..be38260
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2020 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 android.provider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ContentValues;
+import android.os.Parcel;
+import android.provider.SimPhonebookContract.SimRecords.NameValidationResult;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SimPhonebookContractTest {
+
+    @Test
+    public void getContentUri_invalidEfType_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () ->
+                SimPhonebookContract.SimRecords.getContentUri(1, 100)
+        );
+        assertThrows(IllegalArgumentException.class, () ->
+                SimPhonebookContract.SimRecords.getContentUri(1, -1)
+        );
+        assertThrows(IllegalArgumentException.class, () ->
+                SimPhonebookContract.SimRecords.getContentUri(1,
+                        SimPhonebookContract.ElementaryFiles.EF_UNKNOWN)
+        );
+    }
+
+    @Test
+    public void getItemUri_invalidEfType_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () ->
+                SimPhonebookContract.SimRecords.getItemUri(1, 100, 1)
+        );
+        assertThrows(IllegalArgumentException.class, () ->
+                SimPhonebookContract.SimRecords.getItemUri(1, -1, 1)
+        );
+        assertThrows(IllegalArgumentException.class, () ->
+                SimPhonebookContract.SimRecords.getItemUri(1,
+                        SimPhonebookContract.ElementaryFiles.EF_UNKNOWN, 1)
+        );
+    }
+
+    @Test
+    public void getItemUri_invalidRecordIndex_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () ->
+                SimPhonebookContract.SimRecords.getItemUri(1,
+                        SimPhonebookContract.ElementaryFiles.EF_ADN, 0)
+        );
+        assertThrows(IllegalArgumentException.class, () ->
+                SimPhonebookContract.SimRecords.getItemUri(1,
+                        SimPhonebookContract.ElementaryFiles.EF_ADN, -1)
+        );
+    }
+
+    @Test
+    public void nameValidationResult_isValid_validNames() {
+        assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue();
+        assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue();
+        assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue();
+        assertThat(
+                new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue();
+    }
+
+    @Test
+    public void nameValidationResult_isValid_invalidNames() {
+        assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse();
+        assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse();
+        NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c",
+                "A b c", 5, 5);
+        assertThat(unsupportedCharactersResult.isValid()).isFalse();
+        assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue();
+        assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse();
+        assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue();
+        assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse();
+        assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue();
+    }
+
+    @Test
+    public void nameValidationResult_parcel() {
+        ContentValues values = new ContentValues();
+        values.put("name", "Name");
+        values.put("phone_number", "123");
+
+        NameValidationResult result;
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0);
+            parcel.setDataPosition(0);
+            result = parcel.readParcelable(NameValidationResult.class.getClassLoader());
+        } finally {
+            parcel.recycle();
+        }
+
+        assertThat(result.getName()).isEqualTo("name");
+        assertThat(result.getSanitizedName()).isEqualTo("sanitized name");
+        assertThat(result.getEncodedLength()).isEqualTo(1);
+        assertThat(result.getMaxEncodedLength()).isEqualTo(2);
+    }
+}
+
diff --git a/core/tests/coretests/src/android/text/FontFallbackSetup.java b/core/tests/coretests/src/android/text/FontFallbackSetup.java
index e301037..90a6ca3 100644
--- a/core/tests/coretests/src/android/text/FontFallbackSetup.java
+++ b/core/tests/coretests/src/android/text/FontFallbackSetup.java
@@ -79,7 +79,7 @@
 
         FontConfig fontConfig;
         try {
-            fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null);
+            fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null, 0, 0);
         } catch (IOException | XmlPullParserException e) {
             throw new RuntimeException(e);
         }
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index d02c6d5..a5261ae 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -22,6 +22,8 @@
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -30,6 +32,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Insets;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
@@ -130,6 +133,8 @@
     @Test
     public void testHasCutout_noCutout() throws Exception {
         assertTrue(NO_CUTOUT.isBoundsEmpty());
+        assertThat(NO_CUTOUT.getWaterfallInsets(), equalTo(Insets.NONE));
+        assertThat(NO_CUTOUT.getCutoutPath(), nullValue());
     }
 
     @Test
@@ -165,6 +170,59 @@
     }
 
     @Test
+    public void testGetCutoutPath() throws Exception {
+        final String cutoutSpecString = "L1,0 L1,1 L0,1 z";
+        final int displayWidth = 200;
+        final int displayHeight = 400;
+        final float density = 1f;
+        final DisplayCutout cutout = fromSpec(cutoutSpecString, displayWidth, displayHeight,
+                density, Insets.NONE);
+        assertThat(cutout.getCutoutPath(), notNullValue());
+    }
+
+    @Test
+    public void testGetCutoutPath_caches() throws Exception {
+        final String cutoutSpecString = "L1,0 L1,1 L0,1 z";
+        final int displayWidth = 200;
+        final int displayHeight = 400;
+        final float density = 1f;
+        final Path first = fromSpec(cutoutSpecString, displayWidth, displayHeight,
+                density, Insets.NONE).getCutoutPath();
+        final Path second = fromSpec(cutoutSpecString, displayWidth, displayHeight,
+                density, Insets.NONE).getCutoutPath();
+        assertThat(first, equalTo(second));
+    }
+
+    @Test
+    public void testGetCutoutPath_wontCacheIfCutoutPathParerInfoChanged() throws Exception {
+        final int displayWidth = 200;
+        final int displayHeight = 400;
+        final float density = 1f;
+        final Path first = fromSpec("L1,0 L1,1 L0,1 z", displayWidth, displayHeight,
+                density, Insets.NONE).getCutoutPath();
+        final Path second = fromSpec("L2,0 L2,2 L0,2 z", displayWidth, displayHeight,
+                density, Insets.NONE).getCutoutPath();
+        assertThat(first, not(equalTo(second)));
+    }
+
+    @Test
+    public void testGetCutoutPathParserInfo() throws Exception {
+        final String cutoutSpecString = "L1,0 L1,1 L0,1 z";
+        final int displayWidth = 200;
+        final int displayHeight = 400;
+        final float density = 1f;
+        final DisplayCutout cutout = fromSpec(cutoutSpecString, displayWidth, displayHeight,
+                density, Insets.NONE);
+        assertThat(displayWidth, equalTo(cutout.getCutoutPathParserInfo().getDisplayWidth()));
+        assertThat(displayHeight, equalTo(cutout.getCutoutPathParserInfo().getDisplayHeight()));
+        assertThat(density, equalTo(cutout.getCutoutPathParserInfo().getDensity()));
+        assertThat(cutoutSpecString.trim(),
+                equalTo(cutout.getCutoutPathParserInfo().getCutoutSpec()));
+        assertThat(0, equalTo(cutout.getCutoutPathParserInfo().getRotation()));
+        assertThat(1f, equalTo(cutout.getCutoutPathParserInfo().getScale()));
+    }
+
+    @Test
     public void testHashCode() throws Exception {
         assertEquals(mCutoutWithWaterfall.hashCode(), createCutoutWithWaterfall().hashCode());
         assertNotEquals(mCutoutWithWaterfall.hashCode(), mCutoutNumbers.hashCode());
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 9705284..cfe3803 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -27,6 +27,10 @@
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
@@ -427,6 +431,27 @@
                 cutout.getBoundingRectBottom());
     }
 
+    @Test
+    public void testCalculateRelativeRoundedCorners() {
+        mState.setDisplayFrame(new Rect(0, 0, 200, 400));
+        mState.setRoundedCorners(new RoundedCorners(
+                new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10),
+                new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10),
+                new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380),
+                new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380)));
+        WindowInsets windowInsets = mState.calculateInsets(new Rect(1, 2, 197, 396), null, false,
+                false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+                WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+        assertEquals(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8),
+                windowInsets.getRoundedCorner(POSITION_TOP_LEFT));
+        assertEquals(new RoundedCorner(POSITION_TOP_RIGHT, 10, 189, 8),
+                windowInsets.getRoundedCorner(POSITION_TOP_RIGHT));
+        assertEquals(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 179, 378),
+                windowInsets.getRoundedCorner(POSITION_BOTTOM_RIGHT));
+        assertEquals(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 19, 378),
+                windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT));
+    }
+
     private void assertEqualsAndHashCode() {
         assertEquals(mState, mState2);
         assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java
new file mode 100644
index 0000000..8eb13bc
--- /dev/null
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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 android.view;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.graphics.Point;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class RoundedCornerTest {
+
+    @Test
+    public void testGetPosition() {
+        RoundedCorner roundedCorner = new RoundedCorner(
+                RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+        assertThat(roundedCorner.getPosition(), is(RoundedCorner.POSITION_BOTTOM_LEFT));
+    }
+
+    @Test
+    public void testGetRadius() {
+        RoundedCorner roundedCorner = new RoundedCorner(
+                RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+        assertThat(roundedCorner.getRadius(), is(2));
+    }
+
+    @Test
+    public void testGetCenter() {
+        RoundedCorner roundedCorner = new RoundedCorner(
+                RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+        assertThat(roundedCorner.getCenter(), equalTo(new Point(3, 4)));
+    }
+
+    @Test
+    public void testIsEmpty() {
+        RoundedCorner roundedCorner = new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+        assertThat(roundedCorner.isEmpty(), is(true));
+    }
+
+    @Test
+    public void testEquals() {
+        RoundedCorner roundedCorner = new RoundedCorner(
+                RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+        RoundedCorner roundedCorner2 = new RoundedCorner(
+                RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
+        assertThat(roundedCorner, equalTo(roundedCorner2));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/RoundedCornersTest.java b/core/tests/coretests/src/android/view/RoundedCornersTest.java
new file mode 100644
index 0000000..07ef33a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/RoundedCornersTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 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 android.view;
+
+import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
+import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
+import static android.view.RoundedCorner.POSITION_TOP_LEFT;
+import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class RoundedCornersTest {
+
+    final RoundedCorners mRoundedCorners = new RoundedCorners(
+            new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10),
+            new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10),
+            new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380),
+            new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380));
+
+    @Test
+    public void testGetRoundedCorner() {
+        final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+        RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400);
+
+        assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT),
+                equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT),
+                equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT),
+                equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT),
+                equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380)));
+    }
+
+    @Test
+    public void testGetRoundedCorner_noRoundedCorners() {
+        RoundedCorners roundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+
+        assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT), nullValue());
+        assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT), nullValue());
+        assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT), nullValue());
+        assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), nullValue());
+    }
+
+    @Test
+    public void testHashCode() {
+        assertThat(mRoundedCorners.hashCode(),
+                equalTo(RoundedCorners.fromRadii(new Pair<>(10, 20), 200, 400).hashCode()));
+        assertThat(mRoundedCorners.hashCode(),
+                not(equalTo(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400).hashCode())));
+    }
+
+    @Test
+    public void testEquals() {
+        assertThat(mRoundedCorners,
+                equalTo(RoundedCorners.fromRadii(new Pair<>(10, 20), 200, 400)));
+
+        assertThat(mRoundedCorners,
+                not(equalTo(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400))));
+    }
+
+    @Test
+    public void testSetRoundedCorner() {
+        RoundedCorner roundedCorner = new RoundedCorner(POSITION_BOTTOM_LEFT, 5, 6, 7);
+        mRoundedCorners.setRoundedCorner(POSITION_BOTTOM_LEFT, roundedCorner);
+
+        assertThat(mRoundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), equalTo(roundedCorner));
+    }
+
+    @Test
+    public void testSetRoundedCorner_null() {
+        mRoundedCorners.setRoundedCorner(POSITION_BOTTOM_LEFT, null);
+
+        assertThat(mRoundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT],
+                equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT)));
+        assertThat(mRoundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT), nullValue());
+    }
+
+    @Test
+    public void testInsetRoundedCorners_partialOverlap() {
+        RoundedCorners roundedCorners = mRoundedCorners.inset(1, 2, 3, 4);
+
+        assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_LEFT],
+                equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8)));
+        assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_RIGHT],
+                equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 189, 8)));
+        assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_RIGHT],
+                equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 179, 378)));
+        assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT],
+                equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 19, 378)));
+    }
+
+    @Test
+    public void testInsetRoundedCorners_noOverlap() {
+        RoundedCorners roundedCorners = mRoundedCorners.inset(20, 20, 20, 20);
+
+        assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_LEFT].isEmpty(), is(true));
+        assertThat(roundedCorners.mRoundedCorners[POSITION_TOP_RIGHT].isEmpty(), is(true));
+        assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_RIGHT].isEmpty(), is(true));
+        assertThat(roundedCorners.mRoundedCorners[POSITION_BOTTOM_LEFT].isEmpty(), is(true));
+    }
+
+    @Test
+    public void testRotateRoundedCorners_90() {
+        final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+        RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400)
+                .rotate(ROTATION_90, 200, 400);
+
+        assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT),
+                equalTo(new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT),
+                equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 20, 380, 20)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT),
+                equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 380, 180)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT),
+                equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 10, 10, 190)));
+    }
+
+    @Test
+    public void testRotateRoundedCorners_270() {
+        final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+        RoundedCorners roundedCorners = RoundedCorners.fromRadii(radius, 200, 400)
+                .rotate(ROTATION_270, 200, 400);
+
+        assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_LEFT),
+                equalTo(new RoundedCorner(POSITION_TOP_LEFT, 20, 20, 20)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_TOP_RIGHT),
+                equalTo(new RoundedCorner(POSITION_TOP_RIGHT, 10, 390, 10)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_RIGHT),
+                equalTo(new RoundedCorner(POSITION_BOTTOM_RIGHT, 10, 390, 190)));
+        assertThat(roundedCorners.getRoundedCorner(POSITION_BOTTOM_LEFT),
+                equalTo(new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 180)));
+    }
+
+    @Test
+    public void testFromRadius_cache() {
+        final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+        RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400);
+
+        assertThat(RoundedCorners.fromRadii(radius, 200, 400), sameInstance(cached));
+    }
+
+    @Test
+    public void testFromRadius_wontCacheIfRadiusChanged() {
+        final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+        RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400);
+
+        assertThat(RoundedCorners.fromRadii(new Pair<>(5, 10), 200, 400),
+                not(sameInstance(cached)));
+    }
+
+    @Test
+    public void testFromRadius_wontCacheIfDisplayWidthChanged() {
+        final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+        RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400);
+
+        assertThat(RoundedCorners.fromRadii(radius, 100, 400),
+                not(sameInstance(cached)));
+    }
+
+    @Test
+    public void testFromRadius_wontCacheIfDisplayHeightChanged() {
+        final Pair<Integer, Integer> radius = new Pair<>(10, 20);
+        RoundedCorners cached = RoundedCorners.fromRadii(radius, 200, 400);
+
+        assertThat(RoundedCorners.fromRadii(radius, 200, 300),
+                not(sameInstance(cached)));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 8e4e735..b80837f 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -61,7 +61,7 @@
         WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
         WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
         WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null,
-                systemBars(), true /* compatIgnoreVisibility */);
+                null, systemBars(), true /* compatIgnoreVisibility */);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
     }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index a4284a0..47ce2d8 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -278,29 +278,29 @@
 
     @Test
     @UiThreadTest
-    public void setSetImeTemporarilyConsumesInput_recoveryToVisible() {
+    public void setSetImeConsumesInput_recoveryToVisible() {
         mTextView = new TextView(mActivity);
         mTextView.setCursorVisible(true);
         assertTrue(mTextView.isCursorVisible());
 
-        mTextView.setImeTemporarilyConsumesInput(true);
+        mTextView.setImeConsumesInput(true);
         assertFalse(mTextView.isCursorVisible());
 
-        mTextView.setImeTemporarilyConsumesInput(false);
+        mTextView.setImeConsumesInput(false);
         assertTrue(mTextView.isCursorVisible());
     }
 
     @Test
     @UiThreadTest
-    public void setSetImeTemporarilyConsumesInput_recoveryToInvisible() {
+    public void setSetImeConsumesInput_recoveryToInvisible() {
         mTextView = new TextView(mActivity);
         mTextView.setCursorVisible(false);
         assertFalse(mTextView.isCursorVisible());
 
-        mTextView.setImeTemporarilyConsumesInput(true);
+        mTextView.setImeConsumesInput(true);
         assertFalse(mTextView.isCursorVisible());
 
-        mTextView.setImeTemporarilyConsumesInput(false);
+        mTextView.setImeConsumesInput(false);
         assertFalse(mTextView.isCursorVisible());
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java b/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java
deleted file mode 100644
index 127ecfb..0000000
--- a/core/tests/coretests/src/com/android/internal/listeners/ListenerTransportMultiplexerTest.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2020 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.internal.listeners;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import junit.framework.TestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicReference;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ListenerTransportMultiplexerTest extends TestCase {
-
-    TestMultiplexer mMultiplexer;
-
-    @Before
-    public void setUp() {
-        mMultiplexer = new TestMultiplexer();
-    }
-
-    @Test
-    public void testAdd() {
-        Runnable runnable = mock(Runnable.class);
-
-        mMultiplexer.addListener(0, runnable, Runnable::run);
-        assertThat(mMultiplexer.mRegistered).isTrue();
-        assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
-
-        mMultiplexer.notifyListeners();
-        verify(runnable, times(1)).run();
-    }
-
-    @Test
-    public void testAdd_Multiple() {
-        Runnable runnable1 = mock(Runnable.class);
-        Runnable runnable2 = mock(Runnable.class);
-
-        mMultiplexer.addListener(0, runnable1, Runnable::run);
-        mMultiplexer.addListener(0, runnable2, Runnable::run);
-
-        mMultiplexer.notifyListeners();
-        verify(runnable1).run();
-        verify(runnable2).run();
-    }
-
-    @Test
-    public void testRemove() {
-        Runnable runnable = mock(Runnable.class);
-
-        mMultiplexer.addListener(0, runnable, Runnable::run);
-        mMultiplexer.removeListener(runnable);
-        assertThat(mMultiplexer.mRegistered).isFalse();
-
-        mMultiplexer.notifyListeners();
-        verify(runnable, never()).run();
-    }
-
-    @Test
-    public void testRemove_Multiple() {
-        Runnable runnable1 = mock(Runnable.class);
-        Runnable runnable2 = mock(Runnable.class);
-
-        mMultiplexer.addListener(0, runnable1, Runnable::run);
-        mMultiplexer.addListener(1, runnable2, Runnable::run);
-        mMultiplexer.removeListener(runnable1);
-
-        mMultiplexer.notifyListeners();
-        verify(runnable1, never()).run();
-        verify(runnable2).run();
-    }
-
-    @Test
-    public void testMergeMultiple() {
-        Runnable runnable1 = mock(Runnable.class);
-        Runnable runnable2 = mock(Runnable.class);
-        Runnable runnable3 = mock(Runnable.class);
-
-        mMultiplexer.addListener(0, runnable1, Runnable::run);
-        mMultiplexer.addListener(1, runnable2, Runnable::run);
-        assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
-
-        mMultiplexer.notifyListeners();
-        verify(runnable1, times(1)).run();
-        verify(runnable2, times(1)).run();
-        verify(runnable3, times(0)).run();
-
-        mMultiplexer.addListener(0, runnable3, Runnable::run);
-        assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
-
-        mMultiplexer.notifyListeners();
-        verify(runnable1, times(2)).run();
-        verify(runnable2, times(2)).run();
-        verify(runnable3, times(1)).run();
-
-        mMultiplexer.removeListener(runnable2);
-        assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
-
-        mMultiplexer.notifyListeners();
-        verify(runnable1, times(3)).run();
-        verify(runnable2, times(2)).run();
-        verify(runnable3, times(2)).run();
-
-        mMultiplexer.removeListener(runnable1);
-        mMultiplexer.removeListener(runnable3);
-        mMultiplexer.notifyListeners();
-        verify(runnable1, times(3)).run();
-        verify(runnable2, times(2)).run();
-        verify(runnable3, times(2)).run();
-    }
-
-    @Test(timeout = 5000)
-    public void testReentrancy() {
-        AtomicReference<Runnable> runnable = new AtomicReference<>();
-        runnable.set(() -> mMultiplexer.removeListener(runnable.get()));
-
-        mMultiplexer.addListener(0, runnable.get(), command -> {
-            CountDownLatch latch = new CountDownLatch(1);
-            new Handler(Looper.getMainLooper()).post(() -> {
-                command.run();
-                latch.countDown();
-            });
-            try {
-                latch.await();
-            } catch (InterruptedException e) {
-                throw new AssertionError(e);
-            }
-        });
-
-        mMultiplexer.notifyListeners();
-        assertThat(mMultiplexer.mRegistered).isFalse();
-    }
-
-    private static class TestMultiplexer extends ListenerTransportMultiplexer<Integer, Runnable> {
-
-        boolean mRegistered;
-        int mMergedRequest;
-
-        TestMultiplexer() {
-        }
-
-        public void notifyListeners() {
-            deliverToListeners(Runnable::run);
-        }
-
-        @Override
-        protected void registerWithServer(Integer mergedRequest) {
-            mRegistered = true;
-            mMergedRequest = mergedRequest;
-        }
-
-        @Override
-        protected void unregisterWithServer() {
-            mRegistered = false;
-        }
-
-        @Override
-        protected Integer mergeRequests(Collection<Integer> requests) {
-            return requests.stream().max(Comparator.naturalOrder()).get();
-        }
-    }
-}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
index fbe16f2..d2107ea 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsHelperTest.java
@@ -21,12 +21,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -194,7 +192,6 @@
         sippers.add(mBluetoothBatterySipper);
         sippers.add(mIdleBatterySipper);
         doReturn(true).when(mBatteryStatsHelper).isTypeSystem(mSystemBatterySipper);
-        doNothing().when(mBatteryStatsHelper).smearScreenBatterySipper(any(), any());
 
         final double totalUsage = mBatteryStatsHelper.removeHiddenBatterySippers(sippers);
 
@@ -208,19 +205,20 @@
 
     @Test
     public void testSmearScreenBatterySipper() {
+        final ScreenPowerCalculator spc = spy(ScreenPowerCalculator.class);
         final BatterySipper sipperNull = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO,
-                BATTERY_APP_USAGE, 0 /* uid */, true /* isUidNull */);
+                BATTERY_APP_USAGE, 0 /* uid */, true /* isUidNull */, spc);
         final BatterySipper sipperBg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY_ZERO,
-                BATTERY_APP_USAGE, 1 /* uid */, false /* isUidNull */);
+                BATTERY_APP_USAGE, 1 /* uid */, false /* isUidNull */, spc);
         final BatterySipper sipperFg = createTestSmearBatterySipper(TIME_FOREGROUND_ACTIVITY,
-                BATTERY_APP_USAGE, 2 /* uid */, false /* isUidNull */);
+                BATTERY_APP_USAGE, 2 /* uid */, false /* isUidNull */, spc);
 
         final List<BatterySipper> sippers = new ArrayList<>();
         sippers.add(sipperNull);
         sippers.add(sipperBg);
         sippers.add(sipperFg);
 
-        mBatteryStatsHelper.smearScreenBatterySipper(sippers, mScreenBatterySipper);
+        spc.smearScreenBatterySipper(sippers, mScreenBatterySipper);
 
         assertThat(sipperNull.screenPowerMah).isWithin(PRECISION).of(0);
         assertThat(sipperBg.screenPowerMah).isWithin(PRECISION).of(0);
@@ -249,13 +247,13 @@
 
     @Test
     public void testGetProcessForegroundTimeMs_largerActivityTime_returnMinTime() {
-        doReturn(TIME_STATE_FOREGROUND_US + 500).when(mBatteryStatsHelper)
+        final ScreenPowerCalculator spc = spy(ScreenPowerCalculator.class);
+        doReturn(TIME_STATE_FOREGROUND_US + 500).when(spc)
                 .getForegroundActivityTotalTimeUs(eq(mUid), anyLong());
         doReturn(TIME_STATE_FOREGROUND_US).when(mUid).getProcessStateTime(eq(PROCESS_STATE_TOP),
                 anyLong(), anyInt());
 
-        final long time = mBatteryStatsHelper.getProcessForegroundTimeMs(mUid,
-                BatteryStats.STATS_SINCE_CHARGED);
+        final long time = spc.getProcessForegroundTimeMs(mUid);
 
         assertThat(time).isEqualTo(TIME_STATE_FOREGROUND_MS);
     }
@@ -291,15 +289,14 @@
     }
 
     private BatterySipper createTestSmearBatterySipper(long activityTime, double totalPowerMah,
-            int uidCode, boolean isUidNull) {
+            int uidCode, boolean isUidNull, ScreenPowerCalculator spc) {
         final BatterySipper sipper = mock(BatterySipper.class);
         sipper.drainType = BatterySipper.DrainType.APP;
         sipper.totalPowerMah = totalPowerMah;
         doReturn(uidCode).when(sipper).getUid();
         if (!isUidNull) {
             final BatteryStats.Uid uid = mock(BatteryStats.Uid.class, RETURNS_DEEP_STUBS);
-            doReturn(activityTime).when(mBatteryStatsHelper).getProcessForegroundTimeMs(eq(uid),
-                    anyInt());
+            doReturn(activityTime).when(spc).getProcessForegroundTimeMs(eq(uid));
             doReturn(uidCode).when(uid).getUid();
             sipper.uidObj = uid;
         }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 59534e4..2c71287 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -114,7 +114,7 @@
     }
 
     void apply(PowerCalculator calculator) {
-        BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(0, 0, false);
+        BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(0, 0);
         SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
         for (int i = 0; i < uidStats.size(); i++) {
             builder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i));
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 96e9c4a..018a810 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -66,21 +66,17 @@
         final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
         final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(2000);
 
-        final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1, true);
+        final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1);
         builder.setConsumedPower(100);
         builder.setDischargePercentage(20);
 
         final UidBatteryConsumer.Builder uidBatteryConsumerBuilder =
                 builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid);
         uidBatteryConsumerBuilder.setPackageWithHighestDrain("foo");
-        uidBatteryConsumerBuilder.setConsumedPower(200);
         uidBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, 300);
         uidBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
         uidBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 500);
-        uidBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
-                BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                        + BatteryConsumer.POWER_COMPONENT_CPU, 510);
         uidBatteryConsumerBuilder.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, 600);
         uidBatteryConsumerBuilder.setUsageDurationMillis(
                 BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, 700);
@@ -90,13 +86,9 @@
         final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
                 builder.getOrCreateSystemBatteryConsumerBuilder(
                         SystemBatteryConsumer.DRAIN_TYPE_CAMERA);
-        systemBatteryConsumerBuilder.setConsumedPower(10000);
         systemBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 10100);
         systemBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
-        systemBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
-                BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                        + BatteryConsumer.POWER_COMPONENT_CPU, 10210);
         systemBatteryConsumerBuilder.setUsageDurationMillis(
                 BatteryConsumer.TIME_COMPONENT_CPU, 10300);
         systemBatteryConsumerBuilder.setUsageDurationForCustomComponentMillis(
@@ -114,22 +106,19 @@
         for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
             if (uidBatteryConsumer.getUid() == 2000) {
                 assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo("foo");
-                assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(200);
                 assertThat(uidBatteryConsumer.getConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_USAGE)).isEqualTo(300);
                 assertThat(uidBatteryConsumer.getConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(400);
                 assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(500);
-                assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent(
-                        BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                                + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(510);
                 assertThat(uidBatteryConsumer.getUsageDurationMillis(
                         BatteryConsumer.TIME_COMPONENT_CPU)).isEqualTo(600);
                 assertThat(uidBatteryConsumer.getUsageDurationMillis(
                         BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND)).isEqualTo(700);
                 assertThat(uidBatteryConsumer.getUsageDurationForCustomComponentMillis(
                         BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(800);
+                assertThat(uidBatteryConsumer.getConsumedPower()).isEqualTo(1710);
             } else {
                 fail("Unexpected UID " + uidBatteryConsumer.getUid());
             }
@@ -139,18 +128,15 @@
                 batteryUsageStats.getSystemBatteryConsumers();
         for (SystemBatteryConsumer systemBatteryConsumer : systemBatteryConsumers) {
             if (systemBatteryConsumer.getDrainType() == SystemBatteryConsumer.DRAIN_TYPE_CAMERA) {
-                assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(10000);
                 assertThat(systemBatteryConsumer.getConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10100);
                 assertThat(systemBatteryConsumer.getConsumedPowerForCustomComponent(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10200);
-                assertThat(systemBatteryConsumer.getConsumedPowerForCustomComponent(
-                        BatteryConsumer.FIRST_MODELED_POWER_COMPONENT_ID
-                                + BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10210);
                 assertThat(systemBatteryConsumer.getUsageDurationMillis(
                         BatteryConsumer.TIME_COMPONENT_CPU)).isEqualTo(10300);
                 assertThat(systemBatteryConsumer.getUsageDurationForCustomComponentMillis(
                         BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(10400);
+                assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(30510);
             } else {
                 fail("Unexpected drain type " + systemBatteryConsumer.getDrainType());
             }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
index b5720a2..2de800b 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
@@ -19,122 +19,87 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.os.FileUtils;
-
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.File;
 import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class KernelSingleProcessCpuThreadReaderTest {
 
-    private File mProcDirectory;
-
-    @Before
-    public void setUp() {
-        Context context = InstrumentationRegistry.getContext();
-        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        FileUtils.deleteContents(mProcDirectory);
-    }
-
     @Test
     public void getProcessCpuUsage() throws IOException {
-        setupDirectory(42,
-                new int[] {42, 1, 2, 3},
-                new int[] {1000, 2000},
-                // Units are 10ms aka 10000Us
-                new int[][] {{100, 200}, {0, 200}, {100, 300}, {0, 600}},
-                new int[] {4500, 500});
+        // Units are nanoseconds
+        MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4, new String[] {
+                "0:1000000000 2000000000 3000000000:4000000000",
+                "1:100000000 200000000 300000000:400000000",
+        });
 
         KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(42,
-                mProcDirectory.toPath());
+                mockReader);
+        reader.setSelectedThreadIds(new int[] {2, 3});
+        reader.startTrackingThreadCpuTimes();
         KernelSingleProcessCpuThreadReader.ProcessCpuUsage processCpuUsage =
-                reader.getProcessCpuUsage(new int[] {2, 3});
-        assertThat(processCpuUsage.threadCpuTimesMillis).isEqualTo(new long[] {2000, 13000});
-        assertThat(processCpuUsage.selectedThreadCpuTimesMillis).isEqualTo(new long[] {1000, 9000});
-        assertThat(processCpuUsage.processCpuTimesMillis).isEqualTo(new long[] {6666, 43333});
+                reader.getProcessCpuUsage();
+        assertThat(mockReader.mTrackedTgid).isEqualTo(42);
+        // The strings are formatted as <TID TGID AGG_KEY>, where AGG_KEY is 1 for binder
+        // threads and 0 for all other threads.
+        assertThat(mockReader.mTrackedTasks).containsExactly(
+                "2 1",
+                "3 1");
+        assertThat(processCpuUsage.threadCpuTimesMillis).isEqualTo(
+                new long[] {1100, 2200, 3300, 4400});
+        assertThat(processCpuUsage.selectedThreadCpuTimesMillis).isEqualTo(
+                new long[] {100, 200, 300, 400});
     }
 
     @Test
     public void getCpuFrequencyCount() throws IOException {
-        setupDirectory(13,
-                new int[] {13},
-                new int[] {1000, 2000, 3000},
-                new int[][] {{100, 200, 300}},
-                new int[] {14, 15});
+        MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(3, new String[0]);
 
         KernelSingleProcessCpuThreadReader reader = new KernelSingleProcessCpuThreadReader(13,
-                mProcDirectory.toPath());
+                mockReader);
         int cpuFrequencyCount = reader.getCpuFrequencyCount();
         assertThat(cpuFrequencyCount).isEqualTo(3);
     }
 
-    private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies,
-            int[][] threadCpuTimes, int[] processCpuTimes)
-            throws IOException {
+    public static class MockCpuTimeInStateReader implements
+            KernelSingleProcessCpuThreadReader.CpuTimeInStateReader {
+        private final int mCpuFrequencyCount;
+        private final String[] mAggregatedTaskCpuFreqTimes;
+        public int mTrackedTgid;
+        public List<String> mTrackedTasks = new ArrayList<>();
 
-        assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs());
-
-        try (OutputStream timeInStateStream =
-                     Files.newOutputStream(
-                             mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) {
-            for (int i = 0; i < cpuFrequencies.length; i++) {
-                final String line = cpuFrequencies[i] + " 0\n";
-                timeInStateStream.write(line.getBytes());
-            }
+        public MockCpuTimeInStateReader(int cpuFrequencyCount,
+                String[] aggregatedTaskCpuFreqTimes) {
+            mCpuFrequencyCount = cpuFrequencyCount;
+            mAggregatedTaskCpuFreqTimes = aggregatedTaskCpuFreqTimes;
         }
 
-        Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid));
-
-        // Make /proc/$PID
-        assertTrue(processPath.toFile().mkdirs());
-
-        // Write /proc/$PID/stat. Only the fields 14-17 matter.
-        try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) {
-            timeInStateStream.write(
-                    (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 "
-                            + processCpuTimes[0] + " "
-                            + processCpuTimes[1] + " "
-                            + "16 17 18 19 20 ...").getBytes());
+        @Override
+        public int getCpuFrequencyCount() {
+            return mCpuFrequencyCount;
         }
 
-        // Make /proc/$PID/task
-        final Path selfThreadsPath = processPath.resolve("task");
-        assertTrue(selfThreadsPath.toFile().mkdirs());
+        @Override
+        public boolean startTrackingProcessCpuTimes(int tgid) {
+            mTrackedTgid = tgid;
+            return true;
+        }
 
-        // Make thread directories
-        for (int i = 0; i < threadIds.length; i++) {
-            // Make /proc/$PID/task/$TID
-            final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
-            assertTrue(threadPath.toFile().mkdirs());
+        public boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) {
+            mTrackedTasks.add(pid + " " + aggregationKey);
+            return true;
+        }
 
-            // Make /proc/$PID/task/$TID/time_in_state
-            try (OutputStream timeInStateStream =
-                         Files.newOutputStream(threadPath.resolve("time_in_state"))) {
-                for (int j = 0; j < cpuFrequencies.length; j++) {
-                    final String line = cpuFrequencies[j] + " " + threadCpuTimes[i][j] + "\n";
-                    timeInStateStream.write(line.getBytes());
-                }
-            }
+        public String[] getAggregatedTaskCpuFreqTimes(int pid) {
+            return mAggregatedTaskCpuFreqTimes;
         }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 4230066..66a8379 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -21,7 +21,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
 import android.os.Process;
@@ -78,7 +78,8 @@
                 8_000_000_000L, APP_UID, 8000, 8000);
 
         // Note established network
-        stats.noteNetworkInterfaceType("cellular", ConnectivityManager.TYPE_MOBILE);
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR });
 
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index bf74c8d..1687a78 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -47,9 +47,10 @@
 
         setExternalStatsSyncLocked(new DummyExternalStatsSync());
 
-        final boolean[] supportedBuckets = new boolean[MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS];
-        Arrays.fill(supportedBuckets, true);
-        mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedBuckets);
+        final boolean[] supportedStandardBuckets
+                = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
+        Arrays.fill(supportedStandardBuckets, true);
+        mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(supportedStandardBuckets, 2);
 
         // A no-op handler.
         mHandler = new Handler(Looper.getMainLooper()) {
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
index 121c637..d116d4d 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServerCpuThreadReaderTest.java
@@ -16,146 +16,86 @@
 
 package com.android.internal.os;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
-import android.content.Context;
-import android.os.FileUtils;
-
-import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.File;
 import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SystemServerCpuThreadReaderTest {
-    private File mProcDirectory;
-
-    @Before
-    public void setUp() {
-        Context context = InstrumentationRegistry.getContext();
-        mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        FileUtils.deleteContents(mProcDirectory);
-    }
 
     @Test
-    public void testReaderDelta_firstTime() throws IOException {
+    public void testReadDelta() throws IOException {
         int pid = 42;
-        setupDirectory(
-                pid,
-                new int[] {42, 1, 2, 3},
-                new int[] {1000, 2000},
-                // Units are 10ms aka 10000Us
-                new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}},
-                new int[] {1400, 1500});
 
-        SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
-                mProcDirectory.toPath(), pid);
-        reader.setBinderThreadNativeTids(new int[] {1, 3});
-        SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
-                reader.readDelta();
-        assertArrayEquals(new long[] {100 * 10000, 1100 * 10000},
-                systemServiceCpuThreadTimes.threadCpuTimesUs);
-        assertArrayEquals(new long[] {0, 600 * 10000},
-                systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
-    }
+        MockCpuTimeInStateReader mockReader = new MockCpuTimeInStateReader(4);
+        // Units are nanoseconds
+        mockReader.setAggregatedTaskCpuFreqTimes(new String[] {
+                "0:1000000000 2000000000 3000000000:4000000000",
+                "1:100000000 200000000 300000000:400000000",
+        });
 
-    @Test
-    public void testReaderDelta_nextTime() throws IOException {
-        int pid = 42;
-        setupDirectory(
-                pid,
-                new int[] {42, 1, 2, 3},
-                new int[] {1000, 2000},
-                new int[][] {{100, 200}, {0, 200}, {0, 300}, {0, 400}},
-                new int[] {1400, 1500});
-
-        SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(
-                mProcDirectory.toPath(), pid);
+        SystemServerCpuThreadReader reader = new SystemServerCpuThreadReader(pid, mockReader);
         reader.setBinderThreadNativeTids(new int[] {1, 3});
 
-        // First time, populate "last" snapshot
-        reader.readDelta();
-
-        FileUtils.deleteContents(mProcDirectory);
-        setupDirectory(
-                pid,
-                new int[] {42, 1, 2, 3},
-                new int[] {1000, 2000},
-                new int[][] {{500, 600}, {700, 800}, {900, 1000}, {1100, 1200}},
-                new int[] {2400, 2500});
-
-        // Second time, get the actual delta
+        // The first invocation of readDelta populates the "last" snapshot
         SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
                 reader.readDelta();
 
-        assertArrayEquals(new long[] {3100 * 10000, 2500 * 10000},
-                systemServiceCpuThreadTimes.threadCpuTimesUs);
-        assertArrayEquals(new long[] {1800 * 10000, 1400 * 10000},
-                systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
+        assertThat(systemServiceCpuThreadTimes.threadCpuTimesUs)
+                .isEqualTo(new long[] {1100000, 2200000, 3300000, 4400000});
+        assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs)
+                .isEqualTo(new long[] {100000, 200000, 300000, 400000});
+
+        mockReader.setAggregatedTaskCpuFreqTimes(new String[] {
+                "0:1010000000 2020000000 3030000000:4040000000",
+                "1:101000000 202000000 303000000:404000000",
+        });
+
+        // The second invocation gets the actual delta
+        systemServiceCpuThreadTimes = reader.readDelta();
+
+        assertThat(systemServiceCpuThreadTimes.threadCpuTimesUs)
+                .isEqualTo(new long[] {11000, 22000, 33000, 44000});
+        assertThat(systemServiceCpuThreadTimes.binderThreadCpuTimesUs)
+                .isEqualTo(new long[] {1000, 2000, 3000, 4000});
     }
 
-    private void setupDirectory(int pid, int[] threadIds, int[] cpuFrequencies, int[][] cpuTimes,
-            int[] processCpuTimes)
-            throws IOException {
+    public static class MockCpuTimeInStateReader implements
+            KernelSingleProcessCpuThreadReader.CpuTimeInStateReader {
+        private final int mCpuFrequencyCount;
+        private String[] mAggregatedTaskCpuFreqTimes;
 
-        assertTrue(mProcDirectory.toPath().resolve("self").toFile().mkdirs());
-
-        try (OutputStream timeInStateStream =
-                     Files.newOutputStream(
-                             mProcDirectory.toPath().resolve("self").resolve("time_in_state"))) {
-            for (int i = 0; i < cpuFrequencies.length; i++) {
-                final String line = cpuFrequencies[i] + " 0\n";
-                timeInStateStream.write(line.getBytes());
-            }
+        MockCpuTimeInStateReader(int frequencyCount) {
+            mCpuFrequencyCount = frequencyCount;
         }
 
-        Path processPath = mProcDirectory.toPath().resolve(String.valueOf(pid));
-        // Make /proc/$PID
-        assertTrue(processPath.toFile().mkdirs());
-
-        // Write /proc/$PID/stat. Only the fields 14-17 matter.
-        try (OutputStream timeInStateStream = Files.newOutputStream(processPath.resolve("stat"))) {
-            timeInStateStream.write(
-                    (pid + " (test) S 4 5 6 7 8 9 10 11 12 13 "
-                            + processCpuTimes[0] + " "
-                            + processCpuTimes[1] + " "
-                            + "16 17 18 19 20 ...").getBytes());
+        @Override
+        public int getCpuFrequencyCount() {
+            return mCpuFrequencyCount;
         }
 
-        // Make /proc/$PID/task
-        final Path selfThreadsPath = processPath.resolve("task");
-        assertTrue(selfThreadsPath.toFile().mkdirs());
+        @Override
+        public boolean startTrackingProcessCpuTimes(int tgid) {
+            return true;
+        }
 
-        // Make thread directories
-        for (int i = 0; i < threadIds.length; i++) {
-            // Make /proc/$PID/task/$TID
-            final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i]));
-            assertTrue(threadPath.toFile().mkdirs());
+        public boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey) {
+            return true;
+        }
 
-            // Make /proc/$PID/task/$TID/time_in_state
-            try (OutputStream timeInStateStream =
-                         Files.newOutputStream(threadPath.resolve("time_in_state"))) {
-                for (int j = 0; j < cpuFrequencies.length; j++) {
-                    final String line = cpuFrequencies[j] + " " + cpuTimes[i][j] + "\n";
-                    timeInStateStream.write(line.getBytes());
-                }
-            }
+        public void setAggregatedTaskCpuFreqTimes(String[] mAggregatedTaskCpuFreqTimes) {
+            this.mAggregatedTaskCpuFreqTimes = mAggregatedTaskCpuFreqTimes;
+        }
+
+        public String[] getAggregatedTaskCpuFreqTimes(int pid) {
+            return mAggregatedTaskCpuFreqTimes;
         }
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index a5cafb9..24741fe 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -62,7 +62,6 @@
     public void testCalculateApp() {
         // Test Power Profile has two CPU clusters with 3 and 4 speeds, thus 7 freq times total
         mMockSystemServerCpuThreadReader.setCpuTimes(
-                new long[] {10000, 15000, 20000, 25000, 30000, 35000, 40000},
                 new long[] {30000, 40000, 50000, 60000, 70000, 80000, 90000},
                 new long[] {20000, 30000, 40000, 50000, 60000, 70000, 80000});
 
@@ -144,9 +143,7 @@
             super(null);
         }
 
-        public void setCpuTimes(long[] processCpuTimesUs, long[] threadCpuTimesUs,
-                long[] binderThreadCpuTimesUs) {
-            mThreadTimes.processCpuTimesUs = processCpuTimesUs;
+        public void setCpuTimes(long[] threadCpuTimesUs, long[] binderThreadCpuTimesUs) {
             mThreadTimes.threadCpuTimesUs = threadCpuTimesUs;
             mThreadTimes.binderThreadCpuTimesUs = binderThreadCpuTimesUs;
         }
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index c9c81ac..b9908f4 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -21,10 +21,11 @@
 import static com.android.internal.power.MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_DOZE;
 import static com.android.internal.power.MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON;
 import static com.android.internal.power.MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_OTHER;
-import static com.android.internal.power.MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS;
+import static com.android.internal.power.MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
@@ -47,60 +48,77 @@
 
     @Test
     public void testConstruction() {
-        final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
 
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
-            if (supportedEnergyBuckets[i]) {
-                assertTrue(stats.isEnergyBucketSupported(i));
-                assertEquals(0L, stats.getAccumulatedBucketEnergy(i));
+        for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+            if (supportedStandardBuckets[i]) {
+                assertTrue(stats.isStandardBucketSupported(i));
+                assertEquals(0L, stats.getAccumulatedStandardBucketEnergy(i));
             } else {
-                assertFalse(stats.isEnergyBucketSupported(i));
-                assertEquals(ENERGY_DATA_UNAVAILABLE, stats.getAccumulatedBucketEnergy(i));
+                assertFalse(stats.isStandardBucketSupported(i));
+                assertEquals(ENERGY_DATA_UNAVAILABLE, stats.getAccumulatedStandardBucketEnergy(i));
             }
         }
+        for (int i = 0; i < numCustomBuckets; i++) {
+            assertEquals(0L, stats.getAccumulatedCustomBucketEnergy(i));
+        }
     }
 
     @Test
     public void testCreateFromTemplate() {
-        final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
 
         final MeasuredEnergyStats newStats = MeasuredEnergyStats.createFromTemplate(stats);
 
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
-            if (supportedEnergyBuckets[i]) {
-                assertTrue(newStats.isEnergyBucketSupported(i));
-                assertEquals(0, newStats.getAccumulatedBucketEnergy(i));
+        for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+            if (supportedStandardBuckets[i]) {
+                assertTrue(newStats.isStandardBucketSupported(i));
+                assertEquals(0L, newStats.getAccumulatedStandardBucketEnergy(i));
             } else {
-                assertFalse(newStats.isEnergyBucketSupported(i));
-                assertEquals(ENERGY_DATA_UNAVAILABLE, newStats.getAccumulatedBucketEnergy(i));
+                assertFalse(newStats.isStandardBucketSupported(i));
+                assertEquals(ENERGY_DATA_UNAVAILABLE,
+                        newStats.getAccumulatedStandardBucketEnergy(i));
             }
         }
+        for (int i = 0; i < numCustomBuckets; i++) {
+            assertEquals(0L, newStats.getAccumulatedCustomBucketEnergy(i));
+        }
     }
 
     @Test
     public void testReadWriteParcel() {
-        final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
 
         final Parcel parcel = Parcel.obtain();
         stats.writeToParcel(parcel);
@@ -108,94 +126,127 @@
         parcel.setDataPosition(0);
         MeasuredEnergyStats newStats = new MeasuredEnergyStats(parcel);
 
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
-            assertEquals(stats.getAccumulatedBucketEnergy(i),
-                    newStats.getAccumulatedBucketEnergy(i));
+        for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+            assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+                    newStats.getAccumulatedStandardBucketEnergy(i));
         }
+        for (int i = 0; i < numCustomBuckets; i++) {
+            assertEquals(stats.getAccumulatedCustomBucketEnergy(i),
+                    newStats.getAccumulatedCustomBucketEnergy(i));
+        }
+        assertEquals(ENERGY_DATA_UNAVAILABLE,
+                newStats.getAccumulatedCustomBucketEnergy(numCustomBuckets + 1));
         parcel.recycle();
     }
 
     @Test
     public void testCreateAndReadSummaryFromParcel() {
-        final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
 
         final Parcel parcel = Parcel.obtain();
         MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false);
         parcel.setDataPosition(0);
         MeasuredEnergyStats newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel);
 
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
-            assertEquals(stats.isEnergyBucketSupported(i),
-                    newStats.isEnergyBucketSupported(i));
-            assertEquals(stats.getAccumulatedBucketEnergy(i),
-                    newStats.getAccumulatedBucketEnergy(i));
+        for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+            assertEquals(stats.isStandardBucketSupported(i),
+                    newStats.isStandardBucketSupported(i));
+            assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+                    newStats.getAccumulatedStandardBucketEnergy(i));
         }
+        for (int i = 0; i < numCustomBuckets; i++) {
+            assertEquals(stats.getAccumulatedCustomBucketEnergy(i),
+                    newStats.getAccumulatedCustomBucketEnergy(i));
+        }
+        assertEquals(ENERGY_DATA_UNAVAILABLE,
+                newStats.getAccumulatedCustomBucketEnergy(numCustomBuckets + 1));
         parcel.recycle();
     }
 
     @Test
     public void testCreateAndReadSummaryFromParcel_existingTemplate() {
-        final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats template = new MeasuredEnergyStats(supportedEnergyBuckets);
-        template.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
-        template.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
-        template.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        final MeasuredEnergyStats template
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+        template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+        template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        template.updateCustomBucket(0, 50, true);
 
         final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 200, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 7, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 63, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 7, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 63, true);
+        stats.updateCustomBucket(0, 315, true);
+        stats.updateCustomBucket(1, 316, true);
 
         final Parcel parcel = Parcel.obtain();
         MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false);
 
-        final boolean[] newSupportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        newSupportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
-        newSupportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = true; // switched from false to true
-        newSupportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = false; // switched true to false
-        final MeasuredEnergyStats newTemplate = new MeasuredEnergyStats(newSupportedEnergyBuckets);
+        final boolean[] newsupportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        newsupportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        newsupportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = true; // switched false > true
+        newsupportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = false; // switched true > false
+        final MeasuredEnergyStats newTemplate
+                = new MeasuredEnergyStats(newsupportedStandardBuckets, numCustomBuckets);
         parcel.setDataPosition(0);
 
         final MeasuredEnergyStats newStats =
                 MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, newTemplate);
 
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
-            if (!newSupportedEnergyBuckets[i]) {
-                assertFalse(newStats.isEnergyBucketSupported(i));
-                assertEquals(ENERGY_DATA_UNAVAILABLE, newStats.getAccumulatedBucketEnergy(i));
-            } else if (!supportedEnergyBuckets[i]) {
-                assertTrue(newStats.isEnergyBucketSupported(i));
-                assertEquals(0L, newStats.getAccumulatedBucketEnergy(i));
+        for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+            if (!newsupportedStandardBuckets[i]) {
+                assertFalse(newStats.isStandardBucketSupported(i));
+                assertEquals(ENERGY_DATA_UNAVAILABLE,
+                        newStats.getAccumulatedStandardBucketEnergy(i));
+            } else if (!supportedStandardBuckets[i]) {
+                assertTrue(newStats.isStandardBucketSupported(i));
+                assertEquals(0L, newStats.getAccumulatedStandardBucketEnergy(i));
             } else {
-                assertTrue(newStats.isEnergyBucketSupported(i));
-                assertEquals(stats.getAccumulatedBucketEnergy(i),
-                        newStats.getAccumulatedBucketEnergy(i));
+                assertTrue(newStats.isStandardBucketSupported(i));
+                assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+                        newStats.getAccumulatedStandardBucketEnergy(i));
             }
         }
+        for (int i = 0; i < numCustomBuckets; i++) {
+            assertEquals(stats.getAccumulatedCustomBucketEnergy(i),
+                    newStats.getAccumulatedCustomBucketEnergy(i));
+        }
+        assertEquals(ENERGY_DATA_UNAVAILABLE,
+                newStats.getAccumulatedCustomBucketEnergy(numCustomBuckets + 1));
         parcel.recycle();
     }
 
     @Test
     public void testCreateAndReadSummaryFromParcel_skipZero() {
-        final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        Arrays.fill(supportedEnergyBuckets, true);
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        Arrays.fill(supportedStandardBuckets, true);
 
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
-        // Accumulate energy in one bucket, the rest should be zero
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 200, true);
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        // Accumulate energy in one bucket and one custom bucket, the rest should be zero
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true);
+        stats.updateCustomBucket(1, 60, true);
 
+        // Let's try parcelling with including zeros
         final Parcel includeZerosParcel = Parcel.obtain();
         MeasuredEnergyStats.writeSummaryToParcel(stats, includeZerosParcel, false);
         includeZerosParcel.setDataPosition(0);
@@ -203,90 +254,180 @@
         MeasuredEnergyStats newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(
                 includeZerosParcel);
 
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
+        for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
             if (i == ENERGY_BUCKET_SCREEN_ON) {
-                assertEquals(stats.isEnergyBucketSupported(i),
-                        newStats.isEnergyBucketSupported(i));
-                assertEquals(stats.getAccumulatedBucketEnergy(i),
-                        newStats.getAccumulatedBucketEnergy(i));
+                assertEquals(stats.isStandardBucketSupported(i),
+                        newStats.isStandardBucketSupported(i));
+                assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+                        newStats.getAccumulatedStandardBucketEnergy(i));
             } else {
-                assertTrue(newStats.isEnergyBucketSupported(i));
-                assertEquals(0L, newStats.getAccumulatedBucketEnergy(i));
+                assertTrue(newStats.isStandardBucketSupported(i));
+                assertEquals(0L, newStats.getAccumulatedStandardBucketEnergy(i));
             }
         }
+        assertEquals(0L, newStats.getAccumulatedCustomBucketEnergy(0));
+        assertEquals(stats.getAccumulatedCustomBucketEnergy(1),
+                newStats.getAccumulatedCustomBucketEnergy(1));
         includeZerosParcel.recycle();
 
+        // Now let's try parcelling with skipping zeros
         final Parcel skipZerosParcel = Parcel.obtain();
         MeasuredEnergyStats.writeSummaryToParcel(stats, skipZerosParcel, true);
         skipZerosParcel.setDataPosition(0);
 
         newStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(skipZerosParcel);
 
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
+        for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
             if (i == ENERGY_BUCKET_SCREEN_ON) {
-                assertEquals(stats.isEnergyBucketSupported(i),
-                        newStats.isEnergyBucketSupported(i));
-                assertEquals(stats.getAccumulatedBucketEnergy(i),
-                        newStats.getAccumulatedBucketEnergy(i));
+                assertEquals(stats.isStandardBucketSupported(i),
+                        newStats.isStandardBucketSupported(i));
+                assertEquals(stats.getAccumulatedStandardBucketEnergy(i),
+                        newStats.getAccumulatedStandardBucketEnergy(i));
             } else {
-                assertFalse(newStats.isEnergyBucketSupported(i));
-                assertEquals(ENERGY_DATA_UNAVAILABLE, newStats.getAccumulatedBucketEnergy(i));
+                assertFalse(newStats.isStandardBucketSupported(i));
+                assertEquals(ENERGY_DATA_UNAVAILABLE,
+                        newStats.getAccumulatedStandardBucketEnergy(i));
             }
         }
+        assertEquals(0L, newStats.getAccumulatedCustomBucketEnergy(0));
+        assertEquals(stats.getAccumulatedCustomBucketEnergy(1),
+                newStats.getAccumulatedCustomBucketEnergy(1));
         skipZerosParcel.recycle();
     }
 
     @Test
+    public void testCreateAndReadSummaryFromParcel_nullTemplate() {
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
+
+        final Parcel parcel = Parcel.obtain();
+        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false);
+        parcel.setDataPosition(0);
+
+        MeasuredEnergyStats newStats
+                = MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, null);
+        assertNull(newStats);
+        parcel.recycle();
+    }
+
+    @Test
+    public void testCreateAndReadSummaryFromParcel_boring() {
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+
+        final MeasuredEnergyStats template
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+        template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+        template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        template.updateCustomBucket(0, 50, true);
+
+        final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 0L, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 7L, true);
+
+        final Parcel parcel = Parcel.obtain();
+        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false);
+
+        final boolean[] newSupportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        newSupportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        newSupportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = true; // switched false > true
+        newSupportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = false; // switched true > false
+        final MeasuredEnergyStats newTemplate
+                = new MeasuredEnergyStats(newSupportedStandardBuckets, numCustomBuckets);
+        parcel.setDataPosition(0);
+
+        final MeasuredEnergyStats newStats =
+                MeasuredEnergyStats.createAndReadSummaryFromParcel(parcel, newTemplate);
+        // The only non-0 entry in stats is no longer supported, so now there's no interesting data.
+        assertNull(newStats);
+        assertEquals("Parcel was not properly consumed", 0, parcel.dataAvail());
+        parcel.recycle();
+    }
+
+    @Test
     public void testUpdateBucket() {
-        final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_DOZE, 30, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_DOZE, 30, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
 
-        assertEquals(15, stats.getAccumulatedBucketEnergy(ENERGY_BUCKET_SCREEN_ON));
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
+        stats.updateCustomBucket(0, 3, true);
+
+        assertEquals(15, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON));
         assertEquals(ENERGY_DATA_UNAVAILABLE,
-                stats.getAccumulatedBucketEnergy(ENERGY_BUCKET_SCREEN_DOZE));
-        assertEquals(40, stats.getAccumulatedBucketEnergy(ENERGY_BUCKET_SCREEN_OTHER));
+                stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_DOZE));
+        assertEquals(40, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_OTHER));
+        assertEquals(50 + 3, stats.getAccumulatedCustomBucketEnergy(0));
+        assertEquals(60, stats.getAccumulatedCustomBucketEnergy(1));
     }
 
     @Test
     public void testReset() {
-        final boolean[] supportedEnergyBuckets = new boolean[NUMBER_ENERGY_BUCKETS];
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
-        supportedEnergyBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
+        final boolean[] supportedStandardBuckets = new boolean[NUMBER_STANDARD_ENERGY_BUCKETS];
+        final int numCustomBuckets = 2;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_ON] = true;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_DOZE] = false;
+        supportedStandardBuckets[ENERGY_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedEnergyBuckets);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        final MeasuredEnergyStats stats
+                = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true);
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true);
+        stats.updateCustomBucket(0, 50, true);
+        stats.updateCustomBucket(1, 60, true);
 
         MeasuredEnergyStats.resetIfNotNull(stats);
         // All energy should be reset to 0
-        for (int i = 0; i < NUMBER_ENERGY_BUCKETS; i++) {
-            if (supportedEnergyBuckets[i]) {
-                assertTrue(stats.isEnergyBucketSupported(i));
-                assertEquals(0, stats.getAccumulatedBucketEnergy(i));
+        for (int i = 0; i < NUMBER_STANDARD_ENERGY_BUCKETS; i++) {
+            if (supportedStandardBuckets[i]) {
+                assertTrue(stats.isStandardBucketSupported(i));
+                assertEquals(0, stats.getAccumulatedStandardBucketEnergy(i));
             } else {
-                assertFalse(stats.isEnergyBucketSupported(i));
-                assertEquals(ENERGY_DATA_UNAVAILABLE, stats.getAccumulatedBucketEnergy(i));
+                assertFalse(stats.isStandardBucketSupported(i));
+                assertEquals(ENERGY_DATA_UNAVAILABLE, stats.getAccumulatedStandardBucketEnergy(i));
             }
         }
+        for (int i = 0; i < numCustomBuckets; i++) {
+            assertEquals(0, stats.getAccumulatedCustomBucketEnergy(i));
+        }
 
         // Values should increase as usual.
-        stats.updateBucket(ENERGY_BUCKET_SCREEN_ON, 70, true);
-        assertEquals(70L, stats.getAccumulatedBucketEnergy(ENERGY_BUCKET_SCREEN_ON));
+        stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 70, true);
+        assertEquals(70L, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON));
+
+        stats.updateCustomBucket(1, 12, true);
+        assertEquals(12L, stats.getAccumulatedCustomBucketEnergy(1));
     }
 
     /** Test that states are mapped to the expected energy buckets. Beware of mapping changes. */
     @Test
-    public void testEnergyBucketMapping() {
+    public void testStandardBucketMapping() {
         int exp;
 
         exp = ENERGY_BUCKET_SCREEN_ON;
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 36f01f9..e7fdfb8 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -17,8 +17,11 @@
 package android.hardware.devicestate;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 
 import android.annotation.Nullable;
+import android.os.IBinder;
 import android.os.RemoteException;
 
 import androidx.test.filters.SmallTest;
@@ -30,6 +33,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -41,6 +45,9 @@
 @RunWith(JUnit4.class)
 @SmallTest
 public final class DeviceStateManagerGlobalTest {
+    private static final int DEFAULT_DEVICE_STATE = 0;
+    private static final int OTHER_DEVICE_STATE = 1;
+
     private TestDeviceStateManagerService mService;
     private DeviceStateManagerGlobal mDeviceStateManagerGlobal;
 
@@ -52,7 +59,7 @@
 
     @Test
     public void registerListener() {
-        mService.setDeviceState(0);
+        mService.setBaseState(DEFAULT_DEVICE_STATE);
 
         TestDeviceStateListener listener1 = new TestDeviceStateListener();
         TestDeviceStateListener listener2 = new TestDeviceStateListener();
@@ -61,28 +68,58 @@
                 ConcurrentUtils.DIRECT_EXECUTOR);
         mDeviceStateManagerGlobal.registerDeviceStateListener(listener2,
                 ConcurrentUtils.DIRECT_EXECUTOR);
-        assertEquals(0, listener1.getLastReportedState().intValue());
-        assertEquals(0, listener2.getLastReportedState().intValue());
+        assertEquals(DEFAULT_DEVICE_STATE, listener1.getLastReportedState().intValue());
+        assertEquals(DEFAULT_DEVICE_STATE, listener2.getLastReportedState().intValue());
 
-        mService.setDeviceState(1);
-        assertEquals(1, listener1.getLastReportedState().intValue());
-        assertEquals(1, listener2.getLastReportedState().intValue());
+        mService.setBaseState(OTHER_DEVICE_STATE);
+        assertEquals(OTHER_DEVICE_STATE, listener1.getLastReportedState().intValue());
+        assertEquals(OTHER_DEVICE_STATE, listener2.getLastReportedState().intValue());
     }
 
     @Test
     public void unregisterListener() {
-        mService.setDeviceState(0);
+        mService.setBaseState(DEFAULT_DEVICE_STATE);
 
         TestDeviceStateListener listener = new TestDeviceStateListener();
 
         mDeviceStateManagerGlobal.registerDeviceStateListener(listener,
                 ConcurrentUtils.DIRECT_EXECUTOR);
-        assertEquals(0, listener.getLastReportedState().intValue());
+        assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue());
 
         mDeviceStateManagerGlobal.unregisterDeviceStateListener(listener);
 
-        mService.setDeviceState(1);
-        assertEquals(0, listener.getLastReportedState().intValue());
+        mService.setBaseState(OTHER_DEVICE_STATE);
+        assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue());
+    }
+
+    @Test
+    public void submittingRequestRegisteredCallback() {
+        assertTrue(mService.mCallbacks.isEmpty());
+
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
+        mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
+
+        assertFalse(mService.mCallbacks.isEmpty());
+    }
+
+    @Test
+    public void submitRequest() {
+        mService.setBaseState(DEFAULT_DEVICE_STATE);
+
+        TestDeviceStateListener listener = new TestDeviceStateListener();
+        mDeviceStateManagerGlobal.registerDeviceStateListener(listener,
+                ConcurrentUtils.DIRECT_EXECUTOR);
+
+        assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue());
+
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
+
+        assertEquals(OTHER_DEVICE_STATE, listener.getLastReportedState().intValue());
+
+        mDeviceStateManagerGlobal.cancelRequest(request);
+
+        assertEquals(DEFAULT_DEVICE_STATE, listener.getLastReportedState().intValue());
     }
 
     private final class TestDeviceStateListener implements DeviceStateManager.DeviceStateListener {
@@ -100,8 +137,23 @@
         }
     }
 
-    private final class TestDeviceStateManagerService extends IDeviceStateManager.Stub {
-        private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+    private static final class TestDeviceStateManagerService extends IDeviceStateManager.Stub {
+        public static final class Request {
+            public final IBinder token;
+            public final int state;
+            public final int flags;
+
+            private Request(IBinder token, int state, int flags) {
+                this.token = token;
+                this.state = state;
+                this.flags = flags;
+            }
+        }
+
+        private int mBaseState = DEFAULT_DEVICE_STATE;
+        private int mMergedState = DEFAULT_DEVICE_STATE;
+        private ArrayList<Request> mRequests = new ArrayList<>();
+
         private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
 
         @Override
@@ -112,19 +164,86 @@
 
             mCallbacks.add(callback);
             try {
-                callback.onDeviceStateChanged(mDeviceState);
+                callback.onDeviceStateChanged(mMergedState);
             } catch (RemoteException e) {
                 // Do nothing. Should never happen.
             }
         }
 
-        public void setDeviceState(int deviceState) {
-            boolean stateChanged = mDeviceState != deviceState;
-            mDeviceState = deviceState;
-            if (stateChanged) {
+        @Override
+        public int[] getSupportedDeviceStates() {
+            return new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
+        }
+
+        @Override
+        public void requestState(IBinder token, int state, int flags) {
+            if (!mRequests.isEmpty()) {
+                final Request topRequest = mRequests.get(mRequests.size() - 1);
                 for (IDeviceStateManagerCallback callback : mCallbacks) {
                     try {
-                        callback.onDeviceStateChanged(mDeviceState);
+                        callback.onRequestSuspended(topRequest.token);
+                    } catch (RemoteException e) {
+                        // Do nothing. Should never happen.
+                    }
+                }
+            }
+
+            final Request request = new Request(token, state, flags);
+            mRequests.add(request);
+            notifyStateChangedIfNeeded();
+
+            for (IDeviceStateManagerCallback callback : mCallbacks) {
+                try {
+                    callback.onRequestActive(token);
+                } catch (RemoteException e) {
+                    // Do nothing. Should never happen.
+                }
+            }
+        }
+
+        @Override
+        public void cancelRequest(IBinder token) {
+            int index = -1;
+            for (int i = 0; i < mRequests.size(); i++) {
+                if (mRequests.get(i).token.equals(token)) {
+                    index = i;
+                    break;
+                }
+            }
+
+            if (index == -1) {
+                throw new IllegalArgumentException("Unknown request: " + token);
+            }
+
+            mRequests.remove(index);
+            for (IDeviceStateManagerCallback callback : mCallbacks) {
+                try {
+                    callback.onRequestCanceled(token);
+                } catch (RemoteException e) {
+                    // Do nothing. Should never happen.
+                }
+            }
+            notifyStateChangedIfNeeded();
+        }
+
+        public void setBaseState(int state) {
+            mBaseState = state;
+            notifyStateChangedIfNeeded();
+        }
+
+        private void notifyStateChangedIfNeeded() {
+            final int originalMergedState = mMergedState;
+
+            if (!mRequests.isEmpty()) {
+                mMergedState = mRequests.get(mRequests.size() - 1).state;
+            } else {
+                mMergedState = mBaseState;
+            }
+
+            if (mMergedState != originalMergedState) {
+                for (IDeviceStateManagerCallback callback : mCallbacks) {
+                    try {
+                        callback.onDeviceStateChanged(mMergedState);
                     } catch (RemoteException e) {
                         // Do nothing. Should never happen.
                     }
diff --git a/core/tests/mockingcoretests/src/android/view/OWNERS b/core/tests/mockingcoretests/src/android/view/OWNERS
new file mode 100644
index 0000000..9c9f824
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/view/OWNERS
@@ -0,0 +1,2 @@
+# Display
+per-file Display*.java = file:/services/core/java/com/android/server/display/OWNERS
diff --git a/core/tests/overlaytests/device/AndroidTest.xml b/core/tests/overlaytests/device/AndroidTest.xml
index ebbdda5..2d7d9b4 100644
--- a/core/tests/overlaytests/device/AndroidTest.xml
+++ b/core/tests/overlaytests/device/AndroidTest.xml
@@ -19,17 +19,13 @@
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="apct-instrumentation" />
 
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="remount-system" value="true" />
         <option name="push" value="OverlayDeviceTests.apk->/system/app/OverlayDeviceTests.apk" />
     </target_preparer>
-
-    <!-- Reboot to have the test APK scanned by PM and reboot after to remove the test APK. -->
-    <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer">
-      <option name="pre-reboot" value="true" />
-      <option name="post-reboot" value="true" />
-    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" />
 
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="cleanup-apks" value="true" />
diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml
index 28f99dd..734561c 100644
--- a/data/etc/com.android.emergency.xml
+++ b/data/etc/com.android.emergency.xml
@@ -19,5 +19,6 @@
         <!-- Required to place emergency calls from emergency info screen. -->
         <permission name="android.permission.CALL_PRIVILEGED"/>
         <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/com.android.launcher3.xml b/data/etc/com.android.launcher3.xml
index 99c38db..598d202 100644
--- a/data/etc/com.android.launcher3.xml
+++ b/data/etc/com.android.launcher3.xml
@@ -21,5 +21,8 @@
         <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
         <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/>
+        <permission name="android.permission.START_TASKS_FROM_RECENTS"/>
+        <permission name="android.permission.STATUS_BAR"/>
+        <permission name="android.permission.STOP_APP_SWITCHES"/>
     </privapp-permissions>
 </permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 301b491..ea42246 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -165,6 +165,7 @@
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
+    <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="audioserver" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
@@ -223,14 +224,6 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
     </split-permission>
-    <split-permission name="android.permission.RECORD_AUDIO"
-                      targetSdk="31">
-        <new-permission name="android.permission.RECORD_BACKGROUND_AUDIO" />
-    </split-permission>
-    <split-permission name="android.permission.CAMERA"
-                      targetSdk="31">
-        <new-permission name="android.permission.BACKGROUND_CAMERA" />
-    </split-permission>
 
 
     <!-- This is a list of all the libraries available for application
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 03f8918..cdc61a1 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -354,7 +354,7 @@
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.MOVE_PACKAGE"/>
         <!-- Needed for test only -->
-        <permission name="android.permission.NETWORK_AIRPLANE_MODE"/>
+        <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/>
         <permission name="android.permission.OBSERVE_APP_USAGE"/>
         <permission name="android.permission.NETWORK_SCAN"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS" />
@@ -468,6 +468,7 @@
         <!-- Permission required for CTS test - CallLogTest -->
         <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
         <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/>
+        <permission name="android.permission.MODIFY_QUIET_MODE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index c8ff95d..bb795cd 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -51,7 +51,8 @@
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(in, null);
         parser.nextTag();
-        return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null);
+        return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null,
+                0, 0);
     }
 
     /**
@@ -71,7 +72,9 @@
             @NonNull String systemFontDir,
             @Nullable String oemCustomizationXmlPath,
             @Nullable String productFontDir,
-            @Nullable Map<String, File> updatableFontMap
+            @Nullable Map<String, File> updatableFontMap,
+            long lastModifiedDate,
+            int configVersion
     ) throws IOException, XmlPullParserException {
         FontCustomizationParser.Result oemCustomization;
         if (oemCustomizationXmlPath != null) {
@@ -90,7 +93,8 @@
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(is, null);
             parser.nextTag();
-            return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap);
+            return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap,
+                    lastModifiedDate, configVersion);
         }
     }
 
@@ -98,7 +102,9 @@
             @NonNull XmlPullParser parser,
             @NonNull String fontDir,
             @NonNull FontCustomizationParser.Result customization,
-            @Nullable Map<String, File> updatableFontMap)
+            @Nullable Map<String, File> updatableFontMap,
+            long lastModifiedDate,
+            int configVersion)
             throws XmlPullParserException, IOException {
         List<FontConfig.FontFamily> families = new ArrayList<>();
         List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases());
@@ -126,7 +132,7 @@
         }
 
         families.addAll(oemNamedFamilies.values());
-        return new FontConfig(families, aliases);
+        return new FontConfig(families, aliases, lastModifiedDate, configVersion);
     }
 
     /**
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index a7d3f798..5b79d76 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -175,6 +175,39 @@
     public static final int Y16 = 0x20363159;
 
     /**
+     * <p>Android YUV P010 format.</p>
+     *
+     * P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
+     * followed immediately by a Wx(H/2) CbCr plane. Each sample is
+     * represented by a 16-bit little-endian value, with the lower 6 bits set
+     * to zero.
+     *
+     * <p>This format assumes
+     * <ul>
+     * <li>an even height</li>
+     * <li>a vertical stride equal to the height</li>
+     * </ul>
+     * </p>
+     *
+     * <pre>   stride_in_bytes = stride * 2 </pre>
+     * <pre>   y_size = stride_in_bytes * height </pre>
+     * <pre>   cbcr_size = stride_in_bytes * (height / 2) </pre>
+     * <pre>   cb_offset = y_size </pre>
+     * <pre>   cr_offset = cb_offset + 2 </pre>
+     *
+     * <p>For example, the {@link android.media.Image} object can provide data
+     * in this format from a {@link android.hardware.camera2.CameraDevice}
+     * through a {@link android.media.ImageReader} object if this format is
+     * supported by {@link android.hardware.camera2.CameraDevice}.</p>
+     *
+     * @see android.media.Image
+     * @see android.media.ImageReader
+     * @see android.hardware.camera2.CameraDevice
+     *
+     */
+    public static final int YCBCR_P010 = 0x36;
+
+    /**
      * YCbCr format, used for video.
      *
      * <p>For the {@link android.hardware.camera2} API, the {@link #YUV_420_888} format is
@@ -807,6 +840,8 @@
             case RAW_DEPTH:
             case RAW_SENSOR:
                 return 16;
+            case YCBCR_P010:
+                return 20;
             case RAW_DEPTH10:
             case RAW10:
                 return 10;
@@ -839,6 +874,7 @@
             case YUV_420_888:
             case YUV_422_888:
             case YUV_444_888:
+            case YCBCR_P010:
             case FLEX_RGB_888:
             case FLEX_RGBA_8888:
             case RAW_SENSOR:
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index c1310a9..4a92cf1 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -693,6 +693,32 @@
         throw new IllegalArgumentException("Unrecognized outline?");
     }
 
+    /** @hide */
+    public boolean clearStretch() {
+        return nClearStretch(mNativeRenderNode);
+    }
+
+    /** @hide */
+    public boolean stretch(float left, float top, float right, float bottom,
+            float vecX, float vecY, float maxStretchAmount) {
+        if (1.0 < vecX || vecX < -1.0) {
+            throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX);
+        }
+        if (1.0 < vecY || vecY < -1.0) {
+            throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
+        }
+        if (top <= bottom || right <= left) {
+            throw new IllegalArgumentException(
+                    "Stretch region must not be empty, got "
+                            + new RectF(left, top, right, bottom).toString());
+        }
+        if (maxStretchAmount <= 0.0f) {
+            throw new IllegalArgumentException(
+                    "The max stretch amount must be >0, got " + maxStretchAmount);
+        }
+        return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount);
+    }
+
     /**
      * Checks if the RenderNode has a shadow. That is, if the combination of {@link #getElevation()}
      * and {@link #getTranslationZ()} is greater than zero, there is an {@link Outline} set with
@@ -1638,6 +1664,13 @@
     private static native boolean nSetOutlineNone(long renderNode);
 
     @CriticalNative
+    private static native boolean nClearStretch(long renderNode);
+
+    @CriticalNative
+    private static native boolean nStretch(long renderNode, float left, float top, float right,
+            float bottom, float vecX, float vecY, float maxStretch);
+
+    @CriticalNative
     private static native boolean nHasShadow(long renderNode);
 
     @CriticalNative
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index a0568bf..35e6b859 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -42,6 +42,7 @@
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.text.FontConfig;
+import android.util.ArrayMap;
 import android.util.Base64;
 import android.util.LongSparseArray;
 import android.util.LruCache;
@@ -67,9 +68,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * The Typeface class specifies the typeface and intrinsic style of a font.
@@ -146,7 +147,7 @@
      */
     @GuardedBy("SYSTEM_FONT_MAP_LOCK")
     @UnsupportedAppUsage(trackingBug = 123769347)
-    static final Map<String, Typeface> sSystemFontMap = new HashMap<>();
+    static final Map<String, Typeface> sSystemFontMap = new ArrayMap<>();
 
     // DirectByteBuffer object to hold sSystemFontMap's backing memory mapping.
     static ByteBuffer sSystemFontMapBuffer = null;
@@ -249,6 +250,21 @@
     }
 
     /**
+     * Returns true if the system has the font family with the name [familyName]. For example
+     * querying with "sans-serif" would check if the "sans-serif" family is defined in the system
+     * and return true if does.
+     *
+     * @param familyName The name of the font family, cannot be null. If null, exception will be
+     *                   thrown.
+     */
+    private static boolean hasFontFamily(@NonNull String familyName) {
+        Objects.requireNonNull(familyName, "familyName cannot be null");
+        synchronized (SYSTEM_FONT_MAP_LOCK) {
+            return sSystemFontMap.containsKey(familyName);
+        }
+    }
+
+    /**
      * @hide
      * Used by Resources to load a font resource of type xml.
      */
@@ -257,6 +273,11 @@
             FamilyResourceEntry entry, AssetManager mgr, String path) {
         if (entry instanceof ProviderResourceEntry) {
             final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
+
+            String systemFontFamilyName = providerEntry.getSystemFontFamilyName();
+            if (systemFontFamilyName != null && hasFontFamily(systemFontFamilyName)) {
+                return Typeface.create(systemFontFamilyName, NORMAL);
+            }
             // Downloadable font
             List<List<String>> givenCerts = providerEntry.getCerts();
             List<List<byte[]>> certs = new ArrayList<>();
@@ -1210,7 +1231,7 @@
     /** @hide */
     @VisibleForTesting
     public static Map<String, Typeface> deserializeFontMap(ByteBuffer buffer) throws IOException {
-        Map<String, Typeface> fontMap = new HashMap<>();
+        Map<String, Typeface> fontMap = new ArrayMap<>();
         int typefacesBytesCount = buffer.getInt();
         long[] nativePtrs = nativeReadTypefaces(buffer.slice());
         if (nativePtrs == null) {
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index ebf7cea..dd64327 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -570,6 +570,13 @@
         }
     }
 
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        if (mState.mNativePtr != 0) {
+            nSetBounds(mState.mNativePtr, bounds);
+        }
+    }
+
 
     private static native long nCreate(long nativeImageDecoder,
             @Nullable ImageDecoder decoder, int width, int height, long colorSpaceHandle,
@@ -601,4 +608,6 @@
     private static native long nNativeByteSize(long nativePtr);
     @FastNative
     private static native void nSetMirrored(long nativePtr, boolean mirror);
+    @FastNative
+    private static native void nSetBounds(long nativePtr, Rect rect);
 }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 7f22dc2..28b3b04 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1465,24 +1465,6 @@
     }
 
     /**
-     * Notifies the drawable that it has been attached.
-     *
-     * @param v The view that it is attached to
-     * @hide
-     */
-    public void onAttached(View v) {
-    }
-
-    /**
-     * Notifies the drawable that it has been detached.
-     *
-     * @param v The view that it is detached from
-     * @hide
-     */
-    public void onDetached(View v) {
-    }
-
-    /**
      * Sets the source override density for this Drawable. If non-zero, this density is to be used
      * for any calls to {@link Resources#getDrawableForDensity(int, int, Theme)} or
      * {@link Resources#getValueForDensity(int, int, TypedValue, boolean)}.
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index fea756c..9214ff1 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -486,7 +486,8 @@
             long ptr;
             int fontIdentifier;
             if (mFont == null) {
-                ptr = nBuild(builderPtr, readonlyBuffer, filePath, mWeight, italic, mTtcIndex);
+                ptr = nBuild(builderPtr, readonlyBuffer, filePath, mLocaleList, mWeight, italic,
+                        mTtcIndex);
                 long fontBufferPtr = nGetFontBufferAddress(ptr);
                 synchronized (SOURCE_ID_LOCK) {
                     long id = FONT_SOURCE_ID_MAP.get(fontBufferPtr, -1);
@@ -513,8 +514,8 @@
         @CriticalNative
         private static native void nAddAxis(long builderPtr, int tag, float value);
         private static native long nBuild(
-                long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath, int weight,
-                boolean italic, int ttcIndex);
+                long builderPtr, @NonNull ByteBuffer buffer, @NonNull String filePath,
+                @NonNull String localeList, int weight, boolean italic, int ttcIndex);
         @CriticalNative
         private static native long nGetReleaseNativeFont();
 
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index c29c194..77f86fe 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -20,15 +20,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.text.FontConfig;
+import android.util.SparseIntArray;
 
 import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
 
 import libcore.util.NativeAllocationRegistry;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 
 /**
  * A font family class can be used for creating Typeface.
@@ -68,7 +69,9 @@
                     nGetReleaseNativeFamily());
 
         private final ArrayList<Font> mFonts = new ArrayList<>();
-        private final HashSet<Integer> mStyleHashSet = new HashSet<>();
+        // Most FontFamily only has  regular, bold, italic, bold-italic. Thus 4 should be good for
+        // initial capacity.
+        private final SparseIntArray mStyles = new SparseIntArray(4);
 
         /**
          * Constructs a builder.
@@ -77,7 +80,7 @@
          */
         public Builder(@NonNull Font font) {
             Preconditions.checkNotNull(font, "font can not be null");
-            mStyleHashSet.add(makeStyleIdentifier(font));
+            mStyles.append(makeStyleIdentifier(font), 0);
             mFonts.add(font);
         }
 
@@ -97,9 +100,11 @@
          */
         public @NonNull Builder addFont(@NonNull Font font) {
             Preconditions.checkNotNull(font, "font can not be null");
-            if (!mStyleHashSet.add(makeStyleIdentifier(font))) {
+            int key = makeStyleIdentifier(font);
+            if (mStyles.indexOfKey(key) >= 0) {
                 throw new IllegalArgumentException(font + " has already been added");
             }
+            mStyles.append(key, 0);
             mFonts.add(font);
             return this;
         }
@@ -120,7 +125,7 @@
                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
             }
             final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
-            final FontFamily family = new FontFamily(mFonts, langTags, variant, ptr);
+            final FontFamily family = new FontFamily(mFonts, ptr);
             sFamilyRegistory.registerNativeAllocation(family, ptr);
             return family;
         }
@@ -139,15 +144,11 @@
     }
 
     private final ArrayList<Font> mFonts;
-    private final String mLangTags;
-    private final int mVariant;
     private final long mNativePtr;
 
     // Use Builder instead.
-    private FontFamily(@NonNull ArrayList<Font> fonts, String langTags, int variant, long ptr) {
+    private FontFamily(@NonNull ArrayList<Font> fonts, long ptr) {
         mFonts = fonts;
-        mLangTags = langTags;
-        mVariant = variant;
         mNativePtr = ptr;
     }
 
@@ -157,7 +158,7 @@
      * @return a BCP-47 compliant language tag.
      */
     public @Nullable String getLangTags() {
-        return mLangTags;
+        return nGetLangTags(mNativePtr);
     }
 
     /**
@@ -165,7 +166,7 @@
      * @return a family variant
      */
     public int getVariant() {
-        return mVariant;
+        return nGetVariant(mNativePtr);
     }
 
     /**
@@ -191,4 +192,10 @@
     public long getNativePtr() {
         return mNativePtr;
     }
+
+    @FastNative
+    private static native String nGetLangTags(long family);
+
+    @CriticalNative
+    private static native int nGetVariant(long family);
 }
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index a41215f..904085f 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -22,6 +22,7 @@
 import android.graphics.Typeface;
 import android.text.FontConfig;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -36,8 +37,6 @@
 import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -76,7 +75,7 @@
             if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
                 sAvailableFonts = collectAllFonts();
             } else {
-                Set<Font> set = new HashSet<>();
+                Set<Font> set = new ArraySet<>();
                 for (FontFamily[] items : sFamilyMap.values()) {
                     for (FontFamily family : items) {
                         for (int i = 0; i < family.getSize(); ++i) {
@@ -96,7 +95,7 @@
         FontConfig fontConfig = getSystemPreinstalledFontConfig();
         Map<String, FontFamily[]> map = buildSystemFallback(fontConfig);
 
-        Set<Font> res = new HashSet<>();
+        Set<Font> res = new ArraySet<>();
         for (FontFamily[] families : map.values()) {
             for (FontFamily family : families) {
                 for (int i = 0; i < family.getSize(); ++i) {
@@ -218,7 +217,7 @@
     }
 
     private static void appendNamedFamily(@NonNull FontConfig.FontFamily xmlFamily,
-            @NonNull HashMap<String, ByteBuffer> bufferCache,
+            @NonNull ArrayMap<String, ByteBuffer> bufferCache,
             @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap) {
         final String familyName = xmlFamily.getName();
         final FontFamily family = createFontFamily(
@@ -240,10 +239,12 @@
      * @hide
      */
     public static @NonNull FontConfig getSystemFontConfig(
-            @Nullable Map<String, File> updatableFontMap
+            @Nullable Map<String, File> updatableFontMap,
+            long lastModifiedDate,
+            int configVersion
     ) {
         return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR,
-                updatableFontMap);
+                updatableFontMap, lastModifiedDate, configVersion);
     }
 
     /**
@@ -251,7 +252,8 @@
      * @hide
      */
     public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
-        return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null);
+        return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null,
+                0, 0);
     }
 
     /* package */ static @NonNull FontConfig getSystemFontConfigInternal(
@@ -259,17 +261,19 @@
             @NonNull String systemFontDir,
             @Nullable String oemXml,
             @Nullable String productFontDir,
-            @Nullable Map<String, File> updatableFontMap
+            @Nullable Map<String, File> updatableFontMap,
+            long lastModifiedDate,
+            int configVersion
     ) {
         try {
             return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir,
-                                                updatableFontMap);
+                                                updatableFontMap, lastModifiedDate, configVersion);
         } catch (IOException e) {
             Log.e(TAG, "Failed to open/read system font configurations.", e);
-            return new FontConfig(Collections.emptyList(), Collections.emptyList());
+            return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
         } catch (XmlPullParserException e) {
             Log.e(TAG, "Failed to parse the system font configuration.", e);
-            return new FontConfig(Collections.emptyList(), Collections.emptyList());
+            return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
         }
     }
 
@@ -279,8 +283,8 @@
      */
     @VisibleForTesting
     public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig) {
-        final Map<String, FontFamily[]> fallbackMap = new HashMap<>();
-        final HashMap<String, ByteBuffer> bufferCache = new HashMap<>();
+        final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+        final ArrayMap<String, ByteBuffer> bufferCache = new ArrayMap<>();
         final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
 
         final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
@@ -321,7 +325,7 @@
     public static Map<String, Typeface> buildSystemTypefaces(
             FontConfig fontConfig,
             Map<String, FontFamily[]> fallbackMap) {
-        final HashMap<String, Typeface> result = new HashMap<>();
+        final ArrayMap<String, Typeface> result = new ArrayMap<>();
         Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
         return result;
     }
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java
index 7c0af6d..6398cee 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java
@@ -37,6 +37,7 @@
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.LinkedList;
 import java.util.Map;
@@ -237,12 +238,18 @@
     }
 
     private boolean mAllowUsingExhaustedKeys = true;
+    private boolean mAllowUsingExpiredKeys = false;
 
     @Override
     public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) {
         mAllowUsingExhaustedKeys = allowUsingExhaustedKeys;
     }
 
+    @Override
+    public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
+        mAllowUsingExpiredKeys = allowUsingExpiredKeys;
+    }
+
     private boolean mOperationHandleSet = false;
     private long mOperationHandle = 0;
 
@@ -256,7 +263,8 @@
     public long getCredstoreOperationHandle() {
         if (!mOperationHandleSet) {
             try {
-                mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys);
+                mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys,
+                        mAllowUsingExpiredKeys);
                 mOperationHandleSet = true;
             } catch (android.os.RemoteException e) {
                 throw new RuntimeException("Unexpected RemoteException ", e);
@@ -306,7 +314,8 @@
                 rnsParcels,
                 sessionTranscript != null ? sessionTranscript : new byte[0],
                 readerSignature != null ? readerSignature : new byte[0],
-                mAllowUsingExhaustedKeys);
+                mAllowUsingExhaustedKeys,
+                mAllowUsingExpiredKeys);
         } catch (android.os.RemoteException e) {
             throw new RuntimeException("Unexpected RemoteException ", e);
         } catch (android.os.ServiceSpecificException e) {
@@ -410,6 +419,34 @@
     }
 
     @Override
+    public void storeStaticAuthenticationData(X509Certificate authenticationKey,
+            Instant expirationDate,
+            byte[] staticAuthData)
+            throws UnknownAuthenticationKeyException {
+        try {
+            AuthKeyParcel authKeyParcel = new AuthKeyParcel();
+            authKeyParcel.x509cert = authenticationKey.getEncoded();
+            long millisSinceEpoch = (expirationDate.getEpochSecond() * 1000)
+                                    + (expirationDate.getNano() / 1000000);
+            mBinder.storeStaticAuthenticationDataWithExpiration(authKeyParcel,
+                    millisSinceEpoch, staticAuthData);
+        } catch (CertificateEncodingException e) {
+            throw new RuntimeException("Error encoding authenticationKey", e);
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_NOT_SUPPORTED) {
+                throw new UnsupportedOperationException("Not supported", e);
+            } else if (e.errorCode == ICredentialStore.ERROR_AUTHENTICATION_KEY_NOT_FOUND) {
+                throw new UnknownAuthenticationKeyException(e.getMessage(), e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    @Override
     public @NonNull int[] getAuthenticationDataUsageCount() {
         try {
             int[] usageCount = mBinder.getAuthenticationDataUsageCount();
@@ -421,4 +458,49 @@
                     + e.errorCode, e);
         }
     }
+
+    @Override
+    public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) {
+        try {
+            byte[] proofOfOwnership = mBinder.proveOwnership(challenge);
+            return proofOfOwnership;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            if (e.errorCode == ICredentialStore.ERROR_NOT_SUPPORTED) {
+                throw new UnsupportedOperationException("Not supported", e);
+            } else {
+                throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                        + e.errorCode, e);
+            }
+        }
+    }
+
+    @Override
+    public @NonNull byte[] delete(@NonNull byte[] challenge) {
+        try {
+            byte[] proofOfDeletion = mBinder.deleteWithChallenge(challenge);
+            return proofOfDeletion;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
+
+    @Override
+    public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) {
+        try {
+            IWritableCredential binder = mBinder.update();
+            byte[] proofOfProvision =
+                    CredstoreWritableIdentityCredential.personalize(binder, personalizationData);
+            return proofOfProvision;
+        } catch (android.os.RemoteException e) {
+            throw new RuntimeException("Unexpected RemoteException ", e);
+        } catch (android.os.ServiceSpecificException e) {
+            throw new RuntimeException("Unexpected ServiceSpecificException with code "
+                    + e.errorCode, e);
+        }
+    }
 }
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index 1290633..d8d4742 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -162,5 +162,4 @@
                     + e.errorCode, e);
         }
     }
-
 }
diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
index 725e3d8..d2e7984 100644
--- a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
+++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java
@@ -76,7 +76,14 @@
 
     @NonNull @Override
     public byte[] personalize(@NonNull PersonalizationData personalizationData) {
+        return personalize(mBinder, personalizationData);
+    }
 
+    // Used by both personalize() and CredstoreIdentityCredential.update().
+    //
+    @NonNull
+    static byte[] personalize(IWritableCredential binder,
+            @NonNull PersonalizationData personalizationData) {
         Collection<AccessControlProfile> accessControlProfiles =
                 personalizationData.getAccessControlProfiles();
 
@@ -144,7 +151,7 @@
             secureUserId = getRootSid();
         }
         try {
-            byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels,
+            byte[] personalizationReceipt = binder.personalize(acpParcels, ensParcels,
                     secureUserId);
             return personalizationReceipt;
         } catch (android.os.RemoteException e) {
@@ -164,5 +171,4 @@
         return rootSid;
     }
 
-
 }
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 4eb6e42..8f175bb 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -23,6 +23,7 @@
 import java.security.KeyPair;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.Map;
 
@@ -114,6 +115,25 @@
     public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys);
 
     /**
+     * Sets whether to allow using an authentication key which has been expired if no
+     * other key is available. This must be called prior to calling
+     * {@link #getEntries(byte[], Map, byte[], byte[])}.
+     *
+     * <p>By default this is set to false.
+     *
+     * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @param allowUsingExpiredKeys whether to allow using an authentication key which use count
+     *                              has been exceeded if no other key is available.
+     */
+    public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an
      * operation handle.
      *
@@ -289,6 +309,21 @@
      *
      * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey
      * can be obtained using the {@link #getCredentialKeyCertificateChain()} method.
+
+     * <p>If the implementation is feature version 202101 or later,
+     * each X.509 certificate contains an X.509 extension at OID 1.3.6.1.4.1.11129.2.1.26 which
+     * contains a DER encoded OCTET STRING with the bytes of the CBOR with the following CDDL:
+     * <pre>
+     *   ProofOfBinding = [
+     *     "ProofOfBinding",
+     *     bstr,              // Contains SHA-256(ProofOfProvisioning)
+     *   ]
+     * </pre>
+     * <p>This CBOR enables an issuer to determine the exact state of the credential it
+     * returns issuer-signed data for.
+     *
+     * <p> See {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for
+     * known feature versions.
      *
      * @return A collection of X.509 certificates for dynamic authentication keys that need issuer
      * certification.
@@ -308,16 +343,136 @@
      *                          the authenticity
      *                          and integrity of the credential data fields.
      * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized.
+     * @deprecated Use {@link #storeStaticAuthenticationData(X509Certificate, Instant, byte[])}
+     *     instead.
      */
+    @Deprecated
     public abstract void storeStaticAuthenticationData(
             @NonNull X509Certificate authenticationKey,
             @NonNull byte[] staticAuthData)
             throws UnknownAuthenticationKeyException;
 
     /**
+     * Store authentication data associated with a dynamic authentication key.
+     *
+     * This should only be called for an authenticated key returned by
+     * {@link #getAuthKeysNeedingCertification()}.
+     *
+     * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @param authenticationKey The dynamic authentication key for which certification and
+     *                          associated static
+     *                          authentication data is being provided.
+     * @param expirationDate    The expiration date of the static authentication data.
+     * @param staticAuthData    Static authentication data provided by the issuer that validates
+     *                          the authenticity
+     *                          and integrity of the credential data fields.
+     * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized.
+     */
+    public void storeStaticAuthenticationData(
+            @NonNull X509Certificate authenticationKey,
+            @NonNull Instant expirationDate,
+            @NonNull byte[] staticAuthData)
+            throws UnknownAuthenticationKeyException {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Get the number of times the dynamic authentication keys have been used.
      *
      * @return int array of dynamic authentication key usage counts.
      */
     public @NonNull abstract int[] getAuthenticationDataUsageCount();
+
+    /**
+     * Proves ownership of a credential.
+     *
+     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
+     * with payload set to {@code ProofOfDeletion} as defined below.</p>
+     *
+     * <p>The returned CBOR is the following:</p>
+     * <pre>
+     *     ProofOfOwnership = [
+     *          "ProofOfOwnership",           ; tstr
+     *          tstr,                         ; DocType
+     *          bstr,                         ; Challenge
+     *          bool                          ; true if this is a test credential, should
+     *                                        ; always be false.
+     *      ]
+     * </pre>
+     *
+     * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @param challenge is a non-empty byte array whose contents should be unique, fresh and
+     *                  provided by the issuing authority. The value provided is embedded in the
+     *                  generated CBOR and enables the issuing authority to verify that the
+     *                  returned proof is fresh.
+     * @return the COSE_Sign1 data structure above
+     */
+    public @NonNull byte[] proveOwnership(@NonNull byte[] challenge)  {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Deletes a credential.
+     *
+     * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey
+     * with payload set to {@code ProofOfDeletion} as defined below.</p>
+     *
+     * <pre>
+     *     ProofOfDeletion = [
+     *          "ProofOfDeletion",            ; tstr
+     *          tstr,                         ; DocType
+     *          bstr,                         ; Challenge
+     *          bool                          ; true if this is a test credential, should
+     *                                        ; always be false.
+     *      ]
+     * </pre>
+     *
+     * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @param challenge is a non-empty byte array whose contents should be unique, fresh and
+     *                  provided by the issuing authority. The value provided is embedded in the
+     *                  generated CBOR and enables the issuing authority to verify that the
+     *                  returned proof is fresh.
+     * @return the COSE_Sign1 data structure above
+     */
+    public @NonNull byte[] delete(@NonNull byte[] challenge)  {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Updates the credential with new access control profiles and data items.
+     *
+     * <p>This method is similar to
+     * {@link WritableIdentityCredential#personalize(PersonalizationData)} except that it operates
+     * on an existing credential, see the documentation for that method for the format of the
+     * returned data.
+     *
+     * <p>If this call succeeds an side-effect is that all dynamic authentication keys for the
+     * credential are deleted. The application will need to use
+     * {@link #getAuthKeysNeedingCertification()} to generate replacement keys and return
+     * them for issuer certification.
+     *
+     * <p>This is only implemented in feature version 202101 or later. If not implemented, the call
+     * fails with {@link UnsupportedOperationException}. See
+     * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known
+     * feature versions.
+     *
+     * @param personalizationData   The data to update, including access control profiles
+     *                              and data elements and their values, grouped into namespaces.
+     * @return A COSE_Sign1 data structure, see above.
+     */
+    public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java
index 3843d92..6ccd0e8 100644
--- a/identity/java/android/security/identity/IdentityCredentialStore.java
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -72,6 +72,17 @@
  * <p>Credentials provisioned to the direct access store should <strong>always</strong> use reader
  * authentication to protect data elements. The reason for this is user authentication or user
  * approval of data release is not possible when the device is off.
+ *
+ * <p>The Identity Credential API is designed to be able to evolve and change over time
+ * but still provide 100% backwards compatibility. This is complicated by the fact that
+ * there may be a version skew between the API used by the application and the version
+ * implemented in secure hardware. To solve this problem, the API provides for a way
+ * for the application to query which feature version the hardware implements (if any
+ * at all) using
+ * {@link android.content.pm#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} and
+ * {@link android.content.pm#FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS}.
+ * Methods which only work on certain feature versions are clearly documented as
+ * such.
  */
 public abstract class IdentityCredentialStore {
     IdentityCredentialStore() {}
@@ -193,7 +204,9 @@
      * @param credentialName the name of the credential to delete.
      * @return {@code null} if the credential was not found, the COSE_Sign1 data structure above
      *     if the credential was found and deleted.
+     * @deprecated Use {@link IdentityCredential#delete(byte[])} instead.
      */
+    @Deprecated
     public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName);
 
     /** @hide */
@@ -201,5 +214,4 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Ciphersuite {
     }
-
 }
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index fcc518c..21d23b1 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -82,7 +82,7 @@
      *
      * @param locked            - whether it is a lock (true) or unlock (false) event
      * @param syntheticPassword - if it is an unlock event with the password, pass the synthetic
-     *                          password provided by the LockSettingService
+     *                            password provided by the LockSettingService
      *
      * @return 0 if successful or a {@code ResponseCode}.
      */
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index f41b608..ae9f8664 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -19,9 +19,9 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
-import com.android.org.bouncycastle.util.io.pem.PemObject;
-import com.android.org.bouncycastle.util.io.pem.PemReader;
-import com.android.org.bouncycastle.util.io.pem.PemWriter;
+import com.android.internal.org.bouncycastle.util.io.pem.PemObject;
+import com.android.internal.org.bouncycastle.util.io.pem.PemReader;
+import com.android.internal.org.bouncycastle.util.io.pem.PemWriter;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 4a67135..e19d88c 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -45,8 +45,8 @@
 import android.security.keystore.UserNotAuthenticatedException;
 import android.util.Log;
 
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index 92d87aa..f7477bf 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
+import android.security.keymaster.KeymasterDefs;
 import android.system.keystore2.IKeystoreService;
 import android.system.keystore2.KeyDescriptor;
 import android.system.keystore2.KeyEntryResponse;
@@ -107,7 +108,7 @@
                 return request.execute(service);
             } catch (ServiceSpecificException e) {
                 Log.e(TAG, "KeyStore exception", e);
-                throw new KeyStoreException(e.errorCode, "");
+                throw getKeyStoreException(e.errorCode);
             } catch (RemoteException e) {
                 if (firstTry) {
                     Log.w(TAG, "Looks like we may have lost connection to the Keystore "
@@ -274,4 +275,40 @@
         }
     }
 
+    static KeyStoreException getKeyStoreException(int errorCode) {
+        if (errorCode > 0) {
+            // KeyStore layer error
+            switch (errorCode) {
+                case ResponseCode.LOCKED:
+                    return new KeyStoreException(errorCode, "User authentication required");
+                case ResponseCode.UNINITIALIZED:
+                    return new KeyStoreException(errorCode, "Keystore not initialized");
+                case ResponseCode.SYSTEM_ERROR:
+                    return new KeyStoreException(errorCode, "System error");
+                case ResponseCode.PERMISSION_DENIED:
+                    return new KeyStoreException(errorCode, "Permission denied");
+                case ResponseCode.KEY_NOT_FOUND:
+                    return new KeyStoreException(errorCode, "Key not found");
+                case ResponseCode.VALUE_CORRUPTED:
+                    return new KeyStoreException(errorCode, "Key blob corrupted");
+                case ResponseCode.KEY_PERMANENTLY_INVALIDATED:
+                    return new KeyStoreException(errorCode, "Key permanently invalidated");
+                default:
+                    return new KeyStoreException(errorCode, String.valueOf(errorCode));
+            }
+        } else {
+            // Keymaster layer error
+            switch (errorCode) {
+                case KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT:
+                    // The name of this parameter significantly differs between Keymaster and
+                    // framework APIs. Use the framework wording to make life easier for developers.
+                    return new KeyStoreException(errorCode,
+                            "Invalid user authentication validity duration");
+                default:
+                    return new KeyStoreException(errorCode,
+                            KeymasterDefs.getErrorMessage(errorCode));
+            }
+        }
+    }
+
 }
diff --git a/keystore/java/android/security/KeyStoreOperation.java b/keystore/java/android/security/KeyStoreOperation.java
index 7ea9e14..a6552dd 100644
--- a/keystore/java/android/security/KeyStoreOperation.java
+++ b/keystore/java/android/security/KeyStoreOperation.java
@@ -73,8 +73,7 @@
                     );
                 }
                 default:
-                    // TODO Human readable string. Use something like KeyStore.getKeyStoreException
-                    throw new KeyStoreException(e.errorCode, "");
+                    throw KeyStore2.getKeyStoreException(e.errorCode);
             }
         } catch (RemoteException e) {
             // Log exception and report invalid operation handle.
diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java
index 3ef4aa5..372add9 100644
--- a/keystore/java/android/security/KeyStoreSecurityLevel.java
+++ b/keystore/java/android/security/KeyStoreSecurityLevel.java
@@ -52,7 +52,7 @@
         try {
             return request.execute();
         } catch (ServiceSpecificException e) {
-            throw new KeyStoreException(e.errorCode, "");
+            throw KeyStore2.getKeyStoreException(e.errorCode);
         } catch (RemoteException e) {
             // Log exception and report invalid operation handle.
             // This should prompt the caller drop the reference to this operation and retry.
@@ -96,25 +96,26 @@
             } catch (ServiceSpecificException e) {
                 switch (e.errorCode) {
                     case ResponseCode.BACKEND_BUSY: {
+                        long backOffHint = (long) (Math.random() * 80 + 20);
                         if (CompatChanges.isChangeEnabled(
                                 KeyStore2.KEYSTORE_OPERATION_CREATION_MAY_FAIL)) {
                             // Starting with Android S we inform the caller about the
                             // backend being busy.
-                            throw new BackendBusyException();
+                            throw new BackendBusyException(backOffHint);
                         } else {
                             // Before Android S operation creation must always succeed. So we
                             // just have to retry. We do so with a randomized back-off between
-                            // 50 and 250ms.
+                            // 20 and 100ms.
                             // It is a little awkward that we cannot break out of this loop
                             // by interrupting this thread. But that is the expected behavior.
                             // There is some comfort in the fact that interrupting a thread
                             // also does not unblock a thread waiting for a binder transaction.
-                            interruptedPreservingSleep((long) (Math.random() * 200 + 50));
+                            interruptedPreservingSleep(backOffHint);
                         }
                         break;
                     }
                     default:
-                        throw new KeyStoreException(e.errorCode, "");
+                        throw KeyStore2.getKeyStoreException(e.errorCode);
                 }
             } catch (RemoteException e) {
                 Log.w(TAG, "Cannot connect to keystore", e);
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 6ad8d2c..334b111 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -26,24 +26,24 @@
 import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keymaster.KeymasterDefs;
 
-import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
-import com.android.org.bouncycastle.asn1.ASN1InputStream;
-import com.android.org.bouncycastle.asn1.ASN1Integer;
-import com.android.org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import com.android.org.bouncycastle.asn1.DERBitString;
-import com.android.org.bouncycastle.asn1.DERNull;
-import com.android.org.bouncycastle.asn1.DERSequence;
-import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import com.android.org.bouncycastle.asn1.x509.Certificate;
-import com.android.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
-import com.android.org.bouncycastle.asn1.x509.TBSCertificate;
-import com.android.org.bouncycastle.asn1.x509.Time;
-import com.android.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import com.android.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
-import com.android.org.bouncycastle.jce.X509Principal;
-import com.android.org.bouncycastle.jce.provider.X509CertificateObject;
-import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
+import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector;
+import com.android.internal.org.bouncycastle.asn1.ASN1InputStream;
+import com.android.internal.org.bouncycastle.asn1.ASN1Integer;
+import com.android.internal.org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import com.android.internal.org.bouncycastle.asn1.DERBitString;
+import com.android.internal.org.bouncycastle.asn1.DERNull;
+import com.android.internal.org.bouncycastle.asn1.DERSequence;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import com.android.internal.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import com.android.internal.org.bouncycastle.asn1.x509.Certificate;
+import com.android.internal.org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import com.android.internal.org.bouncycastle.asn1.x509.TBSCertificate;
+import com.android.internal.org.bouncycastle.asn1.x509.Time;
+import com.android.internal.org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
+import com.android.internal.org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import com.android.internal.org.bouncycastle.jce.X509Principal;
+import com.android.internal.org.bouncycastle.jce.provider.X509CertificateObject;
+import com.android.internal.org.bouncycastle.x509.X509V3CertificateGenerator;
 
 import libcore.util.EmptyArray;
 
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index b1b6306..16bf546 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -23,7 +23,6 @@
 import android.security.keymaster.ExportResult;
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterDefs;
-import android.sysprop.Keystore2Properties;
 
 import java.io.IOException;
 import java.security.KeyFactory;
@@ -117,8 +116,6 @@
         putSecretKeyFactoryImpl("HmacSHA512");
     }
 
-    private static boolean sKeystore2Enabled;
-
     /**
      * This function indicates whether or not Keystore 2.0 is enabled. Some parts of the
      * Keystore SPI must behave subtly differently when Keystore 2.0 is enabled. However,
@@ -133,10 +130,9 @@
      * @hide
      */
     public static boolean isKeystore2Enabled() {
-        return sKeystore2Enabled;
+        return android.security.keystore2.AndroidKeyStoreProvider.isInstalled();
     }
 
-
     /**
      * Installs a new instance of this provider (and the
      * {@link AndroidKeyStoreBCWorkaroundProvider}).
@@ -164,11 +160,6 @@
             // priority.
             Security.addProvider(workaroundProvider);
         }
-
-        // {@code install()} is run by zygote when this property is still accessible. We store its
-        // value so that the Keystore SPI can act accordingly without having to access an internal
-        // property.
-        sKeystore2Enabled = Keystore2Properties.keystore2_enabled().orElse(false);
     }
 
     private void putSecretKeyFactoryImpl(String algorithm) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 3694d63..d2678c7 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -215,7 +215,8 @@
                 // Keystore 1.0 does not tell us the exact security level of the key
                 // so we report an unknown but secure security level.
                 insideSecureHardware ? KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE
-                        : KeyProperties.SECURITY_LEVEL_SOFTWARE);
+                        : KeyProperties.SECURITY_LEVEL_SOFTWARE,
+                KeyProperties.UNRESTRICTED_USAGE_COUNT);
     }
 
     private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
diff --git a/keystore/java/android/security/keystore/BackendBusyException.java b/keystore/java/android/security/keystore/BackendBusyException.java
index 1a88469..a813e93 100644
--- a/keystore/java/android/security/keystore/BackendBusyException.java
+++ b/keystore/java/android/security/keystore/BackendBusyException.java
@@ -16,37 +16,66 @@
 
 package android.security.keystore;
 
+import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
 
 import java.security.ProviderException;
 
 /**
  * Indicates a transient error that prevented a key operation from being created.
- * Callers should try again with a back-off period of 10-30 milliseconds.
+ * Callers should try again with a back-off period of {@link #getBackOffHintMillis()}
+ * milliseconds.
  */
 public class BackendBusyException extends ProviderException {
 
+    private final long mBackOffHintMillis;
+
     /**
      * Constructs a new {@code BackendBusyException} without detail message and cause.
+     *
      */
-    public BackendBusyException() {
+    public BackendBusyException(@DurationMillisLong long backOffHintMillis) {
         super("The keystore backend has no operation slots available. Retry later.");
+        if (backOffHintMillis < 0) {
+            throw new IllegalArgumentException("Back-off hint cannot be negative.");
+        }
+        mBackOffHintMillis = backOffHintMillis;
     }
 
     /**
      * Constructs a new {@code BackendBusyException} with the provided detail message and
      * no cause.
      */
-    public BackendBusyException(@NonNull String message) {
+    public BackendBusyException(@DurationMillisLong long backOffHintMillis,
+            @NonNull String message) {
         super(message);
+        if (backOffHintMillis < 0) {
+            throw new IllegalArgumentException("Back-off hint cannot be negative.");
+        }
+        mBackOffHintMillis = backOffHintMillis;
     }
 
     /**
      * Constructs a new {@code BackendBusyException} with the provided detail message and
      * cause.
      */
-    public BackendBusyException(@NonNull String message, @NonNull Throwable cause) {
+    public BackendBusyException(@DurationMillisLong long backOffHintMillis,
+            @NonNull String message, @NonNull Throwable cause) {
         super(message, cause);
+        if (backOffHintMillis < 0) {
+            throw new IllegalArgumentException("Back-off hint cannot be negative.");
+        }
+        mBackOffHintMillis = backOffHintMillis;
     }
 
+    /**
+     * When retrying to start a Keystore operation after receiving this exception, this can be
+     * used to determine how long to wait before retrying. It is not guaranteed that the operation
+     * will succeeds after this time. Multiple retries may be necessary if the system is congested.
+     *
+     * @return Number of milliseconds to back off before retrying.
+     */
+    public @DurationMillisLong long getBackOffHintMillis() {
+        return mBackOffHintMillis;
+    }
 }
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index e9aac7d..c2a7b2e 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -274,6 +274,7 @@
     private final boolean mUserConfirmationRequired;
     private final boolean mUnlockedDeviceRequired;
     private final boolean mCriticalToDeviceEncryption;
+    private final int mMaxUsageCount;
     /*
      * ***NOTE***: All new fields MUST also be added to the following:
      * ParcelableKeyGenParameterSpec class.
@@ -313,7 +314,8 @@
             boolean isStrongBoxBacked,
             boolean userConfirmationRequired,
             boolean unlockedDeviceRequired,
-            boolean criticalToDeviceEncryption) {
+            boolean criticalToDeviceEncryption,
+            int maxUsageCount) {
         if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
         }
@@ -366,6 +368,7 @@
         mUserConfirmationRequired = userConfirmationRequired;
         mUnlockedDeviceRequired = unlockedDeviceRequired;
         mCriticalToDeviceEncryption = criticalToDeviceEncryption;
+        mMaxUsageCount = maxUsageCount;
     }
 
     /**
@@ -782,7 +785,7 @@
     }
 
     /**
-     * Return whether this key is critical to the device encryption flow.
+     * Returns whether this key is critical to the device encryption flow.
      *
      * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
      * @hide
@@ -792,6 +795,17 @@
     }
 
     /**
+     * Returns the maximum number of times the limited use key is allowed to be used or
+     * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there’s no restriction on the number of
+     * times the key can be used.
+     *
+     * @see Builder#setMaxUsageCount(int)
+     */
+    public int getMaxUsageCount() {
+        return mMaxUsageCount;
+    }
+
+    /**
      * Builder of {@link KeyGenParameterSpec} instances.
      */
     public final static class Builder {
@@ -827,6 +841,7 @@
         private boolean mUserConfirmationRequired;
         private boolean mUnlockedDeviceRequired = false;
         private boolean mCriticalToDeviceEncryption = false;
+        private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -894,6 +909,7 @@
             mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired();
             mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
             mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
+            mMaxUsageCount = sourceSpec.getMaxUsageCount();
         }
 
         /**
@@ -1553,6 +1569,47 @@
         }
 
         /**
+         * Sets the maximum number of times the key is allowed to be used. After every use of the
+         * key, the use counter will decrease. This authorization applies only to secret key and
+         * private key operations. Public key operations are not restricted. For example, after
+         * successfully encrypting and decrypting data using methods such as
+         * {@link Cipher#doFinal()}, the use counter of the secret key will decrease. After
+         * successfully signing data using methods such as {@link Signature#sign()}, the use
+         * counter of the private key will decrease.
+         *
+         * When the use counter is depleted, the key will be marked for deletion by Android
+         * Keystore and any subsequent attempt to use the key will throw
+         * {@link KeyPermanentlyInvalidatedException}. There is no key to be loaded from the
+         * Android Keystore once the exhausted key is permanently deleted, as if the key never
+         * existed before.
+         *
+         * <p>By default, there is no restriction on the usage of key.
+         *
+         * <p>Some secure hardware may not support this feature at all, in which case it will
+         * be enforced in software, some secure hardware may support it but only with
+         * maxUsageCount = 1, and some secure hardware may support it with larger value
+         * of maxUsageCount.
+         *
+         * <p>The PackageManger feature flags:
+         * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_SINGLE_USE_KEY} and
+         * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_LIMITED_USE_KEY} can be used
+         * to check whether the secure hardware cannot enforce this feature, can only enforce it
+         * with maxUsageCount = 1, or can enforce it with larger value of maxUsageCount.
+         *
+         * @param maxUsageCount maximum number of times the key is allowed to be used or
+         *        {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there is no restriction on the
+         *        usage.
+         */
+        @NonNull
+        public Builder setMaxUsageCount(int maxUsageCount) {
+            if (maxUsageCount == KeyProperties.UNRESTRICTED_USAGE_COUNT || maxUsageCount > 0) {
+                mMaxUsageCount = maxUsageCount;
+                return this;
+            }
+            throw new IllegalArgumentException("maxUsageCount is not valid");
+        }
+
+        /**
          * Builds an instance of {@code KeyGenParameterSpec}.
          */
         @NonNull
@@ -1587,7 +1644,8 @@
                     mIsStrongBoxBacked,
                     mUserConfirmationRequired,
                     mUnlockedDeviceRequired,
-                    mCriticalToDeviceEncryption);
+                    mCriticalToDeviceEncryption,
+                    mMaxUsageCount);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index 7158d0c..f50efd2 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -85,6 +85,7 @@
     private final boolean mInvalidatedByBiometricEnrollment;
     private final boolean mUserConfirmationRequired;
     private final @KeyProperties.SecurityLevelEnum int mSecurityLevel;
+    private final int mRemainingUsageCount;
 
     /**
      * @hide
@@ -109,7 +110,8 @@
             boolean trustedUserPresenceRequired,
             boolean invalidatedByBiometricEnrollment,
             boolean userConfirmationRequired,
-            @KeyProperties.SecurityLevelEnum int securityLevel) {
+            @KeyProperties.SecurityLevelEnum int securityLevel,
+            int remainingUsageCount) {
         mKeystoreAlias = keystoreKeyAlias;
         mInsideSecureHardware = insideSecureHardware;
         mOrigin = origin;
@@ -134,6 +136,7 @@
         mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
         mUserConfirmationRequired = userConfirmationRequired;
         mSecurityLevel = securityLevel;
+        mRemainingUsageCount = remainingUsageCount;
     }
 
     /**
@@ -374,4 +377,15 @@
     public @KeyProperties.SecurityLevelEnum int getSecurityLevel() {
         return mSecurityLevel;
     }
+
+    /**
+     * Returns the remaining number of times the key is allowed to be used or
+     * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there's no restriction on the number of
+     * times the key can be used. Note that this gives a best effort count and need not be
+     * accurate (as there might be usages happening in parallel and the count maintained here need
+     * not be in sync with the usage).
+     */
+    public int getRemainingUsageCount() {
+        return mRemainingUsageCount;
+    }
 }
diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java
index 5928540..3ebca6a 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -20,6 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.annotation.SystemApi;
+import android.os.Process;
 import android.security.KeyStore;
 import android.security.keymaster.KeymasterDefs;
 
@@ -67,6 +69,7 @@
             PURPOSE_SIGN,
             PURPOSE_VERIFY,
             PURPOSE_WRAP_KEY,
+            PURPOSE_AGREE_KEY,
     })
     public @interface PurposeEnum {}
 
@@ -96,6 +99,11 @@
     public static final int PURPOSE_WRAP_KEY = 1 << 5;
 
     /**
+     * Purpose of key: creating a shared ECDH secret through key agreement.
+     */
+    public static final int PURPOSE_AGREE_KEY = 1 << 6;
+
+    /**
      * @hide
      */
     public static abstract class Purpose {
@@ -113,6 +121,8 @@
                     return KeymasterDefs.KM_PURPOSE_VERIFY;
                 case PURPOSE_WRAP_KEY:
                     return KeymasterDefs.KM_PURPOSE_WRAP;
+                case PURPOSE_AGREE_KEY:
+                    return KeymasterDefs.KM_PURPOSE_AGREE_KEY;
                 default:
                     throw new IllegalArgumentException("Unknown purpose: " + purpose);
             }
@@ -130,6 +140,8 @@
                     return PURPOSE_VERIFY;
                 case KeymasterDefs.KM_PURPOSE_WRAP:
                     return PURPOSE_WRAP_KEY;
+                case KeymasterDefs.KM_PURPOSE_AGREE_KEY:
+                    return PURPOSE_AGREE_KEY;
                 default:
                     throw new IllegalArgumentException("Unknown purpose: " + purpose);
             }
@@ -864,9 +876,18 @@
      * which it must be configured in SEPolicy.
      * @hide
      */
+    @SystemApi
     public static final int NAMESPACE_APPLICATION = -1;
 
     /**
+     * The namespace identifier for the WIFI Keystore namespace.
+     * This must be kept in sync with system/sepolicy/private/keystore2_key_contexts
+     * @hide
+     */
+    @SystemApi
+    public static final int NAMESPACE_WIFI = 102;
+
+    /**
      * For legacy support, translate namespaces into known UIDs.
      * @hide
      */
@@ -874,6 +895,8 @@
         switch (namespace) {
             case NAMESPACE_APPLICATION:
                 return KeyStore.UID_SELF;
+            case NAMESPACE_WIFI:
+                return Process.WIFI_UID;
             // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
             //  b/171305388 and b/171305607
             default:
@@ -890,6 +913,8 @@
         switch (uid) {
             case KeyStore.UID_SELF:
                 return NAMESPACE_APPLICATION;
+            case Process.WIFI_UID:
+                return NAMESPACE_WIFI;
             // TODO Translate WIFI and VPN UIDs once the namespaces are defined.
             //  b/171305388 and b/171305607
             default:
@@ -897,4 +922,9 @@
                         + uid);
         }
     }
+
+    /**
+     * This value indicates that there is no restriction on the number of times the key can be used.
+     */
+    public static final int UNRESTRICTED_USAGE_COUNT = -1;
 }
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index e1e404d..aaa3715 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -235,6 +235,7 @@
     private final boolean mUserConfirmationRequired;
     private final boolean mUnlockedDeviceRequired;
     private final boolean mIsStrongBoxBacked;
+    private final int mMaxUsageCount;
 
     private KeyProtection(
             Date keyValidityStart,
@@ -256,7 +257,8 @@
             boolean criticalToDeviceEncryption,
             boolean userConfirmationRequired,
             boolean unlockedDeviceRequired,
-            boolean isStrongBoxBacked) {
+            boolean isStrongBoxBacked,
+            int maxUsageCount) {
         mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
         mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
         mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
@@ -279,6 +281,7 @@
         mUserConfirmationRequired = userConfirmationRequired;
         mUnlockedDeviceRequired = unlockedDeviceRequired;
         mIsStrongBoxBacked = isStrongBoxBacked;
+        mMaxUsageCount = maxUsageCount;
     }
 
     /**
@@ -548,6 +551,17 @@
     }
 
     /**
+     * Returns the maximum number of times the limited use key is allowed to be used or
+     * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there’s no restriction on the number of
+     * times the key can be used.
+     *
+     * @see Builder#setMaxUsageCount(int)
+     */
+    public int getMaxUsageCount() {
+        return mMaxUsageCount;
+    }
+
+    /**
      * Builder of {@link KeyProtection} instances.
      */
     public final static class Builder {
@@ -574,6 +588,7 @@
         private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
         private boolean mCriticalToDeviceEncryption = false;
         private boolean mIsStrongBoxBacked = false;
+        private  int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -1039,6 +1054,47 @@
         }
 
         /**
+         * Sets the maximum number of times the key is allowed to be used. After every use of the
+         * key, the use counter will decrease. This authorization applies only to secret key and
+         * private key operations. Public key operations are not restricted. For example, after
+         * successfully encrypting and decrypting data using methods such as
+         * {@link Cipher#doFinal()}, the use counter of the secret key will decrease. After
+         * successfully signing data using methods such as {@link Signature#sign()}, the use
+         * counter of the private key will decrease.
+         *
+         * When the use counter is depleted, the key will be marked for deletion by Android
+         * Keystore and any subsequent attempt to use the key will throw
+         * {@link KeyPermanentlyInvalidatedException}. There is no key to be loaded from the
+         * Android Keystore once the exhausted key is permanently deleted, as if the key never
+         * existed before.
+         *
+         * <p>By default, there is no restriction on the usage of key.
+         *
+         * <p>Some secure hardware may not support this feature at all, in which case it will
+         * be enforced in software, some secure hardware may support it but only with
+         * maxUsageCount = 1, and some secure hardware may support it with larger value
+         * of maxUsageCount.
+         *
+         * <p>The PackageManger feature flags:
+         * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_SINGLE_USE_KEY} and
+         * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_LIMITED_USE_KEY} can be used
+         * to check whether the secure hardware cannot enforce this feature, can only enforce it
+         * with maxUsageCount = 1, or can enforce it with larger value of maxUsageCount.
+         *
+         * @param maxUsageCount maximum number of times the key is allowed to be used or
+         *        {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there is no restriction on the
+         *        usage.
+         */
+        @NonNull
+        public Builder setMaxUsageCount(int maxUsageCount) {
+            if (maxUsageCount == KeyProperties.UNRESTRICTED_USAGE_COUNT || maxUsageCount > 0) {
+                mMaxUsageCount = maxUsageCount;
+                return this;
+            }
+            throw new IllegalArgumentException("maxUsageCount is not valid");
+        }
+
+        /**
          * Builds an instance of {@link KeyProtection}.
          *
          * @throws IllegalArgumentException if a required field is missing
@@ -1065,7 +1121,8 @@
                     mCriticalToDeviceEncryption,
                     mUserConfirmationRequired,
                     mUnlockedDeviceRequired,
-                    mIsStrongBoxBacked);
+                    mIsStrongBoxBacked,
+                    mMaxUsageCount);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 69c15cc..8163472 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -108,6 +108,7 @@
         out.writeBoolean(mSpec.isUserConfirmationRequired());
         out.writeBoolean(mSpec.isUnlockedDeviceRequired());
         out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
+        out.writeInt(mSpec.getMaxUsageCount());
     }
 
     private static Date readDateOrNull(Parcel in) {
@@ -166,6 +167,7 @@
         final boolean userConfirmationRequired = in.readBoolean();
         final boolean unlockedDeviceRequired = in.readBoolean();
         final boolean criticalToDeviceEncryption = in.readBoolean();
+        final int maxUsageCount = in.readInt();
         // The KeyGenParameterSpec is intentionally not constructed using a Builder here:
         // The intention is for this class to break if new parameters are added to the
         // KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -199,7 +201,8 @@
                 isStrongBoxBacked,
                 userConfirmationRequired,
                 unlockedDeviceRequired,
-                criticalToDeviceEncryption);
+                criticalToDeviceEncryption,
+                maxUsageCount);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
index 6ddaa70..b631999 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java
@@ -38,9 +38,10 @@
 
     public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor,
             @NonNull KeyMetadata metadata,
+            @NonNull byte[] x509EncodedForm,
             @NonNull KeyStoreSecurityLevel securityLevel,
             @NonNull ECParameterSpec params, @NonNull ECPoint w) {
-        super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_EC, securityLevel);
+        super(descriptor, metadata, x509EncodedForm, KeyProperties.KEY_ALGORITHM_EC, securityLevel);
         mParams = params;
         mW = w;
     }
@@ -48,7 +49,7 @@
     public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor,
             @NonNull KeyMetadata metadata,
             @NonNull KeyStoreSecurityLevel securityLevel, @NonNull ECPublicKey info) {
-        this(descriptor, metadata, securityLevel, info.getParams(), info.getW());
+        this(descriptor, metadata, info.getEncoded(), securityLevel, info.getParams(), info.getW());
         if (!"X.509".equalsIgnoreCase(info.getFormat())) {
             throw new IllegalArgumentException(
                     "Unsupported key export format: " + info.getFormat());
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
new file mode 100644
index 0000000..fc963a8
--- /dev/null
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2021 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 android.security.keystore2;
+
+import android.hardware.security.keymint.Algorithm;
+import android.hardware.security.keymint.KeyParameter;
+import android.hardware.security.keymint.KeyPurpose;
+import android.hardware.security.keymint.Tag;
+import android.security.KeyStoreException;
+import android.security.KeyStoreOperation;
+import android.security.keystore.KeyStoreCryptoOperation;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.ProviderException;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.crypto.KeyAgreementSpi;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * {@link KeyAgreementSpi} which provides an ECDH implementation backed by Android KeyStore.
+ *
+ * @hide
+ */
+public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi
+        implements KeyStoreCryptoOperation {
+
+    private static final String TAG = "AndroidKeyStoreKeyAgreementSpi";
+
+    /**
+     * ECDH implementation.
+     *
+     * @hide
+     */
+    public static class ECDH extends AndroidKeyStoreKeyAgreementSpi {
+        public ECDH() {
+            super(Algorithm.EC);
+        }
+    }
+
+    private final int mKeymintAlgorithm;
+
+    // Fields below are populated by engineInit and should be preserved after engineDoFinal.
+    private AndroidKeyStorePrivateKey mKey;
+    private PublicKey mOtherPartyKey;
+
+    // Fields below are reset when engineDoFinal succeeds.
+    private KeyStoreOperation mOperation;
+    private long mOperationHandle;
+
+    protected AndroidKeyStoreKeyAgreementSpi(int keymintAlgorithm) {
+        resetAll();
+
+        mKeymintAlgorithm = keymintAlgorithm;
+    }
+
+    @Override
+    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
+        resetAll();
+
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        } else if (!(key instanceof AndroidKeyStorePrivateKey)) {
+            throw new InvalidKeyException(
+                    "Only Android KeyStore private keys supported. Key: " + key);
+        }
+        // Checking the correct KEY_PURPOSE and algorithm is done by the Keymint implementation in
+        // ensureKeystoreOperationInitialized() below.
+        mKey = (AndroidKeyStorePrivateKey) key;
+
+        boolean success = false;
+        try {
+            ensureKeystoreOperationInitialized();
+            success = true;
+        } finally {
+            if (!success) {
+                resetAll();
+            }
+        }
+    }
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random)
+            throws InvalidKeyException, InvalidAlgorithmParameterException {
+        if (params != null) {
+            throw new InvalidAlgorithmParameterException(
+                    "Unsupported algorithm parameters: " + params);
+        }
+        engineInit(key, random);
+    }
+
+    @Override
+    protected Key engineDoPhase(Key key, boolean lastPhase)
+            throws InvalidKeyException, IllegalStateException {
+        ensureKeystoreOperationInitialized();
+
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        } else if (!(key instanceof PublicKey)) {
+            throw new InvalidKeyException("Only public keys supported. Key: " + key);
+        } else if (!lastPhase) {
+            throw new IllegalStateException(
+                    "Only one other party supported. lastPhase must be set to true.");
+        } else if (mOtherPartyKey != null) {
+            throw new IllegalStateException(
+                    "Only one other party supported. doPhase() must only be called exactly once.");
+        }
+        // The other party key will be passed as part of the doFinal() call, to prevent an
+        // additional IPC.
+        mOtherPartyKey = (PublicKey) key;
+
+        return null; // No intermediate key
+    }
+
+    @Override
+    protected byte[] engineGenerateSecret() throws IllegalStateException {
+        try {
+            ensureKeystoreOperationInitialized();
+        } catch (InvalidKeyException e) {
+            throw new IllegalStateException("Not initialized", e);
+        }
+
+        if (mOtherPartyKey == null) {
+            throw new IllegalStateException("Other party key not provided. Call doPhase() first.");
+        }
+        byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded();
+
+        try {
+            return mOperation.finish(otherPartyKeyEncoded, null);
+        } catch (KeyStoreException e) {
+            throw new ProviderException("Keystore operation failed", e);
+        } finally {
+            resetWhilePreservingInitState();
+        }
+    }
+
+    @Override
+    protected SecretKey engineGenerateSecret(String algorithm)
+            throws IllegalStateException, NoSuchAlgorithmException, InvalidKeyException {
+        byte[] generatedSecret = engineGenerateSecret();
+
+        return new SecretKeySpec(generatedSecret, algorithm);
+    }
+
+    @Override
+    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
+            throws IllegalStateException, ShortBufferException {
+        byte[] generatedSecret = engineGenerateSecret();
+
+        if (generatedSecret.length > sharedSecret.length - offset) {
+            throw new ShortBufferException("Needed: " + generatedSecret.length);
+        }
+        System.arraycopy(generatedSecret, 0, sharedSecret, offset, generatedSecret.length);
+        return generatedSecret.length;
+    }
+
+    @Override
+    public long getOperationHandle() {
+        return mOperationHandle;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            resetAll();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private void resetWhilePreservingInitState() {
+        KeyStoreCryptoOperationUtils.abortOperation(mOperation);
+        mOperationHandle = 0;
+        mOperation = null;
+        mOtherPartyKey = null;
+    }
+
+    private void resetAll() {
+        resetWhilePreservingInitState();
+        mKey = null;
+    }
+
+    private void ensureKeystoreOperationInitialized()
+            throws InvalidKeyException, IllegalStateException {
+        if (mKey == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+        if (mOperation != null) {
+            return;
+        }
+
+        // We don't need to explicitly pass in any other parameters here, as they're part of the
+        // private key that is available to Keymint.
+        List<KeyParameter> parameters = new ArrayList<>();
+        parameters.add(KeyStore2ParameterUtils.makeEnum(
+                Tag.PURPOSE, KeyPurpose.AGREE_KEY
+        ));
+
+        try {
+            mOperation =
+                    mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters);
+        } catch (KeyStoreException keyStoreException) {
+            // If necessary, throw an exception due to KeyStore operation having failed.
+            InvalidKeyException e =
+                    KeyStoreCryptoOperationUtils.getInvalidKeyException(mKey, keyStoreException);
+            if (e != null) {
+                throw e;
+            }
+        }
+
+        // Set the operation handle. This will be a random number, or the operation challenge if
+        // user authentication is required. If we got a challenge we check if the authorization can
+        // possibly succeed.
+        mOperationHandle =
+                KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(mOperation, mKey);
+    }
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
index 233f352..1575bb4 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
@@ -366,6 +366,13 @@
             ));
         }
 
+        if (spec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
+            params.add(KeyStore2ParameterUtils.makeInt(
+                    KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
+                    spec.getMaxUsageCount()
+            ));
+        }
+
         byte[] additionalEntropy =
                 KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
                         mRng, (mKeySizeBits + 7) / 8);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index df0e146..6a92980 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -586,6 +586,13 @@
             ));
         }
 
+        if (mSpec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
+            params.add(KeyStore2ParameterUtils.makeInt(
+                    KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
+                    mSpec.getMaxUsageCount()
+            ));
+        }
+
         addAlgorithmSpecificParameters(params);
 
         if (mSpec.isUniqueIdIncluded()) {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 403da18..e101115 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -94,6 +94,9 @@
             put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede");
         }
 
+        // javax.crypto.KeyAgreement
+        put("KeyAgreement.ECDH", PACKAGE_NAME + ".AndroidKeyStoreKeyAgreementSpi$ECDH");
+
         // java.security.SecretKeyFactory
         putSecretKeyFactoryImpl("AES");
         if (supports3DES) {
@@ -349,17 +352,25 @@
         try {
             response = keyStore.getKeyEntry(descriptor);
         } catch (android.security.KeyStoreException e) {
-            if (e.getErrorCode() == ResponseCode.KEY_PERMANENTLY_INVALIDATED) {
-                throw new KeyPermanentlyInvalidatedException(
-                        "User changed or deleted their auth credentials",
-                        e);
-            } else {
-                throw (UnrecoverableKeyException)
-                        new UnrecoverableKeyException("Failed to obtain information about key")
-                                .initCause(e);
+            switch (e.getErrorCode()) {
+                case ResponseCode.KEY_NOT_FOUND:
+                    return null;
+                case ResponseCode.KEY_PERMANENTLY_INVALIDATED:
+                    throw new KeyPermanentlyInvalidatedException(
+                            "User changed or deleted their auth credentials",
+                            e);
+                default:
+                    throw (UnrecoverableKeyException)
+                            new UnrecoverableKeyException("Failed to obtain information about key")
+                                    .initCause(e);
             }
         }
 
+        if (response.iSecurityLevel == null) {
+            // This seems to be a pure certificate entry, nothing to return here.
+            return null;
+        }
+
         Integer keymasterAlgorithm = null;
         // We just need one digest for the algorithm name
         int keymasterDigest = -1;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java
index 49dd77e..db3e567 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java
@@ -32,13 +32,15 @@
 public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey {
     private final byte[] mCertificate;
     private final byte[] mCertificateChain;
+    private final byte[] mEncoded;
 
     public AndroidKeyStorePublicKey(@NonNull KeyDescriptor descriptor,
-            @NonNull KeyMetadata metadata, @NonNull String algorithm,
-            @NonNull KeyStoreSecurityLevel securityLevel) {
+            @NonNull KeyMetadata metadata, @NonNull byte[] x509EncodedForm,
+            @NonNull String algorithm, @NonNull KeyStoreSecurityLevel securityLevel) {
         super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel);
         mCertificate = metadata.certificate;
         mCertificateChain = metadata.certificateChain;
+        mEncoded = x509EncodedForm;
     }
 
     abstract AndroidKeyStorePrivateKey getPrivateKey();
@@ -50,7 +52,7 @@
 
     @Override
     public byte[] getEncoded() {
-        return ArrayUtils.cloneIfNotEmpty(mCertificate);
+        return ArrayUtils.cloneIfNotEmpty(mEncoded);
     }
 
     @Override
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java
index b578ea9..9fe6cf3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java
@@ -36,9 +36,11 @@
 
     public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor,
             @NonNull KeyMetadata metadata,
+            @NonNull byte[] x509EncodedForm,
             @NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus,
             @NonNull BigInteger publicExponent) {
-        super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_RSA, securityLevel);
+        super(descriptor, metadata, x509EncodedForm, KeyProperties.KEY_ALGORITHM_RSA,
+                securityLevel);
         mModulus = modulus;
         mPublicExponent = publicExponent;
     }
@@ -46,7 +48,8 @@
     public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor,
             @NonNull KeyMetadata metadata,
             @NonNull KeyStoreSecurityLevel securityLevel, @NonNull RSAPublicKey info) {
-        this(descriptor, metadata, securityLevel, info.getModulus(), info.getPublicExponent());
+        this(descriptor, metadata, info.getEncoded(), securityLevel, info.getModulus(),
+                info.getPublicExponent());
         if (!"X.509".equalsIgnoreCase(info.getFormat())) {
             throw new IllegalArgumentException(
                     "Unsupported key export format: " + info.getFormat());
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
index 74503e1..fe05989 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -95,6 +95,7 @@
         boolean userAuthenticationValidWhileOnBody = false;
         boolean trustedUserPresenceRequired = false;
         boolean trustedUserConfirmationRequired = false;
+        int remainingUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
         try {
             for (Authorization a : key.getAuthorizations()) {
                 switch (a.keyParameter.tag) {
@@ -195,6 +196,16 @@
                         trustedUserConfirmationRequired =
                                 KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
                         break;
+                    case KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT:
+                        long remainingUsageCountUnsigned =
+                                KeyStore2ParameterUtils.getUnsignedInt(a);
+                        if (remainingUsageCountUnsigned > Integer.MAX_VALUE) {
+                            throw new ProviderException(
+                                    "Usage count of limited use key too long: "
+                                     + remainingUsageCountUnsigned);
+                        }
+                        remainingUsageCount = (int) remainingUsageCountUnsigned;
+                        break;
                 }
             }
         } catch (IllegalArgumentException e) {
@@ -247,7 +258,8 @@
                 trustedUserPresenceRequired,
                 invalidatedByBiometricEnrollment,
                 trustedUserConfirmationRequired,
-                securityLevel);
+                securityLevel,
+                remainingUsageCount);
     }
 
     private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 5e7f648..8c8acc4 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -490,7 +490,7 @@
             int[] keymasterEncryptionPaddings =
                     KeyProperties.EncryptionPadding.allToKeymaster(
                             spec.getEncryptionPaddings());
-            if (((spec.getPurposes() & KeyProperties.PURPOSE_DECRYPT) != 0)
+            if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0)
                     && (spec.isRandomizedEncryptionRequired())) {
                 for (int keymasterPadding : keymasterEncryptionPaddings) {
                     if (!KeymasterUtils
@@ -535,6 +535,12 @@
                         spec.getKeyValidityForConsumptionEnd()
                 ));
             }
+            if (spec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
+                importArgs.add(KeyStore2ParameterUtils.makeInt(
+                        KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
+                        spec.getMaxUsageCount()
+                ));
+            }
         } catch (IllegalArgumentException | IllegalStateException e) {
             throw new KeyStoreException(e);
         }
@@ -772,6 +778,12 @@
                         params.getKeyValidityForConsumptionEnd()
                 ));
             }
+            if (params.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) {
+                importArgs.add(KeyStore2ParameterUtils.makeInt(
+                        KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT,
+                        params.getMaxUsageCount()
+                ));
+            }
         } catch (IllegalArgumentException | IllegalStateException e) {
             throw new KeyStoreException(e);
         }
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index 7fbbb61..4612ba2 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Sidecar
 android_library_import {
     name: "window-sidecar",
     aars: ["window-sidecar-release.aar"],
@@ -20,7 +21,7 @@
 
 java_library {
     name: "androidx.window.sidecar",
-    srcs: ["src/**/*.java"],
+    srcs: ["src/androidx/window/sidecar/**/*.java", "src/androidx/window/util/**/*.java"],
     static_libs: ["window-sidecar"],
     installable: true,
     sdk_version: "core_platform",
@@ -36,3 +37,31 @@
     src: "androidx.window.sidecar.xml",
     filename_from_src: true,
 }
+
+// Extensions
+// NOTE: This module is still under active development and must not
+// be used in production. Use 'androidx.window.sidecar' instead.
+android_library_import {
+    name: "window-extensions",
+    aars: ["window-extensions-release.aar"],
+    sdk_version: "current",
+}
+
+java_library {
+    name: "androidx.window.extensions",
+    srcs: ["src/androidx/window/extensions/**/*.java", "src/androidx/window/util/**/*.java"],
+    static_libs: ["window-extensions"],
+    installable: true,
+    sdk_version: "core_platform",
+    system_ext_specific: true,
+    libs: ["framework", "androidx.annotation_annotation",],
+    required: ["androidx.window.extensions.xml",],
+}
+
+prebuilt_etc {
+    name: "androidx.window.extensions.xml",
+    system_ext_specific: true,
+    sub_dir: "permissions",
+    src: "androidx.window.extensions.xml",
+    filename_from_src: true,
+}
diff --git a/libs/WindowManager/Jetpack/androidx.window.extensions.xml b/libs/WindowManager/Jetpack/androidx.window.extensions.xml
new file mode 100644
index 0000000..34264aa
--- /dev/null
+++ b/libs/WindowManager/Jetpack/androidx.window.extensions.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<permissions>
+    <library
+        name="androidx.window.extensions"
+        file="/system_ext/framework/androidx.window.extensions.jar"/>
+</permissions>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java
new file mode 100644
index 0000000..b7a6039
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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 androidx.window.extensions;
+
+import android.content.Context;
+
+/**
+ * Provider class that will instantiate the library implementation. It must be included in the
+ * vendor library, and the vendor implementation must match the signature of this class.
+ */
+public class ExtensionProvider {
+    /**
+     * Provides a simple implementation of {@link ExtensionInterface} that can be replaced by
+     * an OEM by overriding this method.
+     */
+    public static ExtensionInterface getExtensionImpl(Context context) {
+        return new SampleExtensionImpl(context);
+    }
+
+    /**
+     * The support library will use this method to check API version compatibility.
+     * @return API version string in MAJOR.MINOR.PATCH-description format.
+     */
+    public static String getApiVersion() {
+        return "1.0.0-settings_sample";
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
new file mode 100644
index 0000000..0bf6965
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 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 androidx.window.extensions;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
+import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.window.util.BaseDisplayFeature;
+import androidx.window.util.SettingsConfigProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reference implementation of androidx.window.extensions OEM interface for use with
+ * WindowManager Jetpack.
+ *
+ * NOTE: This version is a work in progress and under active development. It MUST NOT be used in
+ * production builds since the interface can still change before reaching stable version.
+ * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
+ */
+class SampleExtensionImpl extends StubExtension implements
+        SettingsConfigProvider.StateChangeCallback {
+    private static final String TAG = "SampleExtension";
+
+    private final SettingsConfigProvider mConfigProvider;
+
+    SampleExtensionImpl(Context context) {
+        mConfigProvider = new SettingsConfigProvider(context, this);
+    }
+
+    @Override
+    public void onDevicePostureChanged() {
+        updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState()));
+    }
+
+    @Override
+    public void onDisplayFeaturesChanged() {
+        for (Activity activity : getActivitiesListeningForLayoutChanges()) {
+            ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
+            updateWindowLayout(activity, newLayout);
+        }
+    }
+
+    @NonNull
+    private ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
+        List<ExtensionDisplayFeature> displayFeatures = getDisplayFeatures(activity);
+        return new ExtensionWindowLayoutInfo(displayFeatures);
+    }
+
+    private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
+        List<ExtensionDisplayFeature> features = new ArrayList<>();
+        int displayId = activity.getDisplayId();
+        if (displayId != DEFAULT_DISPLAY) {
+            Log.w(TAG, "This sample doesn't support display features on secondary displays");
+            return features;
+        }
+
+        if (activity.isInMultiWindowMode()) {
+            // It is recommended not to report any display features in multi-window mode, since it
+            // won't be possible to synchronize the display feature positions with window movement.
+            return features;
+        }
+
+        List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures();
+        for (BaseDisplayFeature baseFeature : storedFeatures) {
+            Rect featureRect = baseFeature.getRect();
+            rotateRectToDisplayRotation(displayId, featureRect);
+            transformToWindowSpaceRect(activity, featureRect);
+            features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
+                    baseFeature.getState()));
+        }
+        return features;
+    }
+
+    @Override
+    protected void onListenersChanged() {
+        if (hasListeners()) {
+            mConfigProvider.registerObserversIfNeeded();
+        } else {
+            mConfigProvider.unregisterObserversIfNeeded();
+        }
+
+        onDevicePostureChanged();
+        onDisplayFeaturesChanged();
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java
new file mode 100644
index 0000000..b0895ef
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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 androidx.window.extensions;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Basic implementation of the {@link ExtensionInterface}. An OEM can choose to use it as the base
+ * class for their implementation.
+ */
+abstract class StubExtension implements ExtensionInterface {
+
+    private ExtensionCallback mExtensionCallback;
+    private final Set<Activity> mWindowLayoutChangeListenerActivities = new HashSet<>();
+    private boolean mDeviceStateChangeListenerRegistered;
+
+    StubExtension() {
+    }
+
+    @Override
+    public void setExtensionCallback(@NonNull ExtensionCallback extensionCallback) {
+        this.mExtensionCallback = extensionCallback;
+    }
+
+    @Override
+    public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
+        this.mWindowLayoutChangeListenerActivities.add(activity);
+        this.onListenersChanged();
+    }
+
+    @Override
+    public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
+        this.mWindowLayoutChangeListenerActivities.remove(activity);
+        this.onListenersChanged();
+    }
+
+    @Override
+    public void onDeviceStateListenersChanged(boolean isEmpty) {
+        this.mDeviceStateChangeListenerRegistered = !isEmpty;
+        this.onListenersChanged();
+    }
+
+    void updateDeviceState(ExtensionDeviceState newState) {
+        if (this.mExtensionCallback != null) {
+            mExtensionCallback.onDeviceStateChanged(newState);
+        }
+    }
+
+    void updateWindowLayout(@NonNull Activity activity,
+            @NonNull ExtensionWindowLayoutInfo newLayout) {
+        if (this.mExtensionCallback != null) {
+            mExtensionCallback.onWindowLayoutChanged(activity, newLayout);
+        }
+    }
+
+    @NonNull
+    Set<Activity> getActivitiesListeningForLayoutChanges() {
+        return mWindowLayoutChangeListenerActivities;
+    }
+
+    protected boolean hasListeners() {
+        return !mWindowLayoutChangeListenerActivities.isEmpty()
+                || mDeviceStateChangeListenerRegistered;
+    }
+
+    protected abstract void onListenersChanged();
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
new file mode 100644
index 0000000..1094a0e
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2020 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 androidx.window.sidecar;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
+import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
+
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.window.util.BaseDisplayFeature;
+import androidx.window.util.SettingsConfigProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reference implementation of androidx.window.sidecar OEM interface for use with
+ * WindowManager Jetpack.
+ */
+class SampleSidecarImpl extends StubSidecar implements
+        SettingsConfigProvider.StateChangeCallback {
+    private static final String TAG = "SampleSidecar";
+
+    private final SettingsConfigProvider mConfigProvider;
+
+    SampleSidecarImpl(Context context) {
+        mConfigProvider = new SettingsConfigProvider(context, this);
+    }
+
+    @Override
+    public void onDevicePostureChanged() {
+        updateDeviceState(getDeviceState());
+    }
+
+    @Override
+    public void onDisplayFeaturesChanged() {
+        for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
+            SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
+            updateWindowLayout(windowToken, newLayout);
+        }
+    }
+
+    @NonNull
+    @Override
+    public SidecarDeviceState getDeviceState() {
+        SidecarDeviceState deviceState = new SidecarDeviceState();
+        deviceState.posture = mConfigProvider.getDeviceState();
+        return deviceState;
+    }
+
+    @NonNull
+    @Override
+    public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
+        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
+        SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
+        if (activity == null) {
+            return windowLayoutInfo;
+        }
+        windowLayoutInfo.displayFeatures = getDisplayFeatures(activity);
+        return windowLayoutInfo;
+    }
+
+    private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
+        List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
+        int displayId = activity.getDisplayId();
+        if (displayId != DEFAULT_DISPLAY) {
+            Log.w(TAG, "This sample doesn't support display features on secondary displays");
+            return features;
+        }
+
+        if (activity.isInMultiWindowMode()) {
+            // It is recommended not to report any display features in multi-window mode, since it
+            // won't be possible to synchronize the display feature positions with window movement.
+            return features;
+        }
+
+        List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures();
+        for (BaseDisplayFeature baseFeature : storedFeatures) {
+            SidecarDisplayFeature feature = new SidecarDisplayFeature();
+            Rect featureRect = baseFeature.getRect();
+            rotateRectToDisplayRotation(displayId, featureRect);
+            transformToWindowSpaceRect(activity, featureRect);
+            feature.setRect(featureRect);
+            feature.setType(baseFeature.getType());
+            features.add(feature);
+        }
+        return features;
+    }
+
+    @Override
+    protected void onListenersChanged() {
+        if (hasListeners()) {
+            mConfigProvider.registerObserversIfNeeded();
+        } else {
+            mConfigProvider.unregisterObserversIfNeeded();
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java
deleted file mode 100644
index 5397302..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2020 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 androidx.window.sidecar;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static androidx.window.sidecar.SidecarHelper.getWindowDisplay;
-import static androidx.window.sidecar.SidecarHelper.isInMultiWindow;
-import static androidx.window.sidecar.SidecarHelper.rotateRectToDisplayRotation;
-import static androidx.window.sidecar.SidecarHelper.transformToWindowSpaceRect;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-class SettingsSidecarImpl extends StubSidecar {
-    private static final String TAG = "SettingsSidecar";
-
-    private static final String DEVICE_POSTURE = "device_posture";
-    private static final String DISPLAY_FEATURES = "display_features";
-
-    private static final Pattern FEATURE_PATTERN =
-            Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]");
-
-    private static final String FEATURE_TYPE_FOLD = "fold";
-    private static final String FEATURE_TYPE_HINGE = "hinge";
-
-    private Context mContext;
-    private SettingsObserver mSettingsObserver;
-
-    final class SettingsObserver extends ContentObserver {
-        private final Uri mDevicePostureUri =
-                Settings.Global.getUriFor(DEVICE_POSTURE);
-        private final Uri mDisplayFeaturesUri =
-                Settings.Global.getUriFor(DISPLAY_FEATURES);
-        private final ContentResolver mResolver = mContext.getContentResolver();
-        private boolean mRegisteredObservers;
-
-
-        private SettingsObserver() {
-            super(new Handler(Looper.getMainLooper()));
-        }
-
-        private void registerObserversIfNeeded() {
-            if (mRegisteredObservers) {
-                return;
-            }
-            mRegisteredObservers = true;
-            mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendents */,
-                    this /* ContentObserver */);
-            mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendents */,
-                    this /* ContentObserver */);
-        }
-
-        private void unregisterObserversIfNeeded() {
-            if (!mRegisteredObservers) {
-                return;
-            }
-            mRegisteredObservers = false;
-            mResolver.unregisterContentObserver(this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (uri == null) {
-                return;
-            }
-
-            if (mDevicePostureUri.equals(uri)) {
-                updateDevicePosture();
-                return;
-            }
-            if (mDisplayFeaturesUri.equals(uri)) {
-                updateDisplayFeatures();
-                return;
-            }
-        }
-    }
-
-    SettingsSidecarImpl(Context context) {
-        mContext = context;
-        mSettingsObserver = new SettingsObserver();
-    }
-
-    private void updateDevicePosture() {
-        updateDeviceState(getDeviceState());
-    }
-
-    /** Update display features with values read from settings. */
-    private void updateDisplayFeatures() {
-        for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
-            SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
-            updateWindowLayout(windowToken, newLayout);
-        }
-    }
-
-    @NonNull
-    @Override
-    public SidecarDeviceState getDeviceState() {
-        ContentResolver resolver = mContext.getContentResolver();
-        int posture = Settings.Global.getInt(resolver, DEVICE_POSTURE,
-                SidecarDeviceState.POSTURE_UNKNOWN);
-        SidecarDeviceState deviceState = new SidecarDeviceState();
-        deviceState.posture = posture;
-        return deviceState;
-    }
-
-    @NonNull
-    @Override
-    public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
-        List<SidecarDisplayFeature> displayFeatures = readDisplayFeatures(windowToken);
-        SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
-        windowLayoutInfo.displayFeatures = displayFeatures;
-        return windowLayoutInfo;
-    }
-
-    private List<SidecarDisplayFeature> readDisplayFeatures(IBinder windowToken) {
-        List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
-        int displayId = getWindowDisplay(windowToken);
-        if (displayId != DEFAULT_DISPLAY) {
-            Log.w(TAG, "This sample doesn't support display features on secondary displays");
-            return features;
-        }
-
-        if (isInMultiWindow(windowToken)) {
-            // It is recommended not to report any display features in multi-window mode, since it
-            // won't be possible to synchronize the display feature positions with window movement.
-            return features;
-        }
-
-        ContentResolver resolver = mContext.getContentResolver();
-        String displayFeaturesString = Settings.Global.getString(resolver, DISPLAY_FEATURES);
-        if (TextUtils.isEmpty(displayFeaturesString)) {
-            displayFeaturesString = mContext.getResources().getString(
-                    R.string.config_display_features);
-        }
-        if (TextUtils.isEmpty(displayFeaturesString)) {
-            return features;
-        }
-
-        String[] featureStrings = displayFeaturesString.split(";");
-        for (String featureString : featureStrings) {
-            Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString);
-            if (!featureMatcher.matches()) {
-                Log.e(TAG, "Malformed feature description format: " + featureString);
-                continue;
-            }
-            try {
-                String featureType = featureMatcher.group(1);
-                int type;
-                switch (featureType) {
-                    case FEATURE_TYPE_FOLD:
-                        type = SidecarDisplayFeature.TYPE_FOLD;
-                        break;
-                    case FEATURE_TYPE_HINGE:
-                        type = SidecarDisplayFeature.TYPE_HINGE;
-                        break;
-                    default: {
-                        Log.e(TAG, "Malformed feature type: " + featureType);
-                        continue;
-                    }
-                }
-
-                int left = Integer.parseInt(featureMatcher.group(2));
-                int top = Integer.parseInt(featureMatcher.group(3));
-                int right = Integer.parseInt(featureMatcher.group(4));
-                int bottom = Integer.parseInt(featureMatcher.group(5));
-                Rect featureRect = new Rect(left, top, right, bottom);
-                rotateRectToDisplayRotation(featureRect, displayId);
-                transformToWindowSpaceRect(featureRect, windowToken);
-                if (isNotZero(featureRect)) {
-                    SidecarDisplayFeature feature = new SidecarDisplayFeature();
-                    feature.setRect(featureRect);
-                    feature.setType(type);
-                    features.add(feature);
-                } else {
-                    Log.w(TAG, "Failed to adjust feature to window");
-                }
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Malformed feature description: " + featureString);
-            }
-        }
-        return features;
-    }
-
-    private static boolean isNotZero(Rect rect) {
-        return rect.height() > 0 || rect.width() > 0;
-    }
-
-    @Override
-    protected void onListenersChanged() {
-        if (mSettingsObserver == null) {
-            return;
-        }
-
-        if (hasListeners()) {
-            mSettingsObserver.registerObserversIfNeeded();
-        } else {
-            mSettingsObserver.unregisterObserversIfNeeded();
-        }
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
index 0b4915e..e6f8388 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
@@ -28,7 +28,7 @@
      * an OEM by overriding this method.
      */
     public static SidecarInterface getSidecarImpl(Context context) {
-        return new SettingsSidecarImpl(context);
+        return new SampleSidecarImpl(context);
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java
new file mode 100644
index 0000000..b74a2a4
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 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 androidx.window.util;
+
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+
+/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
+public class BaseDisplayFeature {
+    private final int mType;
+    private final int mState;
+    @NonNull
+    public final Rect mRect;
+
+    public BaseDisplayFeature(int type, int state, @NonNull Rect rect) {
+        this.mType = type;
+        this.mState = state;
+        if (rect.width() == 0 && rect.height() == 0) {
+            throw new IllegalArgumentException(
+                    "Display feature rectangle cannot have zero width and height simultaneously.");
+        }
+        this.mRect = rect;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public int getState() {
+        return mState;
+    }
+
+    @NonNull
+    public Rect getRect() {
+        return mRect;
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
similarity index 62%
rename from libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
rename to libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index e5b6cff..2a593f1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,30 +14,36 @@
  * limitations under the License.
  */
 
-package androidx.window.sidecar;
+package androidx.window.util;
 
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
 import android.app.Activity;
-import android.app.ActivityThread;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
-import android.os.IBinder;
 import android.view.DisplayInfo;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-class SidecarHelper {
+/**
+ * Util class for both Sidecar and Extensions.
+ */
+public final class ExtensionHelper {
+
+    private ExtensionHelper() {
+        // Util class, no instances should be created.
+    }
+
     /**
-     * Rotate the input rectangle specified in default display orientation to the current display
+     * Rotates the input rectangle specified in default display orientation to the current display
      * rotation.
      */
-    static void rotateRectToDisplayRotation(Rect inOutRect, int displayId) {
+    public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) {
         DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
         DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
         int rotation = displayInfo.rotation;
@@ -52,7 +58,7 @@
     }
 
     /**
-     * Rotate the input rectangle within parent bounds for a given delta.
+     * Rotates the input rectangle within parent bounds for a given delta.
      */
     private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight,
             @Surface.Rotation int delta) {
@@ -79,9 +85,9 @@
         }
     }
 
-    /** Transform rectangle from absolute coordinate space to the window coordinate space. */
-    static void transformToWindowSpaceRect(Rect inOutRect, IBinder windowToken) {
-        Rect windowRect = getWindowBounds(windowToken);
+    /** Transforms rectangle from absolute coordinate space to the window coordinate space. */
+    public static void transformToWindowSpaceRect(Activity activity, Rect inOutRect) {
+        Rect windowRect = getWindowBounds(activity);
         if (windowRect == null) {
             inOutRect.setEmpty();
             return;
@@ -95,32 +101,17 @@
     }
 
     /**
-     * Get the current window bounds in absolute coordinates.
-     * NOTE: Only works with Activity windows.
+     * Gets the current window bounds in absolute coordinates.
      */
     @Nullable
-    private static Rect getWindowBounds(IBinder windowToken) {
-        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
-        return activity != null
-                ? activity.getWindowManager().getCurrentWindowMetrics().getBounds()
-                : null;
+    private static Rect getWindowBounds(@NonNull Activity activity) {
+        return activity.getWindowManager().getCurrentWindowMetrics().getBounds();
     }
 
     /**
-     * Check if this window is an Activity window that is in multi-window mode.
+     * Checks if both dimensions of the given rect are zero at the same time.
      */
-    static boolean isInMultiWindow(IBinder windowToken) {
-        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
-        return activity != null && activity.isInMultiWindowMode();
-    }
-
-    /**
-     * Get the id of the parent display for the window.
-     * NOTE: Only works with Activity windows.
-     */
-    static int getWindowDisplay(IBinder windowToken) {
-        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
-        return activity != null
-                ? activity.getWindowManager().getDefaultDisplay().getDisplayId() : INVALID_DISPLAY;
+    public static boolean isZero(@NonNull Rect rect) {
+        return rect.height() == 0 && rect.width() == 0;
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java
new file mode 100644
index 0000000..6dd190c
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2021 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 androidx.window.util;
+
+import static androidx.window.util.ExtensionHelper.isZero;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Device and display feature state provider that uses Settings as the source.
+ */
+public final class SettingsConfigProvider extends ContentObserver {
+    private static final String TAG = "SettingsConfigProvider";
+    private static final String DEVICE_POSTURE = "device_posture";
+    private static final String DISPLAY_FEATURES = "display_features";
+
+    private static final Pattern FEATURE_PATTERN =
+            Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]");
+
+    private static final String FEATURE_TYPE_FOLD = "fold";
+    private static final String FEATURE_TYPE_HINGE = "hinge";
+
+    private final Uri mDevicePostureUri =
+            Settings.Global.getUriFor(DEVICE_POSTURE);
+    private final Uri mDisplayFeaturesUri =
+            Settings.Global.getUriFor(DISPLAY_FEATURES);
+    private final Context mContext;
+    private final ContentResolver mResolver;
+    private final StateChangeCallback mCallback;
+    private boolean mRegisteredObservers;
+
+    public SettingsConfigProvider(@NonNull Context context, @NonNull StateChangeCallback callback) {
+        super(new Handler(Looper.getMainLooper()));
+        mContext = context;
+        mResolver = context.getContentResolver();
+        mCallback = callback;
+    }
+
+    /**
+     * Registers the content observers for Settings keys that store device state and display feature
+     * configurations.
+     */
+    public void registerObserversIfNeeded() {
+        if (mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = true;
+        mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */,
+                this /* ContentObserver */);
+        mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
+                this /* ContentObserver */);
+    }
+
+    /**
+     * Unregisters the content observers that are tracking the state changes.
+     * @see #registerObserversIfNeeded()
+     */
+    public void unregisterObserversIfNeeded() {
+        if (!mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = false;
+        mResolver.unregisterContentObserver(this);
+    }
+
+    /**
+     * Gets the device posture int stored in Settings.
+     */
+    public int getDeviceState() {
+        return Settings.Global.getInt(mResolver, DEVICE_POSTURE,
+                0 /* POSTURE_UNKNOWN */);
+    }
+
+    /**
+     * Gets the list of all display feature configs stored in Settings. Uses a custom
+     * {@link BaseDisplayFeature} class to report the config to be translated for actual
+     * containers in Sidecar or Extensions.
+     */
+    public List<BaseDisplayFeature> getDisplayFeatures() {
+        List<BaseDisplayFeature> features = new ArrayList<>();
+        String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
+        if (TextUtils.isEmpty(displayFeaturesString)) {
+            displayFeaturesString = mContext.getResources().getString(
+                    R.string.config_display_features);
+        }
+        if (TextUtils.isEmpty(displayFeaturesString)) {
+            return features;
+        }
+        String[] featureStrings =  displayFeaturesString.split(";");
+
+        int deviceState = getDeviceState();
+
+        for (String featureString : featureStrings) {
+            Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString);
+            if (!featureMatcher.matches()) {
+                Log.e(TAG, "Malformed feature description format: " + featureString);
+                continue;
+            }
+            try {
+                String featureType = featureMatcher.group(1);
+                int type;
+                switch (featureType) {
+                    case FEATURE_TYPE_FOLD:
+                        type = 1 /* TYPE_FOLD */;
+                        break;
+                    case FEATURE_TYPE_HINGE:
+                        type = 2 /* TYPE_HINGE */;
+                        break;
+                    default: {
+                        Log.e(TAG, "Malformed feature type: " + featureType);
+                        continue;
+                    }
+                }
+
+                int left = Integer.parseInt(featureMatcher.group(2));
+                int top = Integer.parseInt(featureMatcher.group(3));
+                int right = Integer.parseInt(featureMatcher.group(4));
+                int bottom = Integer.parseInt(featureMatcher.group(5));
+                Rect featureRect = new Rect(left, top, right, bottom);
+                if (!isZero(featureRect)) {
+                    BaseDisplayFeature feature = new BaseDisplayFeature(type, deviceState,
+                            featureRect);
+                    features.add(feature);
+                } else {
+                    Log.w(TAG, "Read empty feature");
+                }
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "Malformed feature description: " + featureString);
+            }
+        }
+        return features;
+    }
+
+    @Override
+    public void onChange(boolean selfChange, Uri uri) {
+        if (uri == null) {
+            return;
+        }
+
+        if (mDevicePostureUri.equals(uri)) {
+            mCallback.onDevicePostureChanged();
+            mCallback.onDisplayFeaturesChanged();
+            return;
+        }
+        if (mDisplayFeaturesUri.equals(uri)) {
+            mCallback.onDisplayFeaturesChanged();
+        }
+    }
+
+    /**
+     * Callback that notifies about device or display feature state changes.
+     */
+    public interface StateChangeCallback {
+        /**
+         * Notifies about the device state update.
+         */
+        void onDevicePostureChanged();
+
+        /**
+         * Notifies about the display feature config update.
+         */
+        void onDisplayFeaturesChanged();
+    }
+}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
new file mode 100644
index 0000000..7b306b0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/packages/SystemUI/res/drawable/btn_restart.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/btn_restart.xml
rename to libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
diff --git a/libs/WindowManager/Shell/res/layout/pip_menu.xml b/libs/WindowManager/Shell/res/layout/pip_menu.xml
index 2e0a5e0..93a6e7b 100644
--- a/libs/WindowManager/Shell/res/layout/pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/pip_menu.xml
@@ -36,8 +36,8 @@
             android:layout_height="match_parent">
             <ImageButton
                 android:id="@+id/expand_button"
-                android:layout_width="60dp"
-                android:layout_height="60dp"
+                android:layout_width="@dimen/pip_expand_action_size"
+                android:layout_height="@dimen/pip_expand_action_size"
                 android:layout_gravity="center"
                 android:contentDescription="@string/pip_phone_expand"
                 android:padding="10dp"
@@ -97,4 +97,14 @@
         android:padding="@dimen/pip_resize_handle_padding"
         android:src="@drawable/pip_resize_handle"
         android:background="?android:selectableItemBackgroundBorderless" />
-</FrameLayout>
+
+    <!-- invisible layer to trap the focus, b/169372603 -->
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@null"
+        android:defaultFocusHighlightEnabled="false"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        android:focusedByDefault="true" />
+t </FrameLayout>
diff --git a/packages/SystemUI/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
similarity index 100%
rename from packages/SystemUI/res/layout/size_compat_mode_hint.xml
rename to libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index 2cfb13e..9c3d84e 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -13,6 +13,12 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
     },
+    "-1671119352": {
+      "message": " Delegate animation for %s to %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
+    },
     "-1501874464": {
       "message": "Fullscreen Task Appeared: #%d",
       "level": "VERBOSE",
@@ -49,6 +55,24 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
     },
+    "-1308483871": {
+      "message": " try handler %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
+    },
+    "-1297259344": {
+      "message": " animated by %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
+    },
+    "-1269886472": {
+      "message": "Transition %s doesn't have explicit remote, search filters for match for %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
+    },
     "-1006733970": {
       "message": "Display added: %d",
       "level": "VERBOSE",
@@ -91,12 +115,24 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/apppairs\/AppPairsController.java"
     },
+    "138343607": {
+      "message": " try firstHandler %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
+    },
     "157713005": {
       "message": "Task info changed taskId=%d",
       "level": "VERBOSE",
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
     },
+    "214412327": {
+      "message": "RemoteTransition directly requested for %s: %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
+    },
     "274140888": {
       "message": "Animate alpha: from=%d to=%d",
       "level": "VERBOSE",
@@ -115,6 +151,12 @@
       "group": "WM_SHELL_DRAG_AND_DROP",
       "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
     },
+    "410592459": {
+      "message": "Invalid root leash (%s): %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
+    },
     "473543554": {
       "message": "%s onTaskAppeared Supported",
       "level": "VERBOSE",
@@ -139,6 +181,12 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java"
     },
+    "707170340": {
+      "message": " animated by firstHandler",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/Transitions.java"
+    },
     "900599280": {
       "message": "Can't pair unresizeable tasks task1.isResizeable=%b task1.isResizeable=%b",
       "level": "ERROR",
@@ -163,6 +211,12 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/legacysplitscreen\/LegacySplitScreenTaskListener.java"
     },
+    "990371881": {
+      "message": " Checking filter %s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_TRANSITIONS",
+      "at": "com\/android\/wm\/shell\/transition\/RemoteTransitionHandler.java"
+    },
     "1070270131": {
       "message": "onTransitionReady %s: %s",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index e25a05c..034e65c 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -26,6 +26,9 @@
     <!-- The height of the PiP actions container in which the actions are vertically centered. -->
     <dimen name="pip_action_size">48dp</dimen>
 
+    <!-- The width and height of the PiP expand action. -->
+    <dimen name="pip_expand_action_size">60dp</dimen>
+
     <!-- The padding between actions in the PiP in landscape  Note that the PiP does not reflect
          the configuration of the device, so we can't use -land resources. -->
     <dimen name="pip_between_action_padding_land">8dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 30ef72c..b1425e4 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -146,4 +146,10 @@
 
     <!-- Content description to tell the user a bubble has been dismissed. -->
     <string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
+
+    <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
+    <string name="restart_button_description">Tap to restart this app and go full screen.</string>
+
+    <!-- Generic "got it" acceptance of dialog or cling [CHAR LIMIT=NONE] -->
+    <string name="got_it">Got it</string>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
index 4b3fc81..afe523a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
@@ -106,5 +106,4 @@
     public String toString() {
         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN);
     }
-
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index 52648d9..fe97e24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell;
 
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
 import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.common.ShellExecutor;
@@ -155,7 +155,7 @@
         }
         final int taskId = new Integer(args[2]);
         final int sideStagePosition = args.length > 3
-                ? new Integer(args[3]) : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+                ? new Integer(args[3]) : STAGE_POSITION_BOTTOM_OR_RIGHT;
         mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition));
         return true;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index c9b38d0..a570c0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -44,6 +44,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
 
 import java.io.PrintWriter;
@@ -102,18 +103,31 @@
     private final Object mLock = new Object();
     private final StartingSurfaceDrawer mStartingSurfaceDrawer;
 
+    /**
+     * In charge of showing size compat UI. Can be {@code null} if device doesn't support size
+     * compat.
+     */
+    @Nullable
+    private final SizeCompatUI mSizeCompatUI;
+
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
-        this(null, mainExecutor, context);
+        this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */);
+    }
+
+    public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
+            SizeCompatUI sizeCompatUI) {
+        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI);
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context) {
+            Context context, @Nullable SizeCompatUI sizeCompatUI) {
         super(taskOrganizerController, mainExecutor);
         // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
         //  by a controller, that class should be create while porting
         //  ActivityRecord#addStartingWindow to WMShell.
         mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, mainExecutor);
+        mSizeCompatUI = sizeCompatUI;
     }
 
     @Override
@@ -255,6 +269,7 @@
         if (listener != null) {
             listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
         }
+        notifySizeCompatUI(info.getTaskInfo(), listener);
     }
 
     @Override
@@ -270,6 +285,10 @@
             if (!updated && newListener != null) {
                 newListener.onTaskInfoChanged(taskInfo);
             }
+            if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
+                // Notify the size compat UI if the listener or task info changed.
+                notifySizeCompatUI(taskInfo, newListener);
+            }
         }
     }
 
@@ -294,6 +313,8 @@
             if (listener != null) {
                 listener.onTaskVanished(taskInfo);
             }
+            // Pass null for listener to remove the size compat UI on this task if there is any.
+            notifySizeCompatUI(taskInfo, null /* taskListener */);
         }
     }
 
@@ -320,6 +341,34 @@
         return true;
     }
 
+    /**
+     * Notifies {@link SizeCompatUI} about the size compat info changed on the give Task to update
+     * the UI accordingly.
+     *
+     * @param taskInfo the new Task info
+     * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
+     *                     vanished.
+     */
+    private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
+        if (mSizeCompatUI == null) {
+            return;
+        }
+
+        // The task is vanished, notify to remove size compat UI on this Task if there is any.
+        if (taskListener == null) {
+            mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+                    null /* taskConfig */, null /* sizeCompatActivity*/,
+                    null /* taskListener */);
+            return;
+        }
+
+        mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+                taskInfo.configuration.windowConfiguration.getBounds(),
+                // null if the top activity not in size compat.
+                taskInfo.topActivityInSizeCompat ? taskInfo.topActivityToken : null,
+                taskListener);
+    }
+
     private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) {
         return getTaskListener(runningTaskInfo, false /*removeLaunchCookieIfNeeded*/);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 1df2a4a..bb8a973 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -225,8 +225,9 @@
         mTaskOrganizer.applyTransaction(wct);
         // TODO(b/151449487): Only call callback once we enable synchronization
         if (mListener != null) {
+            final int taskId = mTaskInfo.taskId;
             mListenerExecutor.execute(() -> {
-                mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
+                mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated);
             });
         }
     }
@@ -256,8 +257,10 @@
         }
 
         if (mListener != null) {
+            final int taskId = taskInfo.taskId;
+            final ComponentName baseActivity = taskInfo.baseActivity;
             mListenerExecutor.execute(() -> {
-                mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
+                mListener.onTaskCreated(taskId, baseActivity);
             });
         }
     }
@@ -267,8 +270,9 @@
         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
 
         if (mListener != null) {
+            final int taskId = taskInfo.taskId;
             mListenerExecutor.execute(() -> {
-                mListener.onTaskRemovalStarted(taskInfo.taskId);
+                mListener.onTaskRemovalStarted(taskId);
             });
         }
         mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
@@ -289,8 +293,9 @@
     public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
         if (mListener != null) {
+            final int taskId = taskInfo.taskId;
             mListenerExecutor.execute(() -> {
-                mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
+                mListener.onBackPressedOnTaskRoot(taskId);
             });
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index 5cd660a..255e4d2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.animation
 
-import android.os.Looper
 import android.util.ArrayMap
 import android.util.Log
 import android.view.View
@@ -472,11 +471,6 @@
      * animator is under test.
      */
     internal fun startInternal() {
-        if (!Looper.getMainLooper().isCurrentThread) {
-            Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
-                    "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
-                    "your test setup.")
-        }
         val target = weakTarget.get()
         if (target == null) {
             Log.w(TAG, "Trying to animate a GC-ed object.")
@@ -852,7 +846,7 @@
      * pass to [spring].
      */
     data class SpringConfig internal constructor(
-        internal var stiffness: Float,
+        var stiffness: Float,
         internal var dampingRatio: Float,
         internal var startVelocity: Float = 0f,
         internal var finalPosition: Float = UNSET
@@ -884,8 +878,8 @@
      */
     data class FlingConfig internal constructor(
         internal var friction: Float,
-        internal var min: Float,
-        internal var max: Float,
+        var min: Float,
+        var max: Float,
         internal var startVelocity: Float
     ) {
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 122f917..40fdb97 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -39,6 +39,7 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -49,6 +50,7 @@
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Encapsulates the data and UI elements of a bubble.
@@ -58,6 +60,9 @@
     private static final String TAG = "Bubble";
 
     private final String mKey;
+    @Nullable
+    private final String mGroupKey;
+    private final Executor mMainExecutor;
 
     private long mLastUpdated;
     private long mLastAccessed;
@@ -156,12 +161,14 @@
      * Note: Currently this is only being used when the bubble is persisted to disk.
      */
     Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
-            final int desiredHeight, final int desiredHeightResId, @Nullable final String title) {
+            final int desiredHeight, final int desiredHeightResId, @Nullable final String title,
+            Executor mainExecutor) {
         Objects.requireNonNull(key);
         Objects.requireNonNull(shortcutInfo);
         mMetadataShortcutId = shortcutInfo.getId();
         mShortcutInfo = shortcutInfo;
         mKey = key;
+        mGroupKey = null;
         mFlags = 0;
         mUser = shortcutInfo.getUserHandle();
         mPackageName = shortcutInfo.getPackage();
@@ -170,20 +177,26 @@
         mDesiredHeightResId = desiredHeightResId;
         mTitle = title;
         mShowBubbleUpdateDot = false;
+        mMainExecutor = mainExecutor;
     }
 
     @VisibleForTesting(visibility = PRIVATE)
     Bubble(@NonNull final BubbleEntry entry,
             @Nullable final Bubbles.NotificationSuppressionChangedListener listener,
-            final Bubbles.PendingIntentCanceledListener intentCancelListener) {
+            final Bubbles.PendingIntentCanceledListener intentCancelListener,
+            Executor mainExecutor) {
         mKey = entry.getKey();
+        mGroupKey = entry.getGroupKey();
         mSuppressionListener = listener;
         mIntentCancelListener = intent -> {
             if (mIntent != null) {
                 mIntent.unregisterCancelListener(mIntentCancelListener);
             }
-            intentCancelListener.onPendingIntentCanceled(this);
+            mainExecutor.execute(() -> {
+                intentCancelListener.onPendingIntentCanceled(this);
+            });
         };
+        mMainExecutor = mainExecutor;
         setEntry(entry);
     }
 
@@ -192,6 +205,14 @@
         return mKey;
     }
 
+    /**
+     * @see StatusBarNotification#getGroupKey()
+     * @return the group key for this bubble, if one exists.
+     */
+    public String getGroupKey() {
+        return mGroupKey;
+    }
+
     public UserHandle getUser() {
         return mUser;
     }
@@ -329,7 +350,8 @@
                 stackView,
                 iconFactory,
                 skipInflation,
-                callback);
+                callback,
+                mMainExecutor);
         if (mInflateSynchronously) {
             mInflationTask.onPostExecute(mInflationTask.doInBackground());
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 9419b9c..d73fc6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -28,6 +28,16 @@
 import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_LEFT;
 import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_NONE;
 import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_RIGHT;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_AGED;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_GROUP_CANCELLED;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_INVALID_INTENT;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NOTIF_CANCEL;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_BUBBLE_UP;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
+import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -45,6 +55,8 @@
 import android.graphics.PointF;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -53,6 +65,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.SparseSetArray;
 import android.view.View;
 import android.view.ViewGroup;
@@ -75,6 +88,9 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -83,7 +99,7 @@
  *
  * The controller manages addition, removal, and visible state of bubbles on screen.
  */
-public class BubbleController implements Bubbles {
+public class BubbleController {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
@@ -101,7 +117,8 @@
     public static final String BOTTOM_POSITION = "Bottom";
 
     private final Context mContext;
-    private BubbleExpandListener mExpandListener;
+    private final BubblesImpl mImpl = new BubblesImpl();
+    private Bubbles.BubbleExpandListener mExpandListener;
     @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
     private final FloatingContentCoordinator mFloatingContentCoordinator;
     private final BubbleDataRepository mDataRepository;
@@ -111,7 +128,7 @@
     @Nullable private BubbleStackView mStackView;
     private BubbleIconFactory mBubbleIconFactory;
     private BubblePositioner mBubblePositioner;
-    private SysuiProxy mSysuiProxy;
+    private Bubbles.SysuiProxy mSysuiProxy;
 
     // Tracks the id of the current (foreground) user.
     private int mCurrentUserId;
@@ -177,7 +194,7 @@
     /**
      * Injected constructor.
      */
-    public static BubbleController create(Context context,
+    public static Bubbles create(Context context,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
             @Nullable IStatusBarService statusBarService,
@@ -186,14 +203,15 @@
             LauncherApps launcherApps,
             UiEventLogger uiEventLogger,
             ShellTaskOrganizer organizer,
-            ShellExecutor mainExecutor) {
+            ShellExecutor mainExecutor,
+            Handler mainHandler) {
         BubbleLogger logger = new BubbleLogger(uiEventLogger);
         BubblePositioner positioner = new BubblePositioner(context, windowManager);
-        BubbleData data = new BubbleData(context, logger, positioner);
+        BubbleData data = new BubbleData(context, logger, positioner, mainExecutor);
         return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
-                new BubbleDataRepository(context, launcherApps),
+                new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                logger, organizer, positioner, mainExecutor);
+                logger, organizer, positioner, mainExecutor, mainHandler).mImpl;
     }
 
     /**
@@ -212,7 +230,8 @@
             BubbleLogger bubbleLogger,
             ShellTaskOrganizer organizer,
             BubblePositioner positioner,
-            ShellExecutor mainExecutor) {
+            ShellExecutor mainExecutor,
+            Handler mainHandler) {
         mContext = context;
         mFloatingContentCoordinator = floatingContentCoordinator;
         mDataRepository = dataRepository;
@@ -299,7 +318,12 @@
                 mBubbleData.removeBubblesWithInvalidShortcuts(
                         packageName, validShortcuts, DISMISS_SHORTCUT_REMOVED);
             }
-        });
+        }, mainHandler);
+    }
+
+    @VisibleForTesting
+    public Bubbles getImpl() {
+        return mImpl;
     }
 
     /**
@@ -313,8 +337,7 @@
         }
     }
 
-    @Override
-    public void openBubbleOverflow() {
+    private void openBubbleOverflow() {
         ensureStackViewCreated();
         mBubbleData.setShowingOverflow(true);
         mBubbleData.setSelectedBubble(mBubbleData.getOverflow());
@@ -322,8 +345,7 @@
     }
 
     /** Called when any taskbar state changes (e.g. visibility, position, sizes). */
-    @Override
-    public void onTaskbarChanged(Bundle b) {
+    private void onTaskbarChanged(Bundle b) {
         if (b == null) {
             return;
         }
@@ -371,8 +393,7 @@
      * Called when the status bar has become visible or invisible (either permanently or
      * temporarily).
      */
-    @Override
-    public void onStatusBarVisibilityChanged(boolean visible) {
+    private void onStatusBarVisibilityChanged(boolean visible) {
         if (mStackView != null) {
             // Hide the stack temporarily if the status bar has been made invisible, and the stack
             // is collapsed. An expanded stack should remain visible until collapsed.
@@ -380,15 +401,13 @@
         }
     }
 
-    @Override
-    public void onZenStateChanged() {
+    private void onZenStateChanged() {
         for (Bubble b : mBubbleData.getBubbles()) {
             b.setShowDot(b.showInShade());
         }
     }
 
-    @Override
-    public void onStatusBarStateChanged(boolean isShade) {
+    private void onStatusBarStateChanged(boolean isShade) {
         mIsStatusBarShade = isShade;
         if (!mIsStatusBarShade) {
             collapseStack();
@@ -402,8 +421,7 @@
         updateStack();
     }
 
-    @Override
-    public void onUserChanged(int newUserId) {
+    private void onUserChanged(int newUserId) {
         saveBubbles(mCurrentUserId);
         mBubbleData.dismissAll(DISMISS_USER_CHANGED);
         restoreBubbles(newUserId);
@@ -442,7 +460,7 @@
         return mBubblePositioner;
     }
 
-    SysuiProxy getSysuiProxy() {
+    Bubbles.SysuiProxy getSysuiProxy() {
         return mSysuiProxy;
     }
 
@@ -453,7 +471,8 @@
     private void ensureStackViewCreated() {
         if (mStackView == null) {
             mStackView = new BubbleStackView(
-                    mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator);
+                    mContext, this, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
+                    mMainExecutor);
             mStackView.onOrientationChanged();
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
@@ -576,8 +595,7 @@
         mSavedBubbleKeysPerUser.remove(mCurrentUserId);
     }
 
-    @Override
-    public void updateForThemeChanges() {
+    private void updateForThemeChanges() {
         if (mStackView != null) {
             mStackView.onThemeChanged();
         }
@@ -593,8 +611,7 @@
         }
     }
 
-    @Override
-    public void onConfigChanged(Configuration newConfig) {
+    private void onConfigChanged(Configuration newConfig) {
         if (mBubblePositioner != null) {
             // This doesn't trigger any changes, always update it
             mBubblePositioner.update(newConfig.orientation);
@@ -620,18 +637,19 @@
         }
     }
 
-    @Override
-    public void setBubbleScrim(View view) {
+    private void setBubbleScrim(View view, BiConsumer<Executor, Looper> callback) {
         mBubbleScrim = view;
+        callback.accept(mMainExecutor, mMainExecutor.executeBlockingForResult(() -> {
+            return Looper.myLooper();
+        }, Looper.class));
     }
 
-    @Override
-    public void setSysuiProxy(SysuiProxy proxy) {
+    private void setSysuiProxy(Bubbles.SysuiProxy proxy) {
         mSysuiProxy = proxy;
     }
 
-    @Override
-    public void setExpandListener(BubbleExpandListener listener) {
+    @VisibleForTesting
+    public void setExpandListener(Bubbles.BubbleExpandListener listener) {
         mExpandListener = ((isExpanding, key) -> {
             if (listener != null) {
                 listener.onBubbleExpandChanged(isExpanding, key);
@@ -654,17 +672,17 @@
         return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow();
     }
 
-    @Override
+    @VisibleForTesting
     public boolean isStackExpanded() {
         return mBubbleData.isExpanded();
     }
 
-    @Override
+    @VisibleForTesting
     public void collapseStack() {
         mBubbleData.setExpanded(false /* expanded */);
     }
 
-    @Override
+    @VisibleForTesting
     public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
         boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
                 && !mBubbleData.getAnyBubbleWithkey(key).showInShade());
@@ -674,23 +692,19 @@
         return (isSummary && isSuppressedSummary) || isSuppressedBubble;
     }
 
-    @Override
-    public boolean isSummarySuppressed(String groupKey) {
-        return mBubbleData.isSummarySuppressed(groupKey);
+    private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
+            Executor callbackExecutor) {
+        if (mBubbleData.isSummarySuppressed(groupKey)) {
+            mBubbleData.removeSuppressedSummary(groupKey);
+            if (callback != null) {
+                callbackExecutor.execute(() -> {
+                    callback.accept(mBubbleData.getSummaryKey(groupKey));
+                });
+            }
+        }
     }
 
-    @Override
-    public void removeSuppressedSummary(String groupKey) {
-        mBubbleData.removeSuppressedSummary(groupKey);
-    }
-
-    @Override
-    public String getSummaryKey(String groupKey) {
-        return mBubbleData.getSummaryKey(groupKey);
-    }
-
-    @Override
-    public boolean isBubbleExpanded(String key) {
+    private boolean isBubbleExpanded(String key) {
         return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null
                 && mBubbleData.getSelectedBubble().getKey().equals(key);
     }
@@ -704,7 +718,7 @@
         setIsBubble(bubble, true /* isBubble */);
     }
 
-    @Override
+    @VisibleForTesting
     public void expandStackAndSelectBubble(BubbleEntry entry) {
         if (mIsStatusBarShade) {
             mNotifEntryToExpandOnShadeUnlock = null;
@@ -757,7 +771,8 @@
                     // if the bubble is already active, there's no need to push it to overflow
                     return;
                 }
-                bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble),
+                bubble.inflate(
+                        (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble),
                         mContext, this, mStackView, mBubbleIconFactory, true /* skipInflation */);
             });
             return null;
@@ -809,15 +824,13 @@
         }
     }
 
-    @Override
-    public void onEntryAdded(BubbleEntry entry) {
+    private void onEntryAdded(BubbleEntry entry) {
         if (canLaunchInActivityView(mContext, entry)) {
             updateBubble(entry);
         }
     }
 
-    @Override
-    public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
+    private void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
         // shouldBubbleUp checks canBubble & for bubble metadata
         boolean shouldBubble = shouldBubbleUp && canLaunchInActivityView(mContext, entry);
         if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
@@ -828,8 +841,7 @@
         }
     }
 
-    @Override
-    public void onEntryRemoved(BubbleEntry entry) {
+    private void onEntryRemoved(BubbleEntry entry) {
         if (isSummaryOfBubbles(entry)) {
             final String groupKey = entry.getStatusBarNotification().getGroupKey();
             mBubbleData.removeSuppressedSummary(groupKey);
@@ -844,8 +856,7 @@
         }
     }
 
-    @Override
-    public void onRankingUpdated(RankingMap rankingMap) {
+    private void onRankingUpdated(RankingMap rankingMap) {
         if (mTmpRanking == null) {
             mTmpRanking = new NotificationListenerService.Ranking();
         }
@@ -882,8 +893,7 @@
             return bubbleChildren;
         }
         for (Bubble bubble : mBubbleData.getActiveBubbles()) {
-            final BubbleEntry entry = mSysuiProxy.getPendingOrActiveEntry(bubble.getKey());
-            if (entry != null && groupKey.equals(entry.getStatusBarNotification().getGroupKey())) {
+            if (bubble.getGroupKey() != null && groupKey.equals(bubble.getGroupKey())) {
                 bubbleChildren.add(bubble);
             }
         }
@@ -951,7 +961,7 @@
             ArrayList<Bubble> bubblesToBeRemovedFromRepository = new ArrayList<>();
             for (Pair<Bubble, Integer> removed : removedBubbles) {
                 final Bubble bubble = removed.first;
-                @DismissReason final int reason = removed.second;
+                @Bubbles.DismissReason final int reason = removed.second;
 
                 if (mStackView != null) {
                     mStackView.removeBubble(bubble);
@@ -1029,8 +1039,7 @@
         }
     };
 
-    @Override
-    public boolean handleDismissalInterception(BubbleEntry entry,
+    private boolean handleDismissalInterception(BubbleEntry entry,
             @Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
         if (isSummaryOfBubbles(entry)) {
             handleSummaryDismissalInterception(entry, children, removeCallback);
@@ -1053,8 +1062,8 @@
     private boolean isSummaryOfBubbles(BubbleEntry entry) {
         String groupKey = entry.getStatusBarNotification().getGroupKey();
         ArrayList<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
-        boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
-                && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey()));
+        boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey)
+                && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey());
         boolean isSummary = entry.getStatusBarNotification().getNotification().isGroupSummary();
         return (isSuppressedSummary || isSummary) && !bubbleChildren.isEmpty();
     }
@@ -1137,8 +1146,7 @@
     /**
      * Description of current bubble state.
      */
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("BubbleController state:");
         mBubbleData.dump(fd, pw, args);
         pw.println();
@@ -1216,4 +1224,175 @@
             }
         }
     }
+
+    private class BubblesImpl implements Bubbles {
+        @Override
+        public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
+            return mMainExecutor.executeBlockingForResult(() -> {
+                return BubbleController.this.isBubbleNotificationSuppressedFromShade(key, groupKey);
+            }, Boolean.class);
+        }
+
+        @Override
+        public boolean isBubbleExpanded(String key) {
+            return mMainExecutor.executeBlockingForResult(() -> {
+                return BubbleController.this.isBubbleExpanded(key);
+            }, Boolean.class);
+        }
+
+        @Override
+        public boolean isStackExpanded() {
+            return mMainExecutor.executeBlockingForResult(() -> {
+                return BubbleController.this.isStackExpanded();
+            }, Boolean.class);
+        }
+
+        @Override
+        public void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
+                Executor callbackExecutor) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, callback,
+                        callbackExecutor);
+            });
+        }
+
+        @Override
+        public void collapseStack() {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.collapseStack();
+            });
+        }
+
+        @Override
+        public void updateForThemeChanges() {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.updateForThemeChanges();
+            });
+        }
+
+        @Override
+        public void expandStackAndSelectBubble(BubbleEntry entry) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.expandStackAndSelectBubble(entry);
+            });
+        }
+
+        @Override
+        public void onTaskbarChanged(Bundle b) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onTaskbarChanged(b);
+            });
+        }
+
+        @Override
+        public void openBubbleOverflow() {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.openBubbleOverflow();
+            });
+        }
+
+        @Override
+        public boolean handleDismissalInterception(BubbleEntry entry,
+                @Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
+            return mMainExecutor.executeBlockingForResult(() -> {
+                return BubbleController.this.handleDismissalInterception(entry, children,
+                        removeCallback);
+            }, Boolean.class);
+        }
+
+        @Override
+        public void setSysuiProxy(SysuiProxy proxy) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.setSysuiProxy(proxy);
+            });
+        }
+
+        @Override
+        public void setBubbleScrim(View view, BiConsumer<Executor, Looper> callback) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.setBubbleScrim(view, callback);
+            });
+        }
+
+        @Override
+        public void setExpandListener(BubbleExpandListener listener) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.setExpandListener(listener);
+            });
+        }
+
+        @Override
+        public void onEntryAdded(BubbleEntry entry) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onEntryAdded(entry);
+            });
+        }
+
+        @Override
+        public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onEntryUpdated(entry, shouldBubbleUp);
+            });
+        }
+
+        @Override
+        public void onEntryRemoved(BubbleEntry entry) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onEntryRemoved(entry);
+            });
+        }
+
+        @Override
+        public void onRankingUpdated(RankingMap rankingMap) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onRankingUpdated(rankingMap);
+            });
+        }
+
+        @Override
+        public void onStatusBarVisibilityChanged(boolean visible) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onStatusBarVisibilityChanged(visible);
+            });
+        }
+
+        @Override
+        public void onZenStateChanged() {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onZenStateChanged();
+            });
+        }
+
+        @Override
+        public void onStatusBarStateChanged(boolean isShade) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onStatusBarStateChanged(isShade);
+            });
+        }
+
+        @Override
+        public void onUserChanged(int newUserId) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onUserChanged(newUserId);
+            });
+        }
+
+        @Override
+        public void onConfigChanged(Configuration newConfig) {
+            mMainExecutor.execute(() -> {
+                BubbleController.this.onConfigChanged(newConfig);
+            });
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    BubbleController.this.dump(fd, pw, args);
+                });
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Failed to dump BubbleController in 2s");
+            }
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index e24ff06..53b7537 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -46,6 +46,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -117,6 +118,7 @@
 
     private final Context mContext;
     private final BubblePositioner mPositioner;
+    private final Executor mMainExecutor;
     /** Bubbles that are actively in the stack. */
     private final List<Bubble> mBubbles;
     /** Bubbles that aged out to overflow. */
@@ -155,10 +157,12 @@
      */
     private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
 
-    public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner) {
+    public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner,
+            Executor mainExecutor) {
         mContext = context;
         mLogger = bubbleLogger;
         mPositioner = positioner;
+        mMainExecutor = mainExecutor;
         mOverflow = new BubbleOverflow(context, positioner);
         mBubbles = new ArrayList<>();
         mOverflowBubbles = new ArrayList<>();
@@ -264,7 +268,8 @@
                 bubbleToReturn = mPendingBubbles.get(key);
             } else if (entry != null) {
                 // New bubble
-                bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener);
+                bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener,
+                        mMainExecutor);
             } else {
                 // Persisted bubble being promoted
                 bubbleToReturn = persistedBubble;
@@ -537,7 +542,8 @@
     void overflowBubble(@DismissReason int reason, Bubble bubble) {
         if (bubble.getPendingIntentCanceled()
                 || !(reason == Bubbles.DISMISS_AGED
-                || reason == Bubbles.DISMISS_USER_GESTURE)) {
+                    || reason == Bubbles.DISMISS_USER_GESTURE
+                    || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) {
             return;
         }
         if (DEBUG_BUBBLE_DATA) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index fc565f1..3108b02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -27,6 +27,8 @@
 import com.android.wm.shell.bubbles.storage.BubbleEntity
 import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
 import com.android.wm.shell.bubbles.storage.BubbleVolatileRepository
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.annotations.ExternalThread
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
@@ -34,12 +36,12 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.yield
 
-internal class BubbleDataRepository(context: Context, private val launcherApps: LauncherApps) {
+internal class BubbleDataRepository(context: Context, private val launcherApps: LauncherApps,
+        private val mainExecutor : ShellExecutor) {
     private val volatileRepository = BubbleVolatileRepository(launcherApps)
     private val persistentRepository = BubblePersistentRepository(context)
 
     private val ioScope = CoroutineScope(Dispatchers.IO)
-    private val uiScope = CoroutineScope(Dispatchers.Main)
     private var job: Job? = null
 
     /**
@@ -109,6 +111,8 @@
 
     /**
      * Load bubbles from disk.
+     * @param cb The callback to be run after the bubbles are loaded.  This callback is always made
+     *           on the main thread of the hosting process.
      */
     @SuppressLint("WrongConstant")
     fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch {
@@ -163,10 +167,11 @@
                             shortcutInfo,
                             entity.desiredHeight,
                             entity.desiredHeightResId,
-                            entity.title
+                            entity.title,
+                            mainExecutor
                     ) }
         }
-        uiScope.launch { cb(bubbles) }
+        mainExecutor.execute { cb(bubbles) }
     }
 }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
index 3361c4c..c88a58b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
@@ -61,7 +61,10 @@
         BUBBLE_OVERFLOW_REMOVE_BLOCKED(490),
 
         @UiEvent(doc = "User selected the overflow.")
-        BUBBLE_OVERFLOW_SELECTED(600);
+        BUBBLE_OVERFLOW_SELECTED(600),
+
+        @UiEvent(doc = "Restore bubble to overflow after phone reboot.")
+        BUBBLE_OVERFLOW_RECOVER(691);
 
         private final int mId;
 
@@ -112,6 +115,8 @@
             log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
         } else if (r == Bubbles.DISMISS_USER_GESTURE) {
             log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
+        } else if (r == Bubbles.DISMISS_RELOAD_FROM_DISK) {
+            log(b, Event.BUBBLE_OVERFLOW_RECOVER);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index fac3686..d54be0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -79,6 +79,7 @@
 import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
 import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import java.io.FileDescriptor;
@@ -147,7 +148,7 @@
      * Handler to use for all delayed animations - this way, we can easily cancel them before
      * starting a new animation.
      */
-    private final Handler mDelayedAnimationHandler = new Handler();
+    private final ShellExecutor mDelayedAnimationExecutor;
 
     /**
      * Interface to synchronize {@link View} state and the screen.
@@ -311,7 +312,7 @@
         }
     }
 
-    private BubbleController.BubbleExpandListener mExpandListener;
+    private Bubbles.BubbleExpandListener mExpandListener;
 
     /** Callback to run when we want to unbubble the given notification's conversation. */
     private Consumer<String> mUnbubbleConversationCallback;
@@ -734,9 +735,11 @@
     @SuppressLint("ClickableViewAccessibility")
     public BubbleStackView(Context context, BubbleController bubbleController,
             BubbleData data, @Nullable SurfaceSynchronizer synchronizer,
-            FloatingContentCoordinator floatingContentCoordinator) {
+            FloatingContentCoordinator floatingContentCoordinator,
+            ShellExecutor mainExecutor) {
         super(context);
 
+        mDelayedAnimationExecutor = mainExecutor;
         mBubbleController = bubbleController;
         mBubbleData = data;
 
@@ -1366,7 +1369,7 @@
     /**
      * Sets the listener to notify when the bubble stack is expanded.
      */
-    public void setExpandListener(BubbleController.BubbleExpandListener listener) {
+    public void setExpandListener(Bubbles.BubbleExpandListener listener) {
         mExpandListener = listener;
     }
 
@@ -1475,6 +1478,9 @@
      * Update bubble order and pointer position.
      */
     public void updateBubbleOrder(List<Bubble> bubbles) {
+        if (isExpansionAnimating()) {
+            return;
+        }
         final Runnable reorder = () -> {
             for (int i = 0; i < bubbles.size(); i++) {
                 Bubble bubble = bubbles.get(i);
@@ -1659,6 +1665,7 @@
         }
         beforeExpandedViewAnimation();
 
+        updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */);
         mBubbleContainer.setActiveController(mExpandedAnimationController);
         updateOverflowVisibility();
         updatePointerPosition();
@@ -1734,7 +1741,7 @@
             mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
         }
 
-        mDelayedAnimationHandler.postDelayed(() -> {
+        mDelayedAnimationExecutor.executeDelayed(() -> {
             PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
             PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
                     .spring(AnimatableScaleMatrix.SCALE_X,
@@ -1791,10 +1798,12 @@
 
         final long startDelay =
                 (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f);
-        mDelayedAnimationHandler.postDelayed(() -> mExpandedAnimationController.collapseBackToStack(
-                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
-                /* collapseTo */,
-                () -> mBubbleContainer.setActiveController(mStackAnimationController)), startDelay);
+        mDelayedAnimationExecutor.executeDelayed(() -> {
+            mExpandedAnimationController.collapseBackToStack(
+                    mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+                    /* collapseTo */,
+                    () -> mBubbleContainer.setActiveController(mStackAnimationController));
+        }, startDelay);
 
         if (mTaskbarScrim.getVisibility() == VISIBLE) {
             mTaskbarScrim.animate().alpha(0f).start();
@@ -1870,7 +1879,7 @@
                                 mExpandedBubble));
                     }
                     updateOverflowVisibility();
-
+                    updateBadgesAndZOrder(true /* setBadgeForCollapsedStack */);
                     afterExpandedViewAnimation();
                     if (previouslySelected != null) {
                         previouslySelected.setContentVisibility(false);
@@ -1945,7 +1954,7 @@
 
         mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
 
-        mDelayedAnimationHandler.postDelayed(() -> {
+        mDelayedAnimationExecutor.executeDelayed(() -> {
             if (!mIsExpanded) {
                 mIsBubbleSwitchAnimating = false;
                 return;
@@ -1978,7 +1987,7 @@
      * animating flags for those animations.
      */
     private void cancelDelayedExpandCollapseSwitchAnimations() {
-        mDelayedAnimationHandler.removeCallbacksAndMessages(null);
+        mDelayedAnimationExecutor.removeAllCallbacks();
 
         mIsExpansionAnimating = false;
         mIsBubbleSwitchAnimating = false;
@@ -2618,7 +2627,6 @@
         }
 
         mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
-        updateBadgesAndZOrder(false /* setBadgeForCollapsedStack */);
     }
 
     /**
@@ -2728,9 +2736,13 @@
      * @param action the user interaction enum.
      */
     private void logBubbleEvent(@Nullable BubbleViewProvider provider, int action) {
+        final String packageName =
+                (provider != null && provider instanceof Bubble)
+                    ? ((Bubble) provider).getPackageName()
+                    : "null";
         mBubbleData.logBubbleEvent(provider,
                 action,
-                mContext.getApplicationInfo().packageName,
+                packageName,
                 getBubbleCount(),
                 getBubbleIndex(provider),
                 getNormalizedXPosition(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 0e7e92d..c5a712e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -46,6 +46,7 @@
 
 import java.lang.ref.WeakReference;
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Simple task to inflate views & load necessary info to display a bubble.
@@ -71,6 +72,7 @@
     private BubbleIconFactory mIconFactory;
     private boolean mSkipInflation;
     private Callback mCallback;
+    private Executor mMainExecutor;
 
     /**
      * Creates a task to load information for the provided {@link Bubble}. Once all info
@@ -82,7 +84,8 @@
             BubbleStackView stackView,
             BubbleIconFactory factory,
             boolean skipInflation,
-            Callback c) {
+            Callback c,
+            Executor mainExecutor) {
         mBubble = b;
         mContext = new WeakReference<>(context);
         mController = new WeakReference<>(controller);
@@ -90,6 +93,7 @@
         mIconFactory = factory;
         mSkipInflation = skipInflation;
         mCallback = c;
+        mMainExecutor = mainExecutor;
     }
 
     @Override
@@ -103,10 +107,12 @@
         if (isCancelled() || viewInfo == null) {
             return;
         }
-        mBubble.setViewInfo(viewInfo);
-        if (mCallback != null) {
-            mCallback.onBubbleViewsReady(mBubble);
-        }
+        mMainExecutor.execute(() -> {
+            mBubble.setViewInfo(viewInfo);
+            if (mCallback != null) {
+                mCallback.onBubbleViewsReady(mBubble);
+            }
+        });
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index fa5ac44..6a1026b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -23,6 +23,7 @@
 
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.Looper;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.util.ArraySet;
 import android.view.View;
@@ -37,6 +38,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -50,7 +54,7 @@
             DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
             DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
             DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
-            DISMISS_NO_BUBBLE_UP})
+            DISMISS_NO_BUBBLE_UP, DISMISS_RELOAD_FROM_DISK})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
     @interface DismissReason {}
 
@@ -68,6 +72,7 @@
     int DISMISS_SHORTCUT_REMOVED = 12;
     int DISMISS_PACKAGE_REMOVED = 13;
     int DISMISS_NO_BUBBLE_UP = 14;
+    int DISMISS_RELOAD_FROM_DISK = 15;
 
     /**
      * @return {@code true} if there is a bubble associated with the provided key and if its
@@ -86,13 +91,14 @@
     /** @return {@code true} if stack of bubbles is expanded or not. */
     boolean isStackExpanded();
 
-    /** @return {@code true} if the summary for the provided group key is suppressed. */
-    boolean isSummarySuppressed(String groupKey);
-
     /**
-     * Removes a group key indicating that summary for this group should no longer be suppressed.
+     * Removes a group key indicating that the summary for this group should no longer be
+     * suppressed.
+     *
+     * @param callback If removed, this callback will be called with the summary key of the group
      */
-    void removeSuppressedSummary(String groupKey);
+    void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
+            Executor callbackExecutor);
 
     /** Tell the stack of bubbles to collapse. */
     void collapseStack();
@@ -134,19 +140,16 @@
     boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children,
             IntConsumer removeCallback);
 
-    /**
-     * Retrieves the notif entry key of the summary associated with the provided group key.
-     *
-     * @param groupKey the group to look up
-     * @return the key for the notification that is the summary of this group.
-     */
-    String getSummaryKey(String groupKey);
-
     /** Set the proxy to commnuicate with SysUi side components. */
     void setSysuiProxy(SysuiProxy proxy);
 
-    /** Set the scrim view for bubbles. */
-    void setBubbleScrim(View view);
+    /**
+     * Set the scrim view for bubbles.
+     *
+     * @param callback The callback made with the executor and the executor's looper that the view
+     *                 will be running on.
+     **/
+    void setBubbleScrim(View view, BiConsumer<Executor, Looper> callback);
 
     /** Set a listener to be notified of bubble expand events. */
     void setExpandListener(BubbleExpandListener listener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
index a1b0dbe..cf0cefe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
@@ -91,7 +91,6 @@
     private var touchSlop: Int = -1
     private var movedEnough = false
 
-    private val handler = Handler(Looper.myLooper()!!)
     private var performedLongClick = false
 
     @Suppress("UNCHECKED_CAST")
@@ -115,7 +114,7 @@
                 viewPositionOnTouchDown.set(v.translationX, v.translationY)
 
                 performedLongClick = false
-                handler.postDelayed({
+                v.handler.postDelayed({
                     if (v.isLongClickable) {
                         performedLongClick = v.performLongClick()
                     }
@@ -125,7 +124,7 @@
             MotionEvent.ACTION_MOVE -> {
                 if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
                     movedEnough = true
-                    handler.removeCallbacksAndMessages(null)
+                    v.handler.removeCallbacksAndMessages(null)
                 }
 
                 if (movedEnough) {
@@ -141,7 +140,7 @@
                 } else if (!performedLongClick) {
                     v.performClick()
                 } else {
-                    handler.removeCallbacksAndMessages(null)
+                    v.handler.removeCallbacksAndMessages(null)
                 }
 
                 velocityTracker.clear()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index e1f831e..73371e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -62,8 +62,8 @@
 
     private static final String TAG = "Bubbs.StackCtrl";
 
-    /** Values to use for animating bubbles in. */
-    private static final float ANIMATE_IN_STIFFNESS = 1000f;
+    /** Value to use for animating bubbles in and springing stack after fling. */
+    private static final float STACK_SPRING_STIFFNESS = 700f;
 
     /** Values to use for animating updated bubble to top of stack. */
     private static final float NEW_BUBBLE_START_SCALE = 0.5f;
@@ -80,20 +80,15 @@
 
     private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
             new PhysicsAnimator.SpringConfig(
-                    ANIMATE_IN_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+                    STACK_SPRING_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
 
     /**
      * Friction applied to fling animations. Since the stack must land on one of the sides of the
      * screen, we want less friction horizontally so that the stack has a better chance of making it
      * to the side without needing a spring.
      */
-    private static final float FLING_FRICTION = 2.2f;
+    private static final float FLING_FRICTION = 1.9f;
 
-    /**
-     * Values to use for the stack spring animation used to spring the stack to its final position
-     * after a fling.
-     */
-    private static final int SPRING_AFTER_FLING_STIFFNESS = 750;
     private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
 
     /** Sentinel value for unset position value. */
@@ -216,7 +211,7 @@
 
         @Override
         public void moveToBounds(@NonNull Rect bounds) {
-            springStack(bounds.left, bounds.top, SpringForce.STIFFNESS_LOW);
+            springStack(bounds.left, bounds.top, STACK_SPRING_STIFFNESS);
         }
 
         @NonNull
@@ -341,7 +336,7 @@
      * flings.
      */
     public void springStackAfterFling(float destinationX, float destinationY) {
-        springStack(destinationX, destinationY, SPRING_AFTER_FLING_STIFFNESS);
+        springStack(destinationX, destinationY, STACK_SPRING_STIFFNESS);
     }
 
     /**
@@ -371,7 +366,7 @@
 
         final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
         final float stiffness = Settings.Secure.getFloat(contentResolver, "bubble_stiffness",
-                SPRING_AFTER_FLING_STIFFNESS /* default */);
+                STACK_SPRING_STIFFNESS /* default */);
         final float dampingRatio = Settings.Secure.getFloat(contentResolver, "bubble_damping",
                 SPRING_AFTER_FLING_DAMPING_RATIO);
         final float friction = Settings.Secure.getFloat(contentResolver, "bubble_friction",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 3944128..b2ac61c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -42,10 +42,11 @@
 import androidx.annotation.BinderThread;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.inputmethod.Completable;
+import com.android.internal.inputmethod.ResultCallbacks;
 import com.android.internal.view.IInputMethodManager;
 
 import java.util.ArrayList;
-import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -158,6 +159,14 @@
         }
     }
 
+    private void dispatchVisibilityChanged(int displayId, boolean isShowing) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImeVisibilityChanged(displayId, isShowing);
+            }
+        }
+    }
+
     /**
      * Adds an {@link ImePositionProcessor} to be called during ime position updates.
      */
@@ -207,23 +216,21 @@
         }
 
         protected void insetsChanged(InsetsState insetsState) {
-            mMainExecutor.execute(() -> {
-                if (mInsetsState.equals(insetsState)) {
-                    return;
-                }
+            if (mInsetsState.equals(insetsState)) {
+                return;
+            }
 
-                mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME);
+            updateImeVisibility(insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME));
 
-                final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
-                final Rect newFrame = newSource.getFrame();
-                final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
+            final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
+            final Rect newFrame = newSource.getFrame();
+            final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
 
-                mInsetsState.set(insetsState, true /* copySources */);
-                if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
-                    if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
-                    startAnimation(mImeShowing, true /* forceRestart */);
-                }
-            });
+            mInsetsState.set(insetsState, true /* copySources */);
+            if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
+                if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
+                startAnimation(mImeShowing, true /* forceRestart */);
+            }
         }
 
         @VisibleForTesting
@@ -236,27 +243,25 @@
                         continue;
                     }
                     if (activeControl.getType() == InsetsState.ITYPE_IME) {
-                        mMainExecutor.execute(() -> {
-                            final Point lastSurfacePosition = mImeSourceControl != null
-                                    ? mImeSourceControl.getSurfacePosition() : null;
-                            final boolean positionChanged =
-                                    !activeControl.getSurfacePosition().equals(lastSurfacePosition);
-                            final boolean leashChanged =
-                                    !haveSameLeash(mImeSourceControl, activeControl);
-                            mImeSourceControl = activeControl;
-                            if (mAnimation != null) {
-                                if (positionChanged) {
-                                    startAnimation(mImeShowing, true /* forceRestart */);
-                                }
-                            } else {
-                                if (leashChanged) {
-                                    applyVisibilityToLeash();
-                                }
-                                if (!mImeShowing) {
-                                    removeImeSurface();
-                                }
+                        final Point lastSurfacePosition = mImeSourceControl != null
+                                ? mImeSourceControl.getSurfacePosition() : null;
+                        final boolean positionChanged =
+                                !activeControl.getSurfacePosition().equals(lastSurfacePosition);
+                        final boolean leashChanged =
+                                !haveSameLeash(mImeSourceControl, activeControl);
+                        mImeSourceControl = activeControl;
+                        if (mAnimation != null) {
+                            if (positionChanged) {
+                                startAnimation(mImeShowing, true /* forceRestart */);
                             }
-                        });
+                        } else {
+                            if (leashChanged) {
+                                applyVisibilityToLeash();
+                            }
+                            if (!mImeShowing) {
+                                removeImeSurface();
+                            }
+                        }
                     }
                 }
             }
@@ -281,7 +286,7 @@
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got showInsets for ime");
-            mMainExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */));
+            startAnimation(true /* show */, false /* forceRestart */);
         }
 
 
@@ -290,7 +295,7 @@
                 return;
             }
             if (DEBUG) Slog.d(TAG, "Got hideInsets for ime");
-            mMainExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */));
+            startAnimation(false /* show */, false /* forceRestart */);
         }
 
         public void topFocusedWindowChanged(String packageName) {
@@ -374,7 +379,7 @@
                 seek = true;
             }
             mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
-            mImeShowing = show;
+            updateImeVisibility(show);
             mAnimation = ValueAnimator.ofFloat(startY, endY);
             mAnimation.setDuration(
                     show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
@@ -458,6 +463,13 @@
             }
         }
 
+        private void updateImeVisibility(boolean isShowing) {
+            if (mImeShowing != isShowing) {
+                mImeShowing = isShowing;
+                dispatchVisibilityChanged(mDisplayId, isShowing);
+            }
+        }
+
         @VisibleForTesting
         @BinderThread
         public class DisplayWindowInsetsControllerImpl
@@ -506,7 +518,9 @@
             try {
                 // Remove the IME surface to make the insets invisible for
                 // non-client controlled insets.
-                imms.removeImeSurface();
+                final Completable.Void value = Completable.createVoid();
+                imms.removeImeSurface(ResultCallbacks.of(value));
+                Completable.getResult(value);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to remove IME surface.", e);
             }
@@ -564,6 +578,15 @@
         default void onImeEndPositioning(int displayId, boolean cancel,
                 SurfaceControl.Transaction t) {
         }
+
+        /**
+         * Called when the IME visibility changed.
+         *
+         * @param isShowing {@code true} if the IME is shown.
+         */
+        default void onImeVisibilityChanged(int displayId, boolean isShowing) {
+
+        }
     }
 
     public IInputMethodManager getImms() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 3181dbf..58a4baf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -356,11 +356,11 @@
         if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
             return null;
         }
-        final Insets waterfallInsets =
-                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
         if (rotation == ROTATION_0) {
             return computeSafeInsets(cutout, displayWidth, displayHeight);
         }
+        final Insets waterfallInsets =
+                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
         Rect[] cutoutRects = cutout.getBoundingRectsAll();
         final Rect[] newBounds = new Rect[cutoutRects.length];
@@ -372,8 +372,12 @@
             }
             newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
         }
+        final DisplayCutout.CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
+        final DisplayCutout.CutoutPathParserInfo newInfo = new DisplayCutout.CutoutPathParserInfo(
+                info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
+                info.getCutoutSpec(), rotation, info.getScale());
         return computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets),
+                DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
                 rotated ? displayHeight : displayWidth,
                 rotated ? displayWidth : displayHeight);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
index 4874d3c..a4cd3c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java
@@ -47,6 +47,11 @@
     }
 
     @Override
+    public void removeAllCallbacks() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    @Override
     public void removeCallbacks(@NonNull Runnable r) {
         mHandler.removeCallbacks(r);
     }
@@ -55,9 +60,4 @@
     public boolean hasCallback(Runnable r) {
         return mHandler.hasCallbacks(r);
     }
-
-    @Override
-    public Looper getLooper() {
-        return mHandler.getLooper();
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
index d37e628..6abc8f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java
@@ -17,10 +17,16 @@
 package com.android.wm.shell.common;
 
 import android.os.Looper;
+import android.os.SystemClock;
+import android.os.Trace;
 
+import java.lang.reflect.Array;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
 
 /**
  * Super basic Executor interface that adds support for delayed execution and removing callbacks.
@@ -63,22 +69,42 @@
     }
 
     /**
+     * Convenience method to execute the blocking call with a default timeout and returns a value.
+     * Waits indefinitely for a typed result from a call.
+     */
+    default <T> T executeBlockingForResult(Supplier<T> runnable, Class clazz) {
+        final T[] result = (T[]) Array.newInstance(clazz, 1);
+        final CountDownLatch latch = new CountDownLatch(1);
+        execute(() -> {
+            result[0] = runnable.get();
+            latch.countDown();
+        });
+        try {
+            latch.await();
+            return result[0];
+        } catch (InterruptedException e) {
+            return null;
+        }
+    }
+
+
+    /**
      * See {@link android.os.Handler#postDelayed(Runnable, long)}.
      */
-    void executeDelayed(Runnable r, long delayMillis);
+    void executeDelayed(Runnable runnable, long delayMillis);
+
+    /**
+     * Removes all pending callbacks.
+     */
+    void removeAllCallbacks();
 
     /**
      * See {@link android.os.Handler#removeCallbacks}.
      */
-    void removeCallbacks(Runnable r);
+    void removeCallbacks(Runnable runnable);
 
     /**
      * See {@link android.os.Handler#hasCallbacks(Runnable)}.
      */
-    boolean hasCallback(Runnable r);
-
-    /**
-     * Returns the looper that this executor is running on.
-     */
-    Looper getLooper();
+    boolean hasCallback(Runnable runnable);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
index 00bd9e5..59374a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java
@@ -19,7 +19,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ITaskStackListener;
 import android.content.ComponentName;
-import android.os.IBinder;
 import android.window.TaskSnapshot;
 
 import androidx.annotation.BinderThread;
@@ -85,6 +84,4 @@
     default void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { }
 
     default void onActivityRotation(int displayId) { }
-
-    default void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index db34248..e94080a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -22,7 +22,6 @@
 import android.app.TaskStackListener;
 import android.content.ComponentName;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
 import android.os.Trace;
 import android.util.Log;
@@ -54,13 +53,12 @@
     private static final int ON_TASK_MOVED_TO_FRONT = 12;
     private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 13;
     private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED = 14;
-    private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 15;
-    private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 16;
-    private static final int ON_TASK_DISPLAY_CHANGED = 17;
-    private static final int ON_TASK_LIST_UPDATED = 18;
-    private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 19;
-    private static final int ON_TASK_DESCRIPTION_CHANGED = 20;
-    private static final int ON_ACTIVITY_ROTATION = 21;
+    private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 15;
+    private static final int ON_TASK_DISPLAY_CHANGED = 16;
+    private static final int ON_TASK_LIST_UPDATED = 17;
+    private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 18;
+    private static final int ON_TASK_DESCRIPTION_CHANGED = 19;
+    private static final int ON_ACTIVITY_ROTATION = 20;
 
     /**
      * List of {@link TaskStackListenerCallback} registered from {@link #addListener}.
@@ -264,13 +262,6 @@
     }
 
     @Override
-    public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
-        mMainHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId,
-                0 /* unused */,
-                activityToken).sendToTarget();
-    }
-
-    @Override
     public boolean handleMessage(Message msg) {
         synchronized (mTaskStackListeners) {
             switch (msg.what) {
@@ -383,13 +374,6 @@
                     }
                     break;
                 }
-                case ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED: {
-                    for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                        mTaskStackListeners.get(i).onSizeCompatModeActivityChanged(
-                                msg.arg1, (IBinder) msg.obj);
-                    }
-                    break;
-                }
                 case ON_BACK_PRESSED_ON_TASK_ROOT: {
                     for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                         mTaskStackListeners.get(i).onBackPressedOnTaskRoot(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 800150c..35dcdd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -34,8 +34,11 @@
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -84,8 +87,7 @@
     private DragSession mSession;
 
     public DragAndDropPolicy(Context context, SplitScreen splitScreen) {
-        this(context, ActivityTaskManager.getInstance(), splitScreen,
-                new DefaultStarter(context, splitScreen));
+        this(context, ActivityTaskManager.getInstance(), splitScreen, new DefaultStarter(context));
     }
 
     @VisibleForTesting
@@ -94,7 +96,7 @@
         mContext = context;
         mActivityTaskManager = activityTaskManager;
         mSplitScreen = splitScreen;
-        mStarter = starter;
+        mStarter = mSplitScreen != null ? mSplitScreen : starter;
     }
 
     /**
@@ -195,39 +197,23 @@
             return;
         }
 
-        final ClipDescription description = data.getDescription();
-        final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
-        final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
-        final Intent dragData = mSession.dragData;
         final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
         final boolean leftOrTop = target.type == TYPE_SPLIT_TOP || target.type == TYPE_SPLIT_LEFT;
-        final Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
-                ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
-                : new Bundle();
 
-        if (target.type == TYPE_FULLSCREEN) {
-            // Exit split stages if needed
-            mStarter.exitSplitScreen();
-        } else if (mSplitScreen != null) {
+        @SplitScreen.StageType int stage = STAGE_TYPE_UNDEFINED;
+        @SplitScreen.StagePosition int position = STAGE_POSITION_UNDEFINED;
+        if (target.type != TYPE_FULLSCREEN && mSplitScreen != null) {
             // Update launch options for the split side we are targeting.
-            final int position = leftOrTop
-                    ? SIDE_STAGE_POSITION_TOP_OR_LEFT : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+            position = leftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
             if (!inSplitScreen) {
-                // Update the side stage position to match where we want to launch.
-                mSplitScreen.setSideStagePosition(position);
+                // Launch in the side stage if we are not in split-screen already.
+                stage = STAGE_TYPE_SIDE;
             }
-            mSplitScreen.updateActivityOptions(opts, position);
         }
 
-        if (isTask) {
-            mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts);
-        } else if (isShortcut) {
-            mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME),
-                    dragData.getStringExtra(EXTRA_SHORTCUT_ID),
-                    opts, dragData.getParcelableExtra(EXTRA_USER));
-        } else {
-            mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts);
-        }
+        final ClipDescription description = data.getDescription();
+        final Intent dragData = mSession.dragData;
+        mStarter.startClipDescription(description, dragData, stage, position);
     }
 
     /**
@@ -247,7 +233,6 @@
         int runningTaskActType = ACTIVITY_TYPE_STANDARD;
         boolean runningTaskIsResizeable;
         boolean dragItemSupportsSplitscreen;
-        boolean isPhone;
 
         DragSession(Context context, ActivityTaskManager activityTaskManager,
                 DisplayLayout dispLayout, ClipData data) {
@@ -275,7 +260,6 @@
             final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
             dragItemSupportsSplitscreen = info == null
                     || ActivityInfo.isResizeableMode(info.resizeMode);
-            isPhone = mContext.getResources().getConfiguration().smallestScreenWidthDp < 600;
             dragData = mInitialDragData.getItemAt(0).getIntent();
         }
     }
@@ -284,11 +268,33 @@
      * Interface for actually committing the task launches.
      */
     @VisibleForTesting
-    interface Starter {
-        void startTask(int taskId, Bundle activityOptions);
-        void startShortcut(String packageName, String shortcutId, Bundle activityOptions,
-                UserHandle user);
-        void startIntent(PendingIntent intent, Bundle activityOptions);
+    public interface Starter {
+        default void startClipDescription(ClipDescription description, Intent intent,
+                @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position) {
+            final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
+            final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
+            final Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
+                    ? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) : new Bundle();
+
+            if (isTask) {
+                final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+                startTask(taskId, stage, position, opts);
+            } else if (isShortcut) {
+                final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+                final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
+                final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
+                startShortcut(packageName, id, stage, position, opts, user);
+            } else {
+                startIntent(intent.getParcelableExtra(EXTRA_PENDING_INTENT), stage, position, opts);
+            }
+        }
+        void startTask(int taskId, @SplitScreen.StageType int stage,
+                @SplitScreen.StagePosition int position, @Nullable Bundle options);
+        void startShortcut(String packageName, String shortcutId,
+                @SplitScreen.StageType int stage, @SplitScreen.StagePosition int position,
+                @Nullable Bundle options, UserHandle user);
+        void startIntent(PendingIntent intent, @SplitScreen.StageType int stage,
+                @SplitScreen.StagePosition int position, @Nullable Bundle options);
         void enterSplitScreen(int taskId, boolean leftOrTop);
         void exitSplitScreen();
     }
@@ -299,39 +305,39 @@
      */
     private static class DefaultStarter implements Starter {
         private final Context mContext;
-        private final SplitScreen mSplitScreen;
 
-        public DefaultStarter(Context context, SplitScreen splitScreen) {
+        public DefaultStarter(Context context) {
             mContext = context;
-            mSplitScreen = splitScreen;
         }
 
         @Override
-        public void startTask(int taskId, Bundle activityOptions) {
+        public void startTask(int taskId, int stage, int position,
+                @Nullable Bundle options) {
             try {
-                ActivityTaskManager.getService().startActivityFromRecents(taskId, activityOptions);
+                ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to launch task", e);
             }
         }
 
         @Override
-        public void startShortcut(String packageName, String shortcutId, Bundle activityOptions,
-                UserHandle user) {
+        public void startShortcut(String packageName, String shortcutId, int stage, int position,
+                @Nullable Bundle options, UserHandle user) {
             try {
                 LauncherApps launcherApps =
                         mContext.getSystemService(LauncherApps.class);
                 launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
-                        activityOptions, user);
+                        options, user);
             } catch (ActivityNotFoundException e) {
                 Slog.e(TAG, "Failed to launch shortcut", e);
             }
         }
 
         @Override
-        public void startIntent(PendingIntent intent, Bundle activityOptions) {
+        public void startIntent(PendingIntent intent, int stage, int position,
+                @Nullable Bundle options) {
             try {
-                intent.send(null, 0, null, null, null, null, activityOptions);
+                intent.send(null, 0, null, null, null, null, options);
             } catch (PendingIntent.CanceledException e) {
                 Slog.e(TAG, "Failed to launch activity", e);
             }
@@ -339,14 +345,12 @@
 
         @Override
         public void enterSplitScreen(int taskId, boolean leftOrTop) {
-            mSplitScreen.moveToSideStage(taskId,
-                    leftOrTop ? SIDE_STAGE_POSITION_TOP_OR_LEFT
-                            : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT);
+            throw new UnsupportedOperationException("enterSplitScreen not implemented by starter");
         }
 
         @Override
         public void exitSplitScreen() {
-            mSplitScreen.exitSplitScreen();
+            throw new UnsupportedOperationException("exitSplitScreen not implemented by starter");
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
index d22abe4..125e322 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@@ -24,6 +24,7 @@
 import android.view.SurfaceControl;
 import android.view.animation.Interpolator;
 import android.view.animation.OvershootInterpolator;
+import android.window.WindowContainerToken;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -55,7 +56,7 @@
 
     private final Interpolator mOvershootInterpolator;
     private final OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper;
-    private final HashMap<SurfaceControl, OneHandedTransitionAnimator> mAnimatorMap =
+    private final HashMap<WindowContainerToken, OneHandedTransitionAnimator> mAnimatorMap =
             new HashMap<>();
 
     /**
@@ -67,23 +68,23 @@
     }
 
     @SuppressWarnings("unchecked")
-    OneHandedTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds,
-            Rect endBounds) {
-        final OneHandedTransitionAnimator animator = mAnimatorMap.get(leash);
+    OneHandedTransitionAnimator getAnimator(WindowContainerToken token, SurfaceControl leash,
+            Rect startBounds, Rect endBounds) {
+        final OneHandedTransitionAnimator animator = mAnimatorMap.get(token);
         if (animator == null) {
-            mAnimatorMap.put(leash, setupOneHandedTransitionAnimator(
-                    OneHandedTransitionAnimator.ofBounds(leash, startBounds, endBounds)));
+            mAnimatorMap.put(token, setupOneHandedTransitionAnimator(
+                    OneHandedTransitionAnimator.ofBounds(token, leash, startBounds, endBounds)));
         } else if (animator.isRunning()) {
             animator.updateEndValue(endBounds);
         } else {
             animator.cancel();
-            mAnimatorMap.put(leash, setupOneHandedTransitionAnimator(
-                    OneHandedTransitionAnimator.ofBounds(leash, startBounds, endBounds)));
+            mAnimatorMap.put(token, setupOneHandedTransitionAnimator(
+                    OneHandedTransitionAnimator.ofBounds(token, leash, startBounds, endBounds)));
         }
-        return mAnimatorMap.get(leash);
+        return mAnimatorMap.get(token);
     }
 
-    HashMap<SurfaceControl, OneHandedTransitionAnimator> getAnimatorMap() {
+    HashMap<WindowContainerToken, OneHandedTransitionAnimator> getAnimatorMap() {
         return mAnimatorMap;
     }
 
@@ -91,8 +92,8 @@
         return mAnimatorMap.isEmpty();
     }
 
-    void removeAnimator(SurfaceControl key) {
-        final OneHandedTransitionAnimator animator = mAnimatorMap.remove(key);
+    void removeAnimator(WindowContainerToken token) {
+        final OneHandedTransitionAnimator animator = mAnimatorMap.remove(token);
         if (animator != null && animator.isRunning()) {
             animator.cancel();
         }
@@ -116,6 +117,7 @@
             ValueAnimator.AnimatorListener {
 
         private final SurfaceControl mLeash;
+        private final WindowContainerToken mToken;
         private T mStartValue;
         private T mEndValue;
         private T mCurrentValue;
@@ -128,8 +130,10 @@
 
         private @TransitionDirection int mTransitionDirection;
 
-        private OneHandedTransitionAnimator(SurfaceControl leash, T startValue, T endValue) {
+        private OneHandedTransitionAnimator(WindowContainerToken token, SurfaceControl leash,
+                T startValue, T endValue) {
             mLeash = leash;
+            mToken = token;
             mStartValue = startValue;
             mEndValue = endValue;
             addListener(this);
@@ -208,8 +212,8 @@
             return this;
         }
 
-        SurfaceControl getLeash() {
-            return mLeash;
+        WindowContainerToken getToken() {
+            return mToken;
         }
 
         Rect getDestinationBounds() {
@@ -254,10 +258,10 @@
         }
 
         @VisibleForTesting
-        static OneHandedTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
-                Rect startValue, Rect endValue) {
+        static OneHandedTransitionAnimator<Rect> ofBounds(WindowContainerToken token,
+                SurfaceControl leash, Rect startValue, Rect endValue) {
 
-            return new OneHandedTransitionAnimator<Rect>(leash, new Rect(startValue),
+            return new OneHandedTransitionAnimator<Rect>(token, leash, new Rect(startValue),
                     new Rect(endValue)) {
 
                 private final Rect mTmpRect = new Rect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index a74f476..37a91d0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -56,7 +56,7 @@
     private final float[] mColor;
     private final float mAlpha;
     private final Rect mRect;
-    private final Handler mHandler;
+    private final Executor mMainExecutor;
     private final Point mDisplaySize = new Point();
     private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
@@ -76,13 +76,13 @@
                 @Override
                 public void onOneHandedAnimationStart(
                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
-                    mHandler.post(() -> showBackgroundPanelLayer());
+                    mMainExecutor.execute(() -> showBackgroundPanelLayer());
                 }
             };
 
     @Override
     public void onStopFinished(Rect bounds) {
-        mHandler.post(() -> removeBackgroundPanelLayer());
+        mMainExecutor.execute(() -> removeBackgroundPanelLayer());
     }
 
     public OneHandedBackgroundPanelOrganizer(Context context, DisplayController displayController,
@@ -94,7 +94,7 @@
         mColor = new float[]{defaultRGB, defaultRGB, defaultRGB};
         mAlpha = res.getFloat(R.dimen.config_one_handed_background_alpha);
         mRect = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
-        mHandler = new Handler();
+        mMainExecutor = executor;
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index d2d5591..04d1264 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -26,11 +26,11 @@
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
-import android.util.Log;
 import android.view.SurfaceControl;
 import android.window.DisplayAreaAppearedInfo;
 import android.window.DisplayAreaInfo;
 import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
@@ -44,8 +44,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Executor;
 
 /**
  * Manages OneHanded display areas such as offset.
@@ -69,7 +67,7 @@
     private int mEnterExitAnimationDurationMs;
 
     @VisibleForTesting
-    ArrayMap<DisplayAreaInfo, SurfaceControl> mDisplayAreaMap = new ArrayMap();
+    ArrayMap<WindowContainerToken, SurfaceControl> mDisplayAreaTokenMap = new ArrayMap();
     private DisplayController mDisplayController;
     private OneHandedAnimationController mAnimationController;
     private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
@@ -89,8 +87,9 @@
                 @Override
                 public void onOneHandedAnimationEnd(SurfaceControl.Transaction tx,
                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
-                    mAnimationController.removeAnimator(animator.getLeash());
+                    mAnimationController.removeAnimator(animator.getToken());
                     if (mAnimationController.isAnimatorsConsumed()) {
+                        resetWindowsOffsetInternal(animator.getTransitionDirection());
                         finishOffset(animator.getDestinationOffset(),
                                 animator.getTransitionDirection());
                     }
@@ -99,8 +98,9 @@
                 @Override
                 public void onOneHandedAnimationCancel(
                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
-                    mAnimationController.removeAnimator(animator.getLeash());
+                    mAnimationController.removeAnimator(animator.getToken());
                     if (mAnimationController.isAnimatorsConsumed()) {
+                        resetWindowsOffsetInternal(animator.getTransitionDirection());
                         finishOffset(animator.getDestinationOffset(),
                                 animator.getTransitionDirection());
                     }
@@ -119,7 +119,6 @@
         super(mainExecutor);
         mAnimationController = animationController;
         mDisplayController = displayController;
-        mDefaultDisplayBounds.set(getDisplayBounds());
         mLastVisualDisplayBounds.set(getDisplayBounds());
         final int animationDurationConfig = context.getResources().getInteger(
                 R.integer.config_one_handed_translate_animation_duration);
@@ -134,24 +133,12 @@
     @Override
     public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
             @NonNull SurfaceControl leash) {
-        Objects.requireNonNull(displayAreaInfo, "displayAreaInfo must not be null");
-        Objects.requireNonNull(leash, "leash must not be null");
-        if (mDisplayAreaMap.get(displayAreaInfo) == null) {
-            // mDefaultDisplayBounds may out of date after removeDisplayChangingController()
-            mDefaultDisplayBounds.set(getDisplayBounds());
-            mDisplayAreaMap.put(displayAreaInfo, leash);
-        }
+        mDisplayAreaTokenMap.put(displayAreaInfo.token, leash);
     }
 
     @Override
     public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
-        Objects.requireNonNull(displayAreaInfo,
-                "Requires valid displayArea, and displayArea must not be null");
-        if (!mDisplayAreaMap.containsKey(displayAreaInfo)) {
-            Log.w(TAG, "Unrecognized token: " + displayAreaInfo.token);
-            return;
-        }
-        mDisplayAreaMap.remove(displayAreaInfo);
+        mDisplayAreaTokenMap.remove(displayAreaInfo.token);
     }
 
     @Override
@@ -162,6 +149,7 @@
             final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
             onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
         }
+        mDefaultDisplayBounds.set(getDisplayBounds());
         return displayAreaInfos;
     }
 
@@ -176,9 +164,9 @@
      * handles 90 degree display rotation changes {@link Surface.Rotation}.
      *
      * @param fromRotation starting rotation of the display.
-     * @param toRotation target rotation of the display (after rotating).
-     * @param wct A task transaction {@link WindowContainerTransaction} from
-     *        {@link DisplayChangeController} to populate.
+     * @param toRotation   target rotation of the display (after rotating).
+     * @param wct          A task transaction {@link WindowContainerTransaction} from
+     *                     {@link DisplayChangeController} to populate.
      */
     public void onRotateDisplay(int fromRotation, int toRotation, WindowContainerTransaction wct) {
         // Stop one handed without animation and reset cropped size immediately
@@ -210,22 +198,32 @@
                 : TRANSITION_DIRECTION_EXIT;
 
         final WindowContainerTransaction wct = new WindowContainerTransaction();
-        mDisplayAreaMap.forEach(
-                (key, leash) -> {
-                    animateWindows(leash, fromBounds, toBounds, direction,
+        mDisplayAreaTokenMap.forEach(
+                (token, leash) -> {
+                    animateWindows(token, leash, fromBounds, toBounds, direction,
                             mEnterExitAnimationDurationMs);
-                    wct.setBounds(key.token, toBounds);
+                    wct.setBounds(token, toBounds);
                 });
         applyTransaction(wct);
     }
 
+    private void resetWindowsOffsetInternal(
+            @OneHandedAnimationController.TransitionDirection int td) {
+        if (td == TRANSITION_DIRECTION_TRIGGER) {
+            return;
+        }
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        resetWindowsOffset(wct);
+        applyTransaction(wct);
+    }
+
     private void resetWindowsOffset(WindowContainerTransaction wct) {
         final SurfaceControl.Transaction tx =
                 mSurfaceControlTransactionFactory.getTransaction();
-        mDisplayAreaMap.forEach(
-                (key, leash) -> {
+        mDisplayAreaTokenMap.forEach(
+                (token, leash) -> {
                     final OneHandedAnimationController.OneHandedTransitionAnimator animator =
-                            mAnimationController.getAnimatorMap().remove(leash);
+                            mAnimationController.getAnimatorMap().remove(token);
                     if (animator != null && animator.isRunning()) {
                         animator.cancel();
                     }
@@ -233,16 +231,17 @@
                             .setWindowCrop(leash, -1/* reset */, -1/* reset */);
                     // DisplayRotationController will applyTransaction() after finish rotating
                     if (wct != null) {
-                        wct.setBounds(key.token, null/* reset */);
+                        wct.setBounds(token, null/* reset */);
                     }
                 });
         tx.apply();
     }
 
-    private void animateWindows(SurfaceControl leash, Rect fromBounds, Rect toBounds,
-            @OneHandedAnimationController.TransitionDirection int direction, int durationMs) {
+    private void animateWindows(WindowContainerToken token, SurfaceControl leash, Rect fromBounds,
+            Rect toBounds, @OneHandedAnimationController.TransitionDirection int direction,
+            int durationMs) {
         final OneHandedAnimationController.OneHandedTransitionAnimator animator =
-                mAnimationController.getAnimator(leash, fromBounds, toBounds);
+                mAnimationController.getAnimator(token, leash, fromBounds, toBounds);
         if (animator != null) {
             animator.setTransitionDirection(direction)
                     .addOneHandedAnimationCallback(mOneHandedAnimationCallback)
@@ -311,8 +310,8 @@
         pw.println(TAG + "states: ");
         pw.print(innerPrefix + "mIsInOneHanded=");
         pw.println(mIsInOneHanded);
-        pw.print(innerPrefix + "mDisplayAreaMap=");
-        pw.println(mDisplayAreaMap);
+        pw.print(innerPrefix + "mDisplayAreaTokenMap=");
+        pw.println(mDisplayAreaTokenMap);
         pw.print(innerPrefix + "mDefaultDisplayBounds=");
         pw.println(mDefaultDisplayBounds);
         pw.print(innerPrefix + "mLastVisualDisplayBounds=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
index 1ed121f..49b7e05 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
@@ -221,8 +221,14 @@
                     displaySize.y);
             mInputMonitor = InputManager.getInstance().monitorGestureInput(
                     "onehanded-gesture-offset", DEFAULT_DISPLAY);
-            mInputEventReceiver = new EventReceiver(
-                    mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    mInputEventReceiver = new EventReceiver(
+                            mInputMonitor.getInputChannel(), Looper.myLooper());
+                });
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to create input event receiver", e);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
index 60709be..c7a49ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
@@ -132,8 +132,14 @@
         if (mIsEnabled) {
             mInputMonitor = InputManager.getInstance().monitorGestureInput(
                     "onehanded-touch", DEFAULT_DISPLAY);
-            mInputEventReceiver = new EventReceiver(
-                    mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    mInputEventReceiver = new EventReceiver(
+                            mInputMonitor.getInputChannel(), Looper.myLooper());
+                });
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to create input event receiver", e);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 1f07542..d14c3e3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -35,54 +35,17 @@
 @ExternalThread
 public interface Pip {
     /**
-     * Closes PIP (PIPed activity and PIP system UI).
-     */
-    default void closePip() {
-    }
-
-    /**
-     * Dump the current state and information if need.
-     *
-     * @param pw The stream to dump information to.
-     */
-    default void dump(PrintWriter pw) {
-    }
-
-    /**
      * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
      */
     default void expandPip() {
     }
 
     /**
-     * Get the touch handler which manages all the touch handling for PIP on the Phone,
-     * including moving, dismissing and expanding the PIP. (Do not use in TV)
-     *
-     * @return
-     */
-    default @Nullable PipTouchHandler getPipTouchHandler() {
-        return null;
-    }
-
-    /**
      * Hides the PIP menu.
      */
     default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}
 
     /**
-     * Returns {@code true} if PIP is shown.
-     */
-    default boolean isPipShown() {
-        return false;
-    }
-
-    /**
-     * Moves the PIPed activity to the fullscreen and closes PIP system UI.
-     */
-    default void movePipToFullscreen() {
-    }
-
-    /**
      * Called when configuration is changed.
      */
     default void onConfigurationChanged(Configuration newConfig) {
@@ -101,12 +64,6 @@
     }
 
     /**
-     * Registers the session listener for the current user.
-     */
-    default void registerSessionListenerForCurrentUser() {
-    }
-
-    /**
      * Called when SysUI state changed.
      *
      * @param isSysUiStateValid Is SysUI state valid or not.
@@ -116,19 +73,9 @@
     }
 
     /**
-     * Resize the Pip to the appropriate size for the input state.
-     *
-     * @param state In Pip state also used to determine the new size for the Pip.
+     * Registers the session listener for the current user.
      */
-    default void resizePinnedStack(int state) {
-    }
-
-    /**
-     * Resumes resizing operation on the Pip that was previously suspended.
-     *
-     * @param reason The reason resizing operations on the Pip was suspended.
-     */
-    default void resumePipResizing(int reason) {
+    default void registerSessionListenerForCurrentUser() {
     }
 
     /**
@@ -162,14 +109,6 @@
     default void showPictureInPictureMenu() {}
 
     /**
-     * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called.
-     *
-     * @param reason The reason for suspending resizing operations on the Pip.
-     */
-    default void suspendPipResizing(int reason) {
-    }
-
-    /**
      * Called by Launcher when swiping an auto-pip enabled Activity to home starts
      * @param componentName {@link ComponentName} represents the Activity entering PiP
      * @param activityInfo {@link ActivityInfo} tied to the Activity
@@ -199,4 +138,12 @@
      * PiP and the Back-from-Edge gesture.
      */
     default void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
+
+    /**
+     * Dump the current state and information if need.
+     *
+     * @param pw The stream to dump information to.
+     */
+    default void dump(PrintWriter pw) {
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index aeea10d..45aa387 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -21,6 +21,7 @@
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.app.TaskInfo;
 import android.graphics.Rect;
 import android.view.Choreographer;
 import android.view.SurfaceControl;
@@ -99,18 +100,20 @@
 
     @SuppressWarnings("unchecked")
     @VisibleForTesting
-    public PipTransitionAnimator getAnimator(SurfaceControl leash,
+    public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
             Rect destinationBounds, float alphaStart, float alphaEnd) {
         if (mCurrentAnimator == null) {
             mCurrentAnimator = setupPipTransitionAnimator(
-                    PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
+                    PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
+                            alphaEnd));
         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
                 && mCurrentAnimator.isRunning()) {
             mCurrentAnimator.updateEndValue(alphaEnd);
         } else {
             mCurrentAnimator.cancel();
             mCurrentAnimator = setupPipTransitionAnimator(
-                    PipTransitionAnimator.ofAlpha(leash, destinationBounds, alphaStart, alphaEnd));
+                    PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
+                            alphaEnd));
         }
         return mCurrentAnimator;
     }
@@ -131,13 +134,13 @@
      * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed.
      */
     @VisibleForTesting
-    public PipTransitionAnimator getAnimator(SurfaceControl leash, Rect baseBounds,
-            Rect startBounds, Rect endBounds, Rect sourceHintRect,
+    public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
+            Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
             @PipAnimationController.TransitionDirection int direction, float startingAngle) {
         if (mCurrentAnimator == null) {
             mCurrentAnimator = setupPipTransitionAnimator(
-                    PipTransitionAnimator.ofBounds(leash, startBounds, startBounds, endBounds,
-                            sourceHintRect, direction, 0 /* startingAngle */));
+                    PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds,
+                            endBounds, sourceHintRect, direction, 0 /* startingAngle */));
         } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
                 && mCurrentAnimator.isRunning()) {
             // If we are still animating the fade into pip, then just move the surface and ensure
@@ -152,8 +155,8 @@
         } else {
             mCurrentAnimator.cancel();
             mCurrentAnimator = setupPipTransitionAnimator(
-                    PipTransitionAnimator.ofBounds(leash, baseBounds, startBounds, endBounds,
-                            sourceHintRect, direction, startingAngle));
+                    PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds,
+                            endBounds, sourceHintRect, direction, startingAngle));
         }
         return mCurrentAnimator;
     }
@@ -177,18 +180,18 @@
         /**
          * Called when PiP animation is started.
          */
-        public void onPipAnimationStart(PipTransitionAnimator animator) {}
+        public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {}
 
         /**
          * Called when PiP animation is ended.
          */
-        public void onPipAnimationEnd(SurfaceControl.Transaction tx,
+        public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
                 PipTransitionAnimator animator) {}
 
         /**
          * Called when PiP animation is cancelled.
          */
-        public void onPipAnimationCancel(PipTransitionAnimator animator) {}
+        public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {}
     }
 
     /**
@@ -198,6 +201,7 @@
     public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements
             ValueAnimator.AnimatorUpdateListener,
             ValueAnimator.AnimatorListener {
+        private final TaskInfo mTaskInfo;
         private final SurfaceControl mLeash;
         private final @AnimationType int mAnimationType;
         private final Rect mDestinationBounds = new Rect();
@@ -213,9 +217,10 @@
         private PipSurfaceTransactionHelper mSurfaceTransactionHelper;
         private @TransitionDirection int mTransitionDirection;
 
-        private PipTransitionAnimator(SurfaceControl leash, @AnimationType int animationType,
-                Rect destinationBounds, T baseValue, T startValue, T endValue,
-                float startingAngle) {
+        private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash,
+                @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue,
+                T endValue, float startingAngle) {
+            mTaskInfo = taskInfo;
             mLeash = leash;
             mAnimationType = animationType;
             mDestinationBounds.set(destinationBounds);
@@ -234,7 +239,7 @@
             mCurrentValue = mStartValue;
             onStartTransaction(mLeash, newSurfaceControlTransaction());
             if (mPipAnimationCallback != null) {
-                mPipAnimationCallback.onPipAnimationStart(this);
+                mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this);
             }
         }
 
@@ -250,14 +255,14 @@
             final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
             onEndTransaction(mLeash, tx, mTransitionDirection);
             if (mPipAnimationCallback != null) {
-                mPipAnimationCallback.onPipAnimationEnd(tx, this);
+                mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this);
             }
         }
 
         @Override
         public void onAnimationCancel(Animator animation) {
             if (mPipAnimationCallback != null) {
-                mPipAnimationCallback.onPipAnimationCancel(this);
+                mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this);
             }
         }
 
@@ -368,9 +373,9 @@
         abstract void applySurfaceControlTransaction(SurfaceControl leash,
                 SurfaceControl.Transaction tx, float fraction);
 
-        static PipTransitionAnimator<Float> ofAlpha(SurfaceControl leash,
+        static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash,
                 Rect destinationBounds, float startValue, float endValue) {
-            return new PipTransitionAnimator<Float>(leash, ANIM_TYPE_ALPHA,
+            return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA,
                     destinationBounds, startValue, startValue, endValue, 0) {
                 @Override
                 void applySurfaceControlTransaction(SurfaceControl leash,
@@ -403,7 +408,7 @@
             };
         }
 
-        static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
+        static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash,
                 Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect,
                 @PipAnimationController.TransitionDirection int direction, float startingAngle) {
             // Just for simplicity we'll interpolate between the source rect hint insets and empty
@@ -427,7 +432,7 @@
             final Rect sourceInsets = new Rect(0, 0, 0, 0);
 
             // construct new Rect instances in case they are recycled
-            return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
+            return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
                     endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue),
                     startingAngle) {
                 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
@@ -469,6 +474,11 @@
                     getSurfaceTransactionHelper()
                             .alpha(tx, leash, 1f)
                             .round(tx, leash, shouldApplyCornerRadius());
+                    // TODO(b/178632364): this is a work around for the black background when
+                    // entering PiP in buttion navigation mode.
+                    if (isInPipDirection(direction)) {
+                        tx.setWindowCrop(leash, getStartValue());
+                    }
                     tx.show(leash);
                     tx.apply();
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index f839727..ac5d14c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -19,7 +19,9 @@
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
 import android.annotation.NonNull;
+import android.app.PictureInPictureParams;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -27,9 +29,10 @@
 import android.util.DisplayMetrics;
 import android.util.Size;
 import android.util.TypedValue;
-import android.view.DisplayInfo;
 import android.view.Gravity;
 
+import com.android.wm.shell.common.DisplayLayout;
+
 import java.io.PrintWriter;
 
 /**
@@ -140,11 +143,53 @@
                 true /* useCurrentMinEdgeSize */, false /* useCurrentSize */);
     }
 
+    /**
+     *
+     * Get the smallest/most minimal size allowed.
+     */
+    public Size getMinimalSize(ActivityInfo activityInfo) {
+        if (activityInfo == null || activityInfo.windowLayout == null) {
+            return null;
+        }
+        final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
+        // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
+        // without minWidth/minHeight
+        if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
+            return new Size(windowLayout.minWidth, windowLayout.minHeight);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the source hint rect if it is valid (if provided and is contained by the current
+     * task bounds).
+     */
+    public static Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds) {
+        final Rect sourceHintRect = params != null && params.hasSourceBoundsHint()
+                ? params.getSourceRectHint()
+                : null;
+        if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
+            return sourceHintRect;
+        }
+        return null;
+    }
+
     public float getDefaultAspectRatio() {
         return mDefaultAspectRatio;
     }
 
     /**
+     *
+     * Give the aspect ratio if the supplied PiP params have one, or else return default.
+     */
+    public float getAspectRatioOrDefault(
+            @android.annotation.Nullable PictureInPictureParams params) {
+        return params != null && params.hasSetAspectRatio()
+                ? params.getAspectRatio()
+                : getDefaultAspectRatio();
+    }
+
+    /**
      * @return whether the given {@param aspectRatio} is valid.
      */
     private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
@@ -190,9 +235,9 @@
                 size = adjustSizeToAspectRatio(overrideMinSize, aspectRatio);
             } else {
                 // Calculate the default size using the display size and default min edge size.
-                final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
+                final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
                 size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
-                        displayInfo.logicalWidth, displayInfo.logicalHeight);
+                        displayLayout.width(), displayLayout.height());
             }
         }
 
@@ -232,7 +277,7 @@
         final Size defaultSize;
         final Rect insetBounds = new Rect();
         getInsetBounds(insetBounds);
-        final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
+        final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
         final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
         if (overrideMinSize != null) {
             // The override minimal size is set, use that as the default size making sure it's
@@ -241,7 +286,7 @@
         } else {
             // Calculate the default size using the display size and default min edge size.
             defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
-                    mDefaultMinSize, displayInfo.logicalWidth, displayInfo.logicalHeight);
+                    mDefaultMinSize, displayLayout.width(), displayLayout.height());
         }
 
         // Now that we have the default size, apply the snap fraction if valid or position the
@@ -264,12 +309,12 @@
      * Populates the bounds on the screen that the PIP can be visible in.
      */
     public void getInsetBounds(Rect outRect) {
-        final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
+        final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
         Rect insets = mPipBoundsState.getDisplayLayout().stableInsets();
         outRect.set(insets.left + mScreenEdgeInsets.x,
                 insets.top + mScreenEdgeInsets.y,
-                displayInfo.logicalWidth - insets.right - mScreenEdgeInsets.x,
-                displayInfo.logicalHeight - insets.bottom - mScreenEdgeInsets.y);
+                displayLayout.width() - insets.right - mScreenEdgeInsets.x,
+                displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 9595b5a..cb39b4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -24,7 +24,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Size;
-import android.view.DisplayInfo;
+import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.function.TriConsumer;
@@ -68,7 +68,7 @@
     private int mStashOffset;
     private @Nullable PipReentryState mPipReentryState;
     private @Nullable ComponentName mLastPipComponentName;
-    private final @NonNull DisplayInfo mDisplayInfo = new DisplayInfo();
+    private int mDisplayId = Display.DEFAULT_DISPLAY;
     private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
     /** The current minimum edge size of PIP. */
     private int mMinEdgeSize;
@@ -238,26 +238,20 @@
         return mLastPipComponentName;
     }
 
-    /** Get the current display info. */
-    @NonNull
-    public DisplayInfo getDisplayInfo() {
-        return mDisplayInfo;
+    /** Get the current display id. */
+    public int getDisplayId() {
+        return mDisplayId;
     }
 
-    /** Update the display info. */
-    public void setDisplayInfo(@NonNull DisplayInfo displayInfo) {
-        mDisplayInfo.copyFrom(displayInfo);
-    }
-
-    /** Set the rotation of the display. */
-    public void setDisplayRotation(int rotation) {
-        mDisplayInfo.rotation = rotation;
+    /** Set the current display id for the associated display layout. */
+    public void setDisplayId(int displayId) {
+        mDisplayId = displayId;
     }
 
     /** Returns the display's bounds. */
     @NonNull
     public Rect getDisplayBounds() {
-        return new Rect(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
     }
 
     /** Update the display layout. */
@@ -349,6 +343,16 @@
         }
     }
 
+    /**
+     * Initialize states when first entering PiP.
+     */
+    public void setBoundsStateForEntry(ComponentName componentName, float aspectRatio,
+            Size overrideMinSize) {
+        setLastPipComponentName(componentName);
+        setAspectRatio(aspectRatio);
+        setOverrideMinSize(overrideMinSize);
+    }
+
     /** Returns whether the shelf is currently showing. */
     public boolean isShelfShowing() {
         return mIsShelfShowing;
@@ -474,7 +478,7 @@
         pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds);
         pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
         pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
-        pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
+        pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
         pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
         pw.println(innerPrefix + "mStashedState=" + mStashedState);
         pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index d96d4d0..1a4616c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -31,6 +31,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
 import android.media.session.PlaybackState;
+import android.os.Handler;
 import android.os.UserHandle;
 
 import androidx.annotation.Nullable;
@@ -74,6 +75,7 @@
     }
 
     private final Context mContext;
+    private final Handler mMainHandler;
 
     private final MediaSessionManager mMediaSessionManager;
     private MediaController mMediaController;
@@ -118,15 +120,16 @@
     private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
     private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
 
-    public PipMediaController(Context context) {
+    public PipMediaController(Context context, Handler mainHandler) {
         mContext = context;
+        mMainHandler = mainHandler;
         IntentFilter mediaControlFilter = new IntentFilter();
         mediaControlFilter.addAction(ACTION_PLAY);
         mediaControlFilter.addAction(ACTION_PAUSE);
         mediaControlFilter.addAction(ACTION_NEXT);
         mediaControlFilter.addAction(ACTION_PREV);
-        mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter,
-                UserHandle.USER_ALL);
+        mContext.registerReceiverForAllUsers(mPlayPauseActionReceiver, mediaControlFilter,
+                null /* permission */, mainHandler);
 
         createMediaActions();
         mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
@@ -245,7 +248,7 @@
     public void registerSessionListenerForCurrentUser() {
         mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
         mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null,
-                UserHandle.CURRENT, null);
+                UserHandle.CURRENT, mMainHandler);
     }
 
     /**
@@ -277,7 +280,7 @@
             }
             mMediaController = controller;
             if (controller != null) {
-                controller.registerCallback(mPlaybackChangedListener);
+                controller.registerCallback(mPlaybackChangedListener, mMainHandler);
             }
             notifyActionsChanged();
             notifyMetadataChanged(getMediaMetadata());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 1279cd3..fb83006 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -41,22 +41,20 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.PictureInPictureParams;
+import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Rational;
-import android.util.Size;
+import android.view.Display;
 import android.view.SurfaceControl;
 import android.window.TaskOrganizer;
 import android.window.WindowContainerToken;
@@ -64,18 +62,17 @@
 import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipUpdateThread;
+import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -98,12 +95,6 @@
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final int MSG_RESIZE_IMMEDIATE = 1;
-    private static final int MSG_RESIZE_ANIMATE = 2;
-    private static final int MSG_OFFSET_ANIMATE = 3;
-    private static final int MSG_FINISH_RESIZE = 4;
-    private static final int MSG_RESIZE_USER = 5;
-
     // Not a complete set of states but serves what we want right now.
     private enum State {
         UNDEFINED(0),
@@ -135,25 +126,25 @@
         }
     }
 
-    private final Handler mMainHandler;
-    private final Handler mUpdateHandler;
     private final PipBoundsState mPipBoundsState;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     private final @NonNull PipMenuController mPipMenuController;
     private final PipAnimationController mPipAnimationController;
+    private final PipTransitionController mPipTransitionController;
     private final PipUiEventLogger mPipUiEventLoggerLogger;
-    private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
     private final int mEnterExitAnimationDuration;
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
     private final Optional<LegacySplitScreen> mSplitScreenOptional;
     protected final ShellTaskOrganizer mTaskOrganizer;
+    protected final ShellExecutor mMainExecutor;
 
     // These callbacks are called on the update thread
     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
             new PipAnimationController.PipAnimationCallback() {
         @Override
-        public void onPipAnimationStart(PipAnimationController.PipTransitionAnimator animator) {
+        public void onPipAnimationStart(TaskInfo taskInfo,
+                PipAnimationController.PipTransitionAnimator animator) {
             final int direction = animator.getTransitionDirection();
             if (direction == TRANSITION_DIRECTION_TO_PIP) {
                 // TODO (b//169221267): Add jank listener for transactions without buffer updates.
@@ -164,7 +155,7 @@
         }
 
         @Override
-        public void onPipAnimationEnd(SurfaceControl.Transaction tx,
+        public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
                 PipAnimationController.PipTransitionAnimator animator) {
             final int direction = animator.getTransitionDirection();
             finishResize(tx, animator.getDestinationBounds(), direction,
@@ -178,73 +169,12 @@
         }
 
         @Override
-        public void onPipAnimationCancel(PipAnimationController.PipTransitionAnimator animator) {
+        public void onPipAnimationCancel(TaskInfo taskInfo,
+                PipAnimationController.PipTransitionAnimator animator) {
             sendOnPipTransitionCancelled(animator.getTransitionDirection());
         }
     };
 
-    @SuppressWarnings("unchecked")
-    private final Handler.Callback mUpdateCallbacks = (msg) -> {
-        SomeArgs args = (SomeArgs) msg.obj;
-        Consumer<Rect> updateBoundsCallback = (Consumer<Rect>) args.arg1;
-        switch (msg.what) {
-            case MSG_RESIZE_IMMEDIATE: {
-                Rect toBounds = (Rect) args.arg2;
-                resizePip(toBounds);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-            case MSG_RESIZE_ANIMATE: {
-                Rect currentBounds = (Rect) args.arg2;
-                Rect toBounds = (Rect) args.arg3;
-                Rect sourceHintRect = (Rect) args.arg4;
-                float startingAngle = (float) args.arg5;
-                int duration = args.argi2;
-                animateResizePip(currentBounds, toBounds, sourceHintRect,
-                        args.argi1 /* direction */, duration, startingAngle);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-            case MSG_OFFSET_ANIMATE: {
-                Rect originalBounds = (Rect) args.arg2;
-                final int offset = args.argi1;
-                final int duration = args.argi2;
-                offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
-                Rect toBounds = new Rect(originalBounds);
-                toBounds.offset(0, offset);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-            case MSG_FINISH_RESIZE: {
-                SurfaceControl.Transaction tx = (SurfaceControl.Transaction) args.arg2;
-                Rect toBounds = (Rect) args.arg3;
-                finishResize(tx, toBounds, args.argi1 /* direction */, -1);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-            case MSG_RESIZE_USER: {
-                Rect startBounds = (Rect) args.arg2;
-                Rect toBounds = (Rect) args.arg3;
-                float degrees = (float) args.arg4;
-                userResizePip(startBounds, toBounds, degrees);
-                if (updateBoundsCallback != null) {
-                    updateBoundsCallback.accept(toBounds);
-                }
-                break;
-            }
-        }
-        args.recycle();
-        return true;
-    };
-
     private ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mToken;
     private SurfaceControl mLeash;
@@ -272,30 +202,33 @@
     public PipTaskOrganizer(Context context, @NonNull PipBoundsState pipBoundsState,
             @NonNull PipBoundsAlgorithm boundsHandler,
             @NonNull PipMenuController pipMenuController,
+            @NonNull PipAnimationController pipAnimationController,
             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
+            @NonNull PipTransitionController pipTransitionController,
             Optional<LegacySplitScreen> splitScreenOptional,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
-            @NonNull ShellTaskOrganizer shellTaskOrganizer) {
-        mMainHandler = new Handler(Looper.getMainLooper());
-        mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
+            @NonNull ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
         mPipBoundsState = pipBoundsState;
         mPipBoundsAlgorithm = boundsHandler;
         mPipMenuController = pipMenuController;
+        mPipTransitionController = pipTransitionController;
         mEnterExitAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
         mSurfaceTransactionHelper = surfaceTransactionHelper;
-        mPipAnimationController = new PipAnimationController(mSurfaceTransactionHelper);
+        mPipAnimationController = pipAnimationController;
         mPipUiEventLoggerLogger = pipUiEventLogger;
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
         mSplitScreenOptional = splitScreenOptional;
         mTaskOrganizer = shellTaskOrganizer;
-        mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
-        displayController.addDisplayWindowListener(this);
-    }
+        mMainExecutor = mainExecutor;
 
-    public Handler getUpdateHandler() {
-        return mUpdateHandler;
+        // TODO: Can be removed once wm components are created on the shell-main thread
+        mMainExecutor.execute(() -> {
+            mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
+        });
+        displayController.addDisplayWindowListener(this);
     }
 
     public Rect getCurrentOrAnimatingBounds() {
@@ -316,13 +249,6 @@
     }
 
     /**
-     * Registers {@link PipTransitionCallback} to receive transition callbacks.
-     */
-    public void registerPipTransitionCallback(PipTransitionCallback callback) {
-        mPipTransitionCallbacks.add(callback);
-    }
-
-    /**
      * Registers a callback when a display change has been detected when we enter PiP.
      */
     public void registerOnDisplayIdChangeCallback(IntConsumer onDisplayIdChangeCallback) {
@@ -345,7 +271,7 @@
     public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
             PictureInPictureParams pictureInPictureParams) {
         mInSwipePipToHomeTransition = true;
-        sendOnPipTransitionStarted(componentName, TRANSITION_DIRECTION_TO_PIP);
+        sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
         setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
         // disable the conflicting transaction from fixed rotation, see also
         // onFixedRotationStarted and onFixedRotationFinished
@@ -366,9 +292,9 @@
 
     private void setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params,
             ActivityInfo activityInfo) {
-        mPipBoundsState.setLastPipComponentName(componentName);
-        mPipBoundsState.setAspectRatio(getAspectRatioOrDefault(params));
-        mPipBoundsState.setOverrideMinSize(getMinimalSize(activityInfo));
+        mPipBoundsState.setBoundsStateForEntry(componentName,
+                mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
+                mPipBoundsAlgorithm.getMinimalSize(activityInfo));
     }
 
     /**
@@ -395,7 +321,7 @@
         mPipUiEventLoggerLogger.log(
                 PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
         final boolean orientationDiffers = initialConfig.windowConfiguration.getRotation()
-                != mPipBoundsState.getDisplayInfo().rotation;
+                != mPipBoundsState.getDisplayLayout().rotation();
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         final Rect destinationBounds = initialConfig.windowConfiguration.getBounds();
         final int direction = syncWithSplitScreenBounds(destinationBounds)
@@ -428,15 +354,17 @@
             mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
                 @Override
                 public void onTransactionReady(int id, SurfaceControl.Transaction t) {
-                    t.apply();
-                    // Make sure to grab the latest source hint rect as it could have been updated
-                    // right after applying the windowing mode change.
-                    final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams,
-                            destinationBounds);
-                    scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
-                            0 /* startingAngle */, sourceHintRect, direction, animationDurationMs,
-                            null /* updateBoundsCallback */);
-                    mState = State.EXITING_PIP;
+                    mMainExecutor.execute(() -> {
+                        t.apply();
+                        // Make sure to grab the latest source hint rect as it could have been
+                        // updated right after applying the windowing mode change.
+                        final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+                                mPictureInPictureParams, destinationBounds);
+                        scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
+                                0 /* startingAngle */, sourceHintRect, direction,
+                                animationDurationMs, null /* updateBoundsCallback */);
+                        mState = State.EXITING_PIP;
+                    });
                 }
             });
         }
@@ -465,12 +393,12 @@
         }
 
         // removePipImmediately is expected when the following animation finishes.
-        mUpdateHandler.post(() -> mPipAnimationController
-                .getAnimator(mLeash, mPipBoundsState.getBounds(), 1f, 0f)
+        mPipAnimationController
+                .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), 1f, 0f)
                 .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration)
-                .start());
+                .start();
         mInitialState.remove(mToken.asBinder());
         mState = State.EXITING_PIP;
     }
@@ -506,7 +434,7 @@
 
         // If the displayId of the task is different than what PipBoundsHandler has, then update
         // it. This is possible if we entered PiP on an external display.
-        if (info.displayId != mPipBoundsState.getDisplayInfo().displayId
+        if (info.displayId != mPipBoundsState.getDisplayId()
                 && mOnDisplayIdChangeCallback != null) {
             mOnDisplayIdChangeCallback.accept(info.displayId);
         }
@@ -538,10 +466,17 @@
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
 
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+                mPipMenuController.attach(mLeash);
+            }
+            return;
+        }
+
         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
             mPipMenuController.attach(mLeash);
-            final Rect sourceHintRect = getValidSourceHintRect(info.pictureInPictureParams,
-                    currentBounds);
+            final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+                    info.pictureInPictureParams, currentBounds);
             scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
                     sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
                     null /* updateBoundsCallback */);
@@ -554,21 +489,6 @@
         }
     }
 
-    /**
-     * Returns the source hint rect if it is valid (if provided and is contained by the current
-     * task bounds).
-     */
-    private Rect getValidSourceHintRect(PictureInPictureParams params, Rect sourceBounds) {
-        final Rect sourceHintRect = params != null
-                && params.hasSourceBoundsHint()
-                ? params.getSourceRectHint()
-                : null;
-        if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
-            return sourceHintRect;
-        }
-        return null;
-    }
-
     @VisibleForTesting
     void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
         // If we are fading the PIP in, then we should move the pip to the final location as
@@ -579,12 +499,12 @@
         tx.setAlpha(mLeash, 0f);
         tx.apply();
         applyEnterPipSyncTransaction(destinationBounds, () -> {
-            mUpdateHandler.post(() -> mPipAnimationController
-                    .getAnimator(mLeash, destinationBounds, 0f, 1f)
+            mPipAnimationController
+                    .getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
                     .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                     .setPipAnimationCallback(mPipAnimationCallback)
                     .setDuration(durationMs)
-                    .start());
+                    .start();
             // mState is set right after the animation is kicked off to block any resize
             // requests such as offsetPip that may have been called prior to the transition.
             mState = State.ENTERING_PIP;
@@ -599,34 +519,26 @@
         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
         wct.setBounds(mToken, destinationBounds);
         wct.scheduleFinishEnterPip(mToken, destinationBounds);
+        // TODO: Migrate to SyncTransactionQueue
         mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
             @Override
             public void onTransactionReady(int id, SurfaceControl.Transaction t) {
-                t.apply();
-                if (runnable != null) {
-                    runnable.run();
-                }
+                mMainExecutor.execute(() -> {
+                    t.apply();
+                    if (runnable != null) {
+                        runnable.run();
+                    }
+                });
             }
         });
     }
 
     private void sendOnPipTransitionStarted(
             @PipAnimationController.TransitionDirection int direction) {
-        sendOnPipTransitionStarted(mTaskInfo.baseActivity, direction);
-    }
-
-    private void sendOnPipTransitionStarted(ComponentName componentName,
-            @PipAnimationController.TransitionDirection int direction) {
         if (direction == TRANSITION_DIRECTION_TO_PIP) {
             mState = State.ENTERING_PIP;
         }
-        final Rect pipBounds = mPipBoundsState.getBounds();
-        runOnMainHandler(() -> {
-            for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-                final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-                callback.onPipTransitionStarted(componentName, direction, pipBounds);
-            }
-        });
+        mPipTransitionController.sendOnPipTransitionStarted(direction);
     }
 
     private void sendOnPipTransitionFinished(
@@ -634,30 +546,12 @@
         if (direction == TRANSITION_DIRECTION_TO_PIP) {
             mState = State.ENTERED_PIP;
         }
-        runOnMainHandler(() -> {
-            for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-                final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-                callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction);
-            }
-        });
+        mPipTransitionController.sendOnPipTransitionFinished(direction);
     }
 
     private void sendOnPipTransitionCancelled(
             @PipAnimationController.TransitionDirection int direction) {
-        runOnMainHandler(() -> {
-            for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
-                final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
-                callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction);
-            }
-        });
-    }
-
-    private void runOnMainHandler(Runnable r) {
-        if (Looper.getMainLooper() == Looper.myLooper()) {
-            r.run();
-        } else {
-            mMainHandler.post(r);
-        }
+        mPipTransitionController.sendOnPipTransitionCancelled(direction);
     }
 
     /**
@@ -685,13 +579,18 @@
         mState = State.UNDEFINED;
         mPipUiEventLoggerLogger.setTaskInfo(null);
         mPipMenuController.detach();
+
+        if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) {
+            mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
+        }
     }
 
     @Override
     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
         mPipBoundsState.setLastPipComponentName(info.topActivity);
-        mPipBoundsState.setOverrideMinSize(getMinimalSize(info.topActivityInfo));
+        mPipBoundsState.setOverrideMinSize(
+                mPipBoundsAlgorithm.getMinimalSize(info.topActivityInfo));
         final PictureInPictureParams newParams = info.pictureInPictureParams;
         if (newParams == null || !applyPictureInPictureParams(newParams)) {
             Log.d(TAG, "Ignored onTaskInfoChanged with PiP param: " + newParams);
@@ -872,15 +771,11 @@
             return;
         }
 
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = currentBounds;
-        args.arg3 = destinationBounds;
-        args.arg4 = sourceHintRect;
-        args.arg5 = startingAngle;
-        args.argi1 = direction;
-        args.argi2 = durationMs;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
+        animateResizePip(currentBounds, destinationBounds, sourceHintRect, direction, durationMs,
+                startingAngle);
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(destinationBounds);
+        }
     }
 
     /**
@@ -888,10 +783,24 @@
      * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
      */
     public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) {
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = toBounds;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_IMMEDIATE, args));
+        // Could happen when exitPip
+        if (mToken == null || mLeash == null) {
+            Log.w(TAG, "Abort animation, invalid leash");
+            return;
+        }
+        mPipBoundsState.setBounds(toBounds);
+        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+        mSurfaceTransactionHelper
+                .crop(tx, mLeash, toBounds)
+                .round(tx, mLeash, mState.isInPip());
+        if (mPipMenuController.isMenuVisible()) {
+            mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
+        } else {
+            tx.apply();
+        }
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(toBounds);
+        }
     }
 
     /**
@@ -909,12 +818,27 @@
      */
     public void scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees,
             Consumer<Rect> updateBoundsCallback) {
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = startBounds;
-        args.arg3 = toBounds;
-        args.arg4 = degrees;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_USER, args));
+        // Could happen when exitPip
+        if (mToken == null || mLeash == null) {
+            Log.w(TAG, "Abort animation, invalid leash");
+            return;
+        }
+
+        if (startBounds.isEmpty() || toBounds.isEmpty()) {
+            Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
+            return;
+        }
+
+        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
+        mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, toBounds, degrees);
+        if (mPipMenuController.isMenuVisible()) {
+            mPipMenuController.movePipMenu(mLeash, tx, toBounds);
+        } else {
+            tx.apply();
+        }
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(toBounds);
+        }
     }
 
     /**
@@ -948,13 +872,11 @@
             return;
         }
 
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = createFinishResizeSurfaceTransaction(
-                destinationBounds);
-        args.arg3 = destinationBounds;
-        args.argi1 = direction;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_FINISH_RESIZE, args));
+        finishResize(createFinishResizeSurfaceTransaction(destinationBounds), destinationBounds,
+                direction, -1);
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(destinationBounds);
+        }
     }
 
     private SurfaceControl.Transaction createFinishResizeSurfaceTransaction(
@@ -979,20 +901,15 @@
             Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred");
             return;
         }
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = updateBoundsCallback;
-        args.arg2 = originalBounds;
-        // offset would be zero if triggered from screen rotation.
-        args.argi1 = offset;
-        args.argi2 = duration;
-        mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_OFFSET_ANIMATE, args));
+        offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
+        Rect toBounds = new Rect(originalBounds);
+        toBounds.offset(0, offset);
+        if (updateBoundsCallback != null) {
+            updateBoundsCallback.accept(toBounds);
+        }
     }
 
     private void offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleOffsetPip() instead of this "
-                    + "directly");
-        }
         if (mTaskInfo == null) {
             Log.w(TAG, "mTaskInfo is not set");
             return;
@@ -1003,62 +920,9 @@
                 TRANSITION_DIRECTION_SAME, durationMs, 0);
     }
 
-    private void resizePip(Rect destinationBounds) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
-                    + "directly");
-        }
-        // Could happen when exitPip
-        if (mToken == null || mLeash == null) {
-            Log.w(TAG, "Abort animation, invalid leash");
-            return;
-        }
-        mPipBoundsState.setBounds(destinationBounds);
-        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
-        mSurfaceTransactionHelper
-                .crop(tx, mLeash, destinationBounds)
-                .round(tx, mLeash, mState.isInPip());
-        if (mPipMenuController.isMenuVisible()) {
-            runOnMainHandler(() ->
-                    mPipMenuController.resizePipMenu(mLeash, tx, destinationBounds));
-        } else {
-            tx.apply();
-        }
-    }
-
-    private void userResizePip(Rect startBounds, Rect destinationBounds, float degrees) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleUserResizePip() instead of "
-                    + "this directly");
-        }
-        // Could happen when exitPip
-        if (mToken == null || mLeash == null) {
-            Log.w(TAG, "Abort animation, invalid leash");
-            return;
-        }
-
-        if (startBounds.isEmpty() || destinationBounds.isEmpty()) {
-            Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
-            return;
-        }
-
-        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
-        mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds, degrees);
-        if (mPipMenuController.isMenuVisible()) {
-            runOnMainHandler(() ->
-                    mPipMenuController.movePipMenu(mLeash, tx, destinationBounds));
-        } else {
-            tx.apply();
-        }
-    }
-
     private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds,
             @PipAnimationController.TransitionDirection int direction,
             @PipAnimationController.AnimationType int type) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
-                    + "directly");
-        }
         mPipBoundsState.setBounds(destinationBounds);
         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
             removePipImmediately();
@@ -1097,7 +961,7 @@
                     mSurfaceTransactionHelper.scale(t, snapshotSurface, snapshotSrc, snapshotDest);
                     t.apply();
 
-                    mUpdateHandler.post(() -> {
+                    mMainExecutor.execute(() -> {
                         // Start animation to fade out the snapshot.
                         final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
                         animator.setDuration(mEnterExitAnimationDuration);
@@ -1129,10 +993,8 @@
     }
 
     private void finishResizeForMenu(Rect destinationBounds) {
-        runOnMainHandler(() -> {
-            mPipMenuController.movePipMenu(null, null, destinationBounds);
-            mPipMenuController.updateMenuBounds(destinationBounds);
-        });
+        mPipMenuController.movePipMenu(null, null, destinationBounds);
+        mPipMenuController.updateMenuBounds(destinationBounds);
     }
 
     private void prepareFinishResizeTransaction(Rect destinationBounds,
@@ -1185,10 +1047,6 @@
     private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
             @PipAnimationController.TransitionDirection int direction, int durationMs,
             float startingAngle) {
-        if (Looper.myLooper() != mUpdateHandler.getLooper()) {
-            throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of "
-                    + "this directly");
-        }
         // Could happen when exitPip
         if (mToken == null || mLeash == null) {
             Log.w(TAG, "Abort animation, invalid leash");
@@ -1197,33 +1055,14 @@
         Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
                 ? mPipBoundsState.getBounds() : currentBounds;
         mPipAnimationController
-                .getAnimator(mLeash, baseBounds, currentBounds, destinationBounds, sourceHintRect,
-                        direction, startingAngle)
+                .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
+                        sourceHintRect, direction, startingAngle)
                 .setTransitionDirection(direction)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(durationMs)
                 .start();
     }
 
-    private Size getMinimalSize(ActivityInfo activityInfo) {
-        if (activityInfo == null || activityInfo.windowLayout == null) {
-            return null;
-        }
-        final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout;
-        // -1 will be populated if an activity specifies defaultWidth/defaultHeight in <layout>
-        // without minWidth/minHeight
-        if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
-            return new Size(windowLayout.minWidth, windowLayout.minHeight);
-        }
-        return null;
-    }
-
-    private float getAspectRatioOrDefault(@Nullable PictureInPictureParams params) {
-        return params == null || !params.hasSetAspectRatio()
-                ? mPipBoundsAlgorithm.getDefaultAspectRatio()
-                : params.getAspectRatio();
-    }
-
     /**
      * Sync with {@link LegacySplitScreen} on destination bounds if PiP is going to split screen.
      *
@@ -1273,24 +1112,4 @@
     public String toString() {
         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_PIP);
     }
-
-    /**
-     * Callback interface for PiP transitions (both from and to PiP mode)
-     */
-    public interface PipTransitionCallback {
-        /**
-         * Callback when the pip transition is started.
-         */
-        void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds);
-
-        /**
-         * Callback when the pip transition is finished.
-         */
-        void onPipTransitionFinished(ComponentName activity, int direction);
-
-        /**
-         * Callback when the pip transition is cancelled.
-         */
-        void onPipTransitionCanceled(ComponentName activity, int direction);
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
new file mode 100644
index 0000000..91e8c99
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
+import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
+import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
+
+import android.app.TaskInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * Implementation of transitions for PiP on phone. Responsible for enter (alpha, bounds) and
+ * exit animation.
+ */
+public class PipTransition extends PipTransitionController {
+
+    private final int mEnterExitAnimationDuration;
+    private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+    private Transitions.TransitionFinishCallback mFinishCallback;
+
+    public PipTransition(Context context,
+            PipBoundsState pipBoundsState, PipMenuController pipMenuController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipAnimationController pipAnimationController,
+            Transitions transitions,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer) {
+        super(pipBoundsState, pipMenuController, pipBoundsAlgorithm,
+                pipAnimationController, transitions, shellTaskOrganizer);
+        mEnterExitAnimationDuration = context.getResources()
+                .getInteger(R.integer.config_pipResizeAnimationDuration);
+    }
+
+    @Override
+    public boolean startAnimation(@android.annotation.NonNull IBinder transition,
+            @android.annotation.NonNull TransitionInfo info,
+            @android.annotation.NonNull SurfaceControl.Transaction t,
+            @android.annotation.NonNull Transitions.TransitionFinishCallback finishCallback) {
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.getTaskInfo() != null
+                    && change.getTaskInfo().configuration.windowConfiguration.getWindowingMode()
+                    == WINDOWING_MODE_PINNED) {
+                mFinishCallback = finishCallback;
+                return startEnterAnimation(change.getTaskInfo(), change.getLeash(), t);
+            }
+        }
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        return null;
+    }
+
+    @Override
+    public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
+            @PipAnimationController.TransitionDirection int direction,
+            SurfaceControl.Transaction tx) {
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        prepareFinishResizeTransaction(taskInfo, destinationBounds,
+                direction, tx, wct);
+        mFinishCallback.onTransitionFinished(wct, null);
+        finishResizeForMenu(destinationBounds);
+    }
+
+    private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
+            final SurfaceControl.Transaction t) {
+        setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
+                taskInfo.topActivityInfo);
+        final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+        final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        PipAnimationController.PipTransitionAnimator animator;
+        if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+            final Rect sourceHintRect =
+                    PipBoundsAlgorithm.getValidSourceHintRect(
+                            taskInfo.pictureInPictureParams, currentBounds);
+            animator = mPipAnimationController.getAnimator(taskInfo, leash,
+                    currentBounds, currentBounds, destinationBounds, sourceHintRect,
+                    TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */);
+        } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+            t.setAlpha(leash, 0f);
+            t.apply();
+            animator = mPipAnimationController.getAnimator(taskInfo, leash,
+                    destinationBounds, 0f, 1f);
+            mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+        } else {
+            throw new RuntimeException("Unrecognized animation type: "
+                    + mOneShotAnimationType);
+        }
+        animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
+                .setPipAnimationCallback(mPipAnimationCallback)
+                .setDuration(mEnterExitAnimationDuration)
+                .start();
+        return true;
+    }
+
+    private void finishResizeForMenu(Rect destinationBounds) {
+        mPipMenuController.movePipMenu(null, null, destinationBounds);
+        mPipMenuController.updateMenuBounds(destinationBounds);
+    }
+
+    private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds,
+            @PipAnimationController.TransitionDirection int direction,
+            SurfaceControl.Transaction tx,
+            WindowContainerTransaction wct) {
+        Rect taskBounds = null;
+        if (isInPipDirection(direction)) {
+            // If we are animating from fullscreen using a bounds animation, then reset the
+            // activity windowing mode set by WM, and set the task bounds to the final bounds
+            taskBounds = destinationBounds;
+            wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+            wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds);
+        } else if (isOutPipDirection(direction)) {
+            // If we are animating to fullscreen, then we need to reset the override bounds
+            // on the task to ensure that the task "matches" the parent's bounds.
+            taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP)
+                    ? null : destinationBounds;
+            wct.setWindowingMode(taskInfo.token, getOutPipWindowingMode());
+            // Simply reset the activity mode set prior to the animation running.
+            wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+        }
+
+        wct.setBounds(taskInfo.token, taskBounds);
+        wct.setBoundsChangeTransaction(taskInfo.token, tx);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
new file mode 100644
index 0000000..d801c91
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+
+import android.app.PictureInPictureParams;
+import android.app.TaskInfo;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Responsible supplying PiP Transitions.
+ */
+public abstract class PipTransitionController implements Transitions.TransitionHandler {
+
+    protected final PipAnimationController mPipAnimationController;
+    protected final PipBoundsAlgorithm mPipBoundsAlgorithm;
+    protected final PipBoundsState mPipBoundsState;
+    protected final ShellTaskOrganizer mShellTaskOrganizer;
+    protected final PipMenuController mPipMenuController;
+    private final Handler mMainHandler;
+    private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
+
+    protected final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
+            new PipAnimationController.PipAnimationCallback() {
+                @Override
+                public void onPipAnimationStart(TaskInfo taskInfo,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    final int direction = animator.getTransitionDirection();
+                    if (direction == TRANSITION_DIRECTION_TO_PIP) {
+                        // TODO (b//169221267): Add jank listener for transactions without buffer
+                        //  updates.
+                        //InteractionJankMonitor.getInstance().begin(
+                        //        InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000);
+                    }
+                    sendOnPipTransitionStarted(direction);
+                }
+
+                @Override
+                public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    final int direction = animator.getTransitionDirection();
+                    mPipBoundsState.setBounds(animator.getDestinationBounds());
+                    if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
+                        return;
+                    }
+                    onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx);
+                    sendOnPipTransitionFinished(direction);
+                    if (direction == TRANSITION_DIRECTION_TO_PIP) {
+                        // TODO (b//169221267): Add jank listener for transactions without buffer
+                        //  updates.
+                        //InteractionJankMonitor.getInstance().end(
+                        //        InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
+                    }
+                }
+
+                @Override
+                public void onPipAnimationCancel(TaskInfo taskInfo,
+                        PipAnimationController.PipTransitionAnimator animator) {
+                    sendOnPipTransitionCancelled(animator.getTransitionDirection());
+                }
+            };
+
+    /**
+     * Called when transition is about to finish. This is usually for performing tasks such as
+     * applying WindowContainerTransaction to finalize the PiP bounds and send to the framework.
+     */
+    public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
+            @PipAnimationController.TransitionDirection int direction,
+            SurfaceControl.Transaction tx) {
+    }
+
+    public PipTransitionController(PipBoundsState pipBoundsState,
+            PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipAnimationController pipAnimationController, Transitions transitions,
+            @android.annotation.NonNull ShellTaskOrganizer shellTaskOrganizer) {
+        mPipBoundsState = pipBoundsState;
+        mPipMenuController = pipMenuController;
+        mShellTaskOrganizer = shellTaskOrganizer;
+        mPipBoundsAlgorithm = pipBoundsAlgorithm;
+        mPipAnimationController = pipAnimationController;
+        mMainHandler = new Handler(Looper.getMainLooper());
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            transitions.addHandler(this);
+        }
+    }
+
+    /**
+     * Registers {@link PipTransitionCallback} to receive transition callbacks.
+     */
+    public void registerPipTransitionCallback(PipTransitionCallback callback) {
+        mPipTransitionCallbacks.add(callback);
+    }
+
+    protected void sendOnPipTransitionStarted(
+            @PipAnimationController.TransitionDirection int direction) {
+        final Rect pipBounds = mPipBoundsState.getBounds();
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionStarted(direction, pipBounds);
+        }
+    }
+
+    protected void sendOnPipTransitionFinished(
+            @PipAnimationController.TransitionDirection int direction) {
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionFinished(direction);
+        }
+    }
+
+    protected void sendOnPipTransitionCancelled(
+            @PipAnimationController.TransitionDirection int direction) {
+        for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+            final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+            callback.onPipTransitionCanceled(direction);
+        }
+    }
+
+    /**
+     * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
+     * and can be overridden to restore to an alternate windowing mode.
+     */
+    public int getOutPipWindowingMode() {
+        // By default, simply reset the windowing mode to undefined.
+        return WINDOWING_MODE_UNDEFINED;
+    }
+
+    protected void setBoundsStateForEntry(ComponentName componentName,
+            PictureInPictureParams params,
+            ActivityInfo activityInfo) {
+        mPipBoundsState.setBoundsStateForEntry(componentName,
+                mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
+                mPipBoundsAlgorithm.getMinimalSize(activityInfo));
+    }
+
+    /**
+     * Callback interface for PiP transitions (both from and to PiP mode)
+     */
+    public interface PipTransitionCallback {
+        /**
+         * Callback when the pip transition is started.
+         */
+        void onPipTransitionStarted(int direction, Rect pipBounds);
+
+        /**
+         * Callback when the pip transition is finished.
+         */
+        void onPipTransitionFinished(int direction);
+
+        /**
+         * Callback when the pip transition is cancelled.
+         */
+        void onPipTransitionCanceled(int direction);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 5db8f3d..a57eee8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -30,15 +30,18 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Debug;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Size;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowManagerGlobal;
 
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipMediaController.ActionListener;
@@ -97,6 +100,8 @@
     private final RectF mTmpDestinationRectF = new RectF();
     private final Context mContext;
     private final PipMediaController mMediaController;
+    private final ShellExecutor mMainExecutor;
+    private final Handler mMainHandler;
 
     private final ArrayList<Listener> mListeners = new ArrayList<>();
     private final SystemWindows mSystemWindows;
@@ -116,11 +121,14 @@
         }
     };
 
-    public PhonePipMenuController(Context context,
-            PipMediaController mediaController, SystemWindows systemWindows) {
+    public PhonePipMenuController(Context context, PipMediaController mediaController,
+            SystemWindows systemWindows, ShellExecutor mainExecutor,
+            Handler mainHandler) {
         mContext = context;
         mMediaController = mediaController;
         mSystemWindows = systemWindows;
+        mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
     }
 
     public boolean isMenuVisible() {
@@ -156,7 +164,7 @@
         if (mPipMenuView != null) {
             detachPipMenuView();
         }
-        mPipMenuView = new PipMenuView(mContext, this);
+        mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler);
         mSystemWindows.addView(mPipMenuView,
                 getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                 0, SHELL_ROOT_LAYER_PIP);
@@ -203,6 +211,11 @@
         }
     }
 
+    @Nullable
+    Size getEstimatedMinMenuSize() {
+        return mPipMenuView == null ? null : mPipMenuView.getEstimatedMinMenuSize();
+    }
+
     /**
      * When other components requests the menu controller directly to show the menu, we must
      * first fire off the request to the other listeners who will then propagate the call
@@ -217,13 +230,13 @@
      * Similar to {@link #showMenu(int, Rect, boolean, boolean, boolean)} but only show the menu
      * upon PiP window transition is finished.
      */
-    public void showMenuWithDelay(int menuState, Rect stackBounds, boolean allowMenuTimeout,
+    public void showMenuWithPossibleDelay(int menuState, Rect stackBounds, boolean allowMenuTimeout,
             boolean willResizeMenu, boolean showResizeHandle) {
         // hide all visible controls including close button and etc. first, this is to ensure
         // menu is totally invisible during the transition to eliminate unpleasant artifacts
         fadeOutMenu();
         showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
-                true /* withDelay */, showResizeHandle);
+                willResizeMenu /* withDelay=willResizeMenu here */, showResizeHandle);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
index 2cd0107..d97d2d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java
@@ -21,22 +21,20 @@
 
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.OnOpChangedListener;
-import android.app.IActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.Handler;
 import android.util.Pair;
 
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipUtils;
 
 public class PipAppOpsListener {
     private static final String TAG = PipAppOpsListener.class.getSimpleName();
 
     private Context mContext;
-    private Handler mHandler;
-    private IActivityManager mActivityManager;
+    private ShellExecutor mMainExecutor;
     private AppOpsManager mAppOpsManager;
     private Callback mCallback;
 
@@ -53,7 +51,7 @@
                     if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
                             mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
                                     packageName) != MODE_ALLOWED) {
-                        mHandler.post(() -> mCallback.dismissPip());
+                        mMainExecutor.execute(() -> mCallback.dismissPip());
                     }
                 }
             } catch (NameNotFoundException e) {
@@ -63,11 +61,9 @@
         }
     };
 
-    public PipAppOpsListener(Context context, IActivityManager activityManager,
-            Callback callback) {
+    public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
         mContext = context;
-        mHandler = new Handler(mContext.getMainLooper());
-        mActivityManager = activityManager;
+        mMainExecutor = mainExecutor;
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mCallback = callback;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index fa88084..c3970e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -19,8 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
@@ -63,6 +61,7 @@
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUtils;
 
 import java.io.PrintWriter;
@@ -71,11 +70,11 @@
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
-public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
+public class PipController implements PipTransitionController.PipTransitionCallback {
     private static final String TAG = "PipController";
 
     private Context mContext;
-    private ShellExecutor mMainExecutor;
+    protected ShellExecutor mMainExecutor;
     private DisplayController mDisplayController;
     private PipInputConsumer mPipInputConsumer;
     private WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -84,8 +83,9 @@
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private PipBoundsState mPipBoundsState;
     private PipTouchHandler mTouchHandler;
+    private PipTransitionController mPipTransitionController;
+    protected final PipImpl mImpl = new PipImpl();
 
-    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
     private final Rect mTmpInsetBounds = new Rect();
 
     private boolean mIsInFixedRotation;
@@ -144,7 +144,7 @@
         }
     };
 
-    private final DisplayController.OnDisplaysChangedListener mFixedRotationListener =
+    private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
             new DisplayController.OnDisplaysChangedListener() {
                 @Override
                 public void onFixedRotationStarted(int displayId, int newRotation) {
@@ -158,8 +158,20 @@
 
                 @Override
                 public void onDisplayAdded(int displayId) {
-                    mPipBoundsState.setDisplayLayout(
-                            mDisplayController.getDisplayLayout(displayId));
+                    if (displayId != mPipBoundsState.getDisplayId()) {
+                        return;
+                    }
+                    onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
+                            false /* saveRestoreSnapFraction */);
+                }
+
+                @Override
+                public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+                    if (displayId != mPipBoundsState.getDisplayId()) {
+                        return;
+                    }
+                    onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
+                            true /* saveRestoreSnapFraction */);
                 }
             };
 
@@ -204,6 +216,29 @@
         }
     }
 
+    /**
+     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+     */
+    @Nullable
+    public static Pip create(Context context, DisplayController displayController,
+            PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+            PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
+            PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
+        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+            Slog.w(TAG, "Device doesn't support Pip feature");
+            return null;
+        }
+
+        return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
+                pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+                pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+                taskStackListener, mainExecutor)
+                .mImpl;
+    }
+
     protected PipController(Context context,
             DisplayController displayController,
             PipAppOpsListener pipAppOpsListener,
@@ -213,6 +248,7 @@
             PhonePipMenuController phonePipMenuController,
             PipTaskOrganizer pipTaskOrganizer,
             PipTouchHandler pipTouchHandler,
+            PipTransitionController pipTransitionController,
             WindowManagerShellWrapper windowManagerShellWrapper,
             TaskStackListenerImpl taskStackListener,
             ShellExecutor mainExecutor
@@ -234,16 +270,14 @@
         mMenuController = phonePipMenuController;
         mTouchHandler = pipTouchHandler;
         mAppOpsListener = pipAppOpsListener;
+        mPipTransitionController = pipTransitionController;
         mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
-                INPUT_CONSUMER_PIP);
-        mPipTaskOrganizer.registerPipTransitionCallback(this);
+                INPUT_CONSUMER_PIP, mainExecutor);
+        mPipTransitionController.registerPipTransitionCallback(this);
         mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
-            final DisplayInfo newDisplayInfo = new DisplayInfo();
-            displayController.getDisplay(displayId).getDisplayInfo(newDisplayInfo);
-            mPipBoundsState.setDisplayInfo(newDisplayInfo);
-            updateMovementBounds(null /* toBounds */, false /* fromRotation */,
-                    false /* fromImeAdjustment */, false /* fromShelfAdjustment */,
-                    null /* wct */);
+            mPipBoundsState.setDisplayId(displayId);
+            onDisplayChanged(displayController.getDisplayLayout(displayId),
+                    false /* saveRestoreSnapFraction */);
         });
         mPipBoundsState.setOnMinimalSizeChangeCallback(
                 () -> {
@@ -268,13 +302,12 @@
             mPipInputConsumer.setRegistrationListener(mTouchHandler::onRegistrationChanged);
         }
         displayController.addDisplayChangingController(mRotationController);
-        displayController.addDisplayWindowListener(mFixedRotationListener);
+        displayController.addDisplayWindowListener(mDisplaysChangedListener);
 
         // Ensure that we have the display info in case we get calls to update the bounds before the
         // listener calls back
-        final DisplayInfo displayInfo = new DisplayInfo();
-        context.getDisplay().getDisplayInfo(displayInfo);
-        mPipBoundsState.setDisplayInfo(displayInfo);
+        mPipBoundsState.setDisplayId(context.getDisplayId());
+        mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
 
         try {
             mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener);
@@ -288,7 +321,7 @@
             if (taskInfo != null) {
                 // If SystemUI restart, and it already existed a pinned stack,
                 // register the pip input consumer to ensure touch can send to it.
-                mPipInputConsumer.registerInputConsumer(true /* withSfVsync */);
+                mPipInputConsumer.registerInputConsumer();
             }
         } catch (RemoteException | UnsupportedOperationException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
@@ -301,12 +334,10 @@
                     @Override
                     public void onActivityPinned(String packageName, int userId, int taskId,
                             int stackId) {
-                        mMainExecutor.execute(() -> {
-                            mTouchHandler.onActivityPinned();
-                            mMediaController.onActivityPinned();
-                            mAppOpsListener.onActivityPinned(packageName);
-                        });
-                        mPipInputConsumer.registerInputConsumer(true /* withSfVsync */);
+                        mTouchHandler.onActivityPinned();
+                        mMediaController.onActivityPinned();
+                        mAppOpsListener.onActivityPinned(packageName);
+                        mPipInputConsumer.registerInputConsumer();
                     }
 
                     @Override
@@ -314,10 +345,8 @@
                         final Pair<ComponentName, Integer> topPipActivityInfo =
                                 PipUtils.getTopPipActivity(mContext);
                         final ComponentName topActivity = topPipActivityInfo.first;
-                        mMainExecutor.execute(() -> {
-                            mTouchHandler.onActivityUnpinned(topActivity);
-                            mAppOpsListener.onActivityUnpinned();
-                        });
+                        mTouchHandler.onActivityUnpinned(topActivity);
+                        mAppOpsListener.onActivityUnpinned();
                         mPipInputConsumer.unregisterInputConsumer();
                     }
 
@@ -333,60 +362,77 @@
                 });
     }
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        mMainExecutor.execute(() -> {
-            mPipBoundsAlgorithm.onConfigurationChanged(mContext);
-            mTouchHandler.onConfigurationChanged();
-            mPipBoundsState.onConfigurationChanged();
-        });
+    private void onConfigurationChanged(Configuration newConfig) {
+        mPipBoundsAlgorithm.onConfigurationChanged(mContext);
+        mTouchHandler.onConfigurationChanged();
+        mPipBoundsState.onConfigurationChanged();
     }
 
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        mMainExecutor.execute(() -> {
-            mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
-        });
+    private void onDensityOrFontScaleChanged() {
+        mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
     }
 
-    @Override
-    public void onOverlayChanged() {
-        mMainExecutor.execute(() -> {
-            mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
+    private void onOverlayChanged() {
+        onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()),
+                false /* saveRestoreSnapFraction */);
+    }
+
+    private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) {
+        Runnable updateDisplayLayout = () -> {
+            mPipBoundsState.setDisplayLayout(layout);
             updateMovementBounds(null /* toBounds */,
                     false /* fromRotation */, false /* fromImeAdjustment */,
                     false /* fromShelfAdjustment */,
                     null /* windowContainerTransaction */);
-        });
+        };
+
+        if (saveRestoreSnapFraction) {
+            // Calculate the snap fraction of the current stack along the old movement bounds
+            final PipSnapAlgorithm pipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
+            final Rect postChangeStackBounds = new Rect(mPipBoundsState.getBounds());
+            final float snapFraction = pipSnapAlgorithm.getSnapFraction(postChangeStackBounds,
+                    mPipBoundsAlgorithm.getMovementBounds(postChangeStackBounds),
+                    mPipBoundsState.getStashedState());
+
+            updateDisplayLayout.run();
+
+            // Calculate the stack bounds in the new orientation based on same fraction along the
+            // rotated movement bounds.
+            final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds(
+                    postChangeStackBounds, false /* adjustForIme */);
+            pipSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
+                    snapFraction, mPipBoundsState.getStashedState(),
+                    mPipBoundsState.getStashOffset(),
+                    mPipBoundsState.getDisplayBounds());
+
+            mTouchHandler.getMotionHelper().movePip(postChangeStackBounds);
+        } else {
+            updateDisplayLayout.run();
+        }
     }
 
-    @Override
-    public void registerSessionListenerForCurrentUser() {
+    private void registerSessionListenerForCurrentUser() {
         mMediaController.registerSessionListenerForCurrentUser();
     }
 
-    @Override
-    public void onSystemUiStateChanged(boolean isValidState, int flag) {
+    private void onSystemUiStateChanged(boolean isValidState, int flag) {
         mTouchHandler.onSystemUiStateChanged(isValidState);
     }
 
     /**
      * Expands the PIP.
      */
-    @Override
     public void expandPip() {
         mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */);
     }
 
-    @Override
-    public PipTouchHandler getPipTouchHandler() {
+    private PipTouchHandler getPipTouchHandler() {
         return mTouchHandler;
     }
 
     /**
      * Hides the PIP menu.
      */
-    @Override
     public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
         mMenuController.hideMenu(onStartCallback, onEndCallback);
     }
@@ -408,9 +454,8 @@
     /**
      * Sets both shelf visibility and its height.
      */
-    @Override
-    public void setShelfHeight(boolean visible, int height) {
-        mMainExecutor.execute(() -> setShelfHeightLocked(visible, height));
+    private void setShelfHeight(boolean visible, int height) {
+        setShelfHeightLocked(visible, height);
     }
 
     private void setShelfHeightLocked(boolean visible, int height) {
@@ -418,18 +463,15 @@
         mPipBoundsState.setShelfVisibility(visible, shelfHeight);
     }
 
-    @Override
-    public void setPinnedStackAnimationType(int animationType) {
-        mMainExecutor.execute(() -> mPipTaskOrganizer.setOneShotAnimationType(animationType));
+    private void setPinnedStackAnimationType(int animationType) {
+        mPipTaskOrganizer.setOneShotAnimationType(animationType);
     }
 
-    @Override
-    public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
-        mMainExecutor.execute(() -> mPinnedStackAnimationRecentsCallback = callback);
+    private void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+        mPinnedStackAnimationRecentsCallback = callback;
     }
 
-    @Override
-    public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+    private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
             PictureInPictureParams pictureInPictureParams,
             int launcherRotation, int shelfHeight) {
         setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
@@ -438,13 +480,21 @@
                 pictureInPictureParams);
     }
 
-    @Override
-    public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+    private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
         mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds);
     }
 
+    /**
+     * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's
+     * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
+     */
+    private void setPipExclusionBoundsChangeListener(
+            Consumer<Rect> pipExclusionBoundsChangeListener) {
+        mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener);
+    }
+
     @Override
-    public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
+    public void onPipTransitionStarted(int direction, Rect pipBounds) {
         if (isOutPipDirection(direction)) {
             // Exiting PIP, save the reentry state to restore to when re-entering.
             saveReentryState(pipBounds);
@@ -468,23 +518,13 @@
         }
     }
 
-    /**
-     * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's
-     * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
-     */
     @Override
-    public void setPipExclusionBoundsChangeListener(
-            Consumer<Rect> pipExclusionBoundsChangeListener) {
-        mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener);
-    }
-
-    @Override
-    public void onPipTransitionFinished(ComponentName activity, int direction) {
+    public void onPipTransitionFinished(int direction) {
         onPipTransitionFinishedOrCanceled(direction);
     }
 
     @Override
-    public void onPipTransitionCanceled(ComponentName activity, int direction) {
+    public void onPipTransitionCanceled(int direction) {
         onPipTransitionFinishedOrCanceled(direction);
     }
 
@@ -501,7 +541,7 @@
         // Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler before
         // passing to mTouchHandler/mPipTaskOrganizer
         final Rect outBounds = new Rect(toBounds);
-        mTmpDisplayInfo.copyFrom(mPipBoundsState.getDisplayInfo());
+        final int rotation = mPipBoundsState.getDisplayLayout().rotation();
 
         mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
         mPipBoundsState.setNormalBounds(mPipBoundsAlgorithm.getNormalBounds());
@@ -513,8 +553,7 @@
         mPipTaskOrganizer.onMovementBoundsChanged(outBounds, fromRotation, fromImeAdjustment,
                 fromShelfAdjustment, wct);
         mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mPipBoundsState.getNormalBounds(),
-                outBounds, fromImeAdjustment, fromShelfAdjustment,
-                mTmpDisplayInfo.rotation);
+                outBounds, fromImeAdjustment, fromShelfAdjustment, rotation);
     }
 
     /**
@@ -526,13 +565,6 @@
         // Update the display layout, note that we have to do this on every rotation even if we
         // aren't in PIP since we need to update the display layout to get the right resources
         mPipBoundsState.getDisplayLayout().rotateTo(context.getResources(), toRotation);
-
-        // Populate the new {@link #mDisplayInfo}.
-        // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
-        // therefore, the width/height may require a swap first.
-        // Moving forward, we should get the new dimensions after rotation from DisplayLayout.
-        mPipBoundsState.setDisplayRotation(toRotation);
-        updateDisplayInfoIfNeeded();
     }
 
     /**
@@ -544,9 +576,8 @@
     private boolean onDisplayRotationChanged(Context context, Rect outBounds, Rect oldBounds,
             Rect outInsetBounds,
             int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
-        // Bail early if the event is not sent to current {@link #mDisplayInfo}
-        if ((displayId != mPipBoundsState.getDisplayInfo().displayId)
-                || (fromRotation == toRotation)) {
+        // Bail early if the event is not sent to current display
+        if ((displayId != mPipBoundsState.getDisplayId()) || (fromRotation == toRotation)) {
             return false;
         }
 
@@ -571,13 +602,6 @@
         // Update the display layout
         mPipBoundsState.getDisplayLayout().rotateTo(context.getResources(), toRotation);
 
-        // Populate the new {@link #mDisplayInfo}.
-        // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
-        // therefore, the width/height may require a swap first.
-        // Moving forward, we should get the new dimensions after rotation from DisplayLayout.
-        mPipBoundsState.getDisplayInfo().rotation = toRotation;
-        updateDisplayInfoIfNeeded();
-
         // Calculate the stack bounds in the new orientation based on same fraction along the
         // rotated movement bounds.
         final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds(
@@ -592,23 +616,7 @@
         return true;
     }
 
-    private void updateDisplayInfoIfNeeded() {
-        final DisplayInfo displayInfo = mPipBoundsState.getDisplayInfo();
-        final boolean updateNeeded;
-        if ((displayInfo.rotation == ROTATION_0) || (displayInfo.rotation == ROTATION_180)) {
-            updateNeeded = (displayInfo.logicalWidth > displayInfo.logicalHeight);
-        } else {
-            updateNeeded = (displayInfo.logicalWidth < displayInfo.logicalHeight);
-        }
-        if (updateNeeded) {
-            final int newLogicalHeight = displayInfo.logicalWidth;
-            displayInfo.logicalWidth = displayInfo.logicalHeight;
-            displayInfo.logicalHeight = newLogicalHeight;
-        }
-    }
-
-    @Override
-    public void dump(PrintWriter pw) {
+    private void dump(PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG);
         mMenuController.dump(pw, innerPrefix);
@@ -619,23 +627,123 @@
         mPipInputConsumer.dump(pw, innerPrefix);
     }
 
-    /**
-     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
-     */
-    @Nullable
-    public static PipController create(Context context, DisplayController displayController,
-            PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
-            PipBoundsState pipBoundsState, PipMediaController pipMediaController,
-            PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
-            PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
-            TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
-        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
-            Slog.w(TAG, "Device doesn't support Pip feature");
-            return null;
+    private class PipImpl implements Pip {
+        @Override
+        public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
+            mMainExecutor.execute(() -> {
+                PipController.this.hidePipMenu(onStartCallback, onEndCallback);
+            });
         }
 
-        return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
-                pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
-                pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor);
+        @Override
+        public void expandPip() {
+            mMainExecutor.execute(() -> {
+                PipController.this.expandPip();
+            });
+        }
+
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            mMainExecutor.execute(() -> {
+                PipController.this.onConfigurationChanged(newConfig);
+            });
+        }
+
+        @Override
+        public void onDensityOrFontScaleChanged() {
+            mMainExecutor.execute(() -> {
+                PipController.this.onDensityOrFontScaleChanged();
+            });
+        }
+
+        @Override
+        public void onOverlayChanged() {
+            mMainExecutor.execute(() -> {
+                PipController.this.onOverlayChanged();
+            });
+        }
+
+        @Override
+        public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
+            mMainExecutor.execute(() -> {
+                PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag);
+            });
+        }
+
+        @Override
+        public void registerSessionListenerForCurrentUser() {
+            mMainExecutor.execute(() -> {
+                PipController.this.registerSessionListenerForCurrentUser();
+            });
+        }
+
+        @Override
+        public void setShelfHeight(boolean visible, int height) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setShelfHeight(visible, height);
+            });
+        }
+
+        @Override
+        public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setPinnedStackAnimationListener(callback);
+            });
+        }
+
+        @Override
+        public void setPinnedStackAnimationType(int animationType) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setPinnedStackAnimationType(animationType);
+            });
+        }
+
+        @Override
+        public void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+            mMainExecutor.execute(() -> {
+                PipController.this.setPipExclusionBoundsChangeListener(listener);
+            });
+        }
+
+        @Override
+        public void showPictureInPictureMenu() {
+            mMainExecutor.execute(() -> {
+                PipController.this.showPictureInPictureMenu();
+            });
+        }
+
+        @Override
+        public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+                PictureInPictureParams pictureInPictureParams, int launcherRotation,
+                int shelfHeight) {
+            Rect[] result = new Rect[1];
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo,
+                            pictureInPictureParams, launcherRotation, shelfHeight);
+                });
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Failed to start swipe pip to home");
+            }
+            return result[0];
+        }
+
+        @Override
+        public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
+            mMainExecutor.execute(() -> {
+                PipController.this.stopSwipePipToHome(componentName, destinationBounds);
+            });
+        }
+
+        @Override
+        public void dump(PrintWriter pw) {
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    PipController.this.dump(pw);
+                });
+            } catch (InterruptedException e) {
+                Slog.e(TAG, "Failed to dump PipController in 2s");
+            }
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index bebe5f9..d9a7bdb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -37,9 +37,12 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.DismissCircleView;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.pip.PipUiEventLogger;
 
+import java.util.concurrent.TimeUnit;
+
 import kotlin.Unit;
 
 /**
@@ -57,36 +60,32 @@
      * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
      * PIP.
      */
-    private final MagnetizedObject<Rect> mMagnetizedPip;
+    private MagnetizedObject<Rect> mMagnetizedPip;
 
     /**
      * Container for the dismiss circle, so that it can be animated within the container via
      * translation rather than within the WindowManager via slow layout animations.
      */
-    private final ViewGroup mTargetViewContainer;
+    private ViewGroup mTargetViewContainer;
 
     /** Circle view used to render the dismiss target. */
-    private final DismissCircleView mTargetView;
+    private DismissCircleView mTargetView;
 
     /**
      * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
      */
-    private final MagnetizedObject.MagneticTarget mMagneticTarget;
+    private MagnetizedObject.MagneticTarget mMagneticTarget;
 
-    /** PhysicsAnimator instance for animating the dismiss target in/out. */
-    private final PhysicsAnimator<View> mMagneticTargetAnimator;
+    /**
+     * PhysicsAnimator instance for animating the dismiss target in/out.
+     */
+    private PhysicsAnimator<View> mMagneticTargetAnimator;
 
     /** Default configuration to use for springing the dismiss target in/out. */
     private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
             new PhysicsAnimator.SpringConfig(
                     SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
-    /**
-     * Runnable that can be posted delayed to show the target. This needs to be saved as a member
-     * variable so we can pass it to removeCallbacks.
-     */
-    private Runnable mShowTargetAction = this::showDismissTargetMaybe;
-
     // Allow dragging the PIP to a location to close it
     private final boolean mEnableDismissDragToEdge;
 
@@ -96,74 +95,76 @@
     private final PipMotionHelper mMotionHelper;
     private final PipUiEventLogger mPipUiEventLogger;
     private final WindowManager mWindowManager;
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
 
     public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
-            PipMotionHelper motionHelper, Handler handler) {
+            PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
         mContext = context;
         mPipUiEventLogger = pipUiEventLogger;
         mMotionHelper = motionHelper;
-        mHandler = handler;
+        mMainExecutor = mainExecutor;
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
         Resources res = context.getResources();
         mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
         mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
 
-        mTargetView = new DismissCircleView(context);
-        mTargetViewContainer = new FrameLayout(context);
-        mTargetViewContainer.setBackgroundDrawable(
-                context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
-        mTargetViewContainer.setClipChildren(false);
-        mTargetViewContainer.addView(mTargetView);
+        mMainExecutor.execute(() -> {
+            mTargetView = new DismissCircleView(context);
+            mTargetViewContainer = new FrameLayout(context);
+            mTargetViewContainer.setBackgroundDrawable(
+                    context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
+            mTargetViewContainer.setClipChildren(false);
+            mTargetViewContainer.addView(mTargetView);
 
-        mMagnetizedPip = mMotionHelper.getMagnetizedPip();
-        mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
-        updateMagneticTargetSize();
+            mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+            mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+            updateMagneticTargetSize();
 
-        mMagnetizedPip.setAnimateStuckToTarget(
-                (target, velX, velY, flung, after) -> {
+            mMagnetizedPip.setAnimateStuckToTarget(
+                    (target, velX, velY, flung, after) -> {
+                        if (mEnableDismissDragToEdge) {
+                            mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung,
+                                    after);
+                        }
+                        return Unit.INSTANCE;
+                    });
+            mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+                @Override
+                public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+                    // Show the dismiss target, in case the initial touch event occurred within
+                    // the magnetic field radius.
                     if (mEnableDismissDragToEdge) {
-                        mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
+                        showDismissTargetMaybe();
                     }
-                    return Unit.INSTANCE;
-                });
-        mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
-            @Override
-            public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                // Show the dismiss target, in case the initial touch event occurred within the
-                // magnetic field radius.
-                if (mEnableDismissDragToEdge) {
-                    showDismissTargetMaybe();
                 }
-            }
 
-            @Override
-            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
-                    float velX, float velY, boolean wasFlungOut) {
-                if (wasFlungOut) {
-                    mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
-                    hideDismissTargetMaybe();
-                } else {
-                    mMotionHelper.setSpringingToTouch(true);
+                @Override
+                public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                        float velX, float velY, boolean wasFlungOut) {
+                    if (wasFlungOut) {
+                        mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
+                        hideDismissTargetMaybe();
+                    } else {
+                        mMotionHelper.setSpringingToTouch(true);
+                    }
                 }
-            }
 
-            @Override
-            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
-                mMotionHelper.notifyDismissalPending();
+                @Override
+                public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+                    mMainExecutor.executeDelayed(() -> {
+                        mMotionHelper.notifyDismissalPending();
+                        mMotionHelper.animateDismiss();
+                        hideDismissTargetMaybe();
 
-                handler.post(() -> {
-                    mMotionHelper.animateDismiss();
-                    hideDismissTargetMaybe();
-                });
+                        mPipUiEventLogger.log(
+                                PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+                    }, 0);
+                }
+            });
 
-                mPipUiEventLogger.log(
-                        PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
-            }
+            mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
         });
-
-        mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
     }
 
     /**
@@ -200,7 +201,6 @@
     /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
     public void createOrUpdateDismissTarget() {
         if (!mTargetViewContainer.isAttachedToWindow()) {
-            mHandler.removeCallbacks(mShowTargetAction);
             mMagneticTargetAnimator.cancel();
 
             mTargetViewContainer.setVisibility(View.INVISIBLE);
@@ -270,7 +270,6 @@
             return;
         }
 
-        mHandler.removeCallbacks(mShowTargetAction);
         mMagneticTargetAnimator
                 .spring(DynamicAnimation.TRANSLATION_Y,
                         mTargetViewContainer.getHeight(),
@@ -286,8 +285,6 @@
      * Removes the dismiss target and cancels any pending callbacks to show it.
      */
     public void cleanUpDismissTarget() {
-        mHandler.removeCallbacks(mShowTargetAction);
-
         if (mTargetViewContainer.isAttachedToWindow()) {
             mWindowManager.removeViewImmediate(mTargetViewContainer);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 0c64c8c..6e3a20d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -29,6 +29,8 @@
 import android.view.InputChannel;
 import android.view.InputEvent;
 
+import com.android.wm.shell.common.ShellExecutor;
+
 import java.io.PrintWriter;
 
 /**
@@ -81,6 +83,7 @@
     private final IWindowManager mWindowManager;
     private final IBinder mToken;
     private final String mName;
+    private final ShellExecutor mMainExecutor;
 
     private InputEventReceiver mInputEventReceiver;
     private InputListener mListener;
@@ -89,10 +92,12 @@
     /**
      * @param name the name corresponding to the input consumer that is defined in the system.
      */
-    public PipInputConsumer(IWindowManager windowManager, String name) {
+    public PipInputConsumer(IWindowManager windowManager, String name,
+            ShellExecutor mainExecutor) {
         mWindowManager = windowManager;
         mToken = new Binder();
         mName = name;
+        mMainExecutor = mainExecutor;
     }
 
     /**
@@ -107,9 +112,11 @@
      */
     public void setRegistrationListener(RegistrationListener listener) {
         mRegistrationListener = listener;
-        if (mRegistrationListener != null) {
-            mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
-        }
+        mMainExecutor.execute(() -> {
+            if (mRegistrationListener != null) {
+                mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
+            }
+        });
     }
 
     /**
@@ -125,14 +132,6 @@
      * Registers the input consumer.
      */
     public void registerInputConsumer() {
-        registerInputConsumer(false);
-    }
-
-    /**
-     * Registers the input consumer.
-     * @param withSfVsync the flag set using sf vsync signal or no
-     */
-    public void registerInputConsumer(boolean withSfVsync) {
         if (mInputEventReceiver != null) {
             return;
         }
@@ -144,11 +143,15 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to create input consumer", e);
         }
-        mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper(),
-                withSfVsync ? Choreographer.getSfInstance() : Choreographer.getInstance());
-        if (mRegistrationListener != null) {
-            mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
-        }
+        mMainExecutor.execute(() -> {
+            // Choreographer.getSfInstance() must be called on the thread that the input event
+            // receiver should be receiving events
+            mInputEventReceiver = new InputEventReceiver(inputChannel,
+                Looper.myLooper(), Choreographer.getSfInstance());
+            if (mRegistrationListener != null) {
+                mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
+            }
+        });
     }
 
     /**
@@ -166,9 +169,11 @@
         }
         mInputEventReceiver.dispose();
         mInputEventReceiver = null;
-        if (mRegistrationListener != null) {
-            mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
-        }
+        mMainExecutor.execute(() -> {
+            if (mRegistrationListener != null) {
+                mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
+            }
+        });
     }
 
     public void dump(PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 2e10fc9..962c467 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -48,6 +48,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Size;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -63,6 +64,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipUtils;
 
 import java.util.ArrayList;
@@ -75,9 +77,6 @@
 
     private static final String TAG = "PipMenuView";
 
-    private static final int MESSAGE_INVALID_TYPE = -1;
-    public static final int MESSAGE_MENU_EXPANDED = 8;
-
     private static final int INITIAL_DISMISS_DELAY = 3500;
     private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
     private static final long MENU_FADE_DURATION = 125;
@@ -85,8 +84,6 @@
     private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
 
     private static final float MENU_BACKGROUND_ALPHA = 0.3f;
-    private static final float DISMISS_BACKGROUND_ALPHA = 0.6f;
-
     private static final float DISABLED_ACTION_ALPHA = 0.54f;
 
     private static final boolean ENABLE_RESIZE_HANDLE = false;
@@ -116,7 +113,8 @@
                 }
             };
 
-    private Handler mHandler = new Handler();
+    private ShellExecutor mMainExecutor;
+    private Handler mMainHandler;
 
     private final Runnable mHideMenuRunnable = this::hideMenu;
 
@@ -127,10 +125,13 @@
     protected View mTopEndContainer;
     protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
 
-    public PipMenuView(Context context, PhonePipMenuController controller) {
+    public PipMenuView(Context context, PhonePipMenuController controller,
+            ShellExecutor mainExecutor, Handler mainHandler) {
         super(context, null, 0);
         mContext = context;
         mController = controller;
+        mMainExecutor = mainExecutor;
+        mMainHandler = mainHandler;
 
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         inflate(context, R.layout.pip_menu, this);
@@ -230,7 +231,6 @@
                     && (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
             mAllowTouches = !disallowTouchesUntilAnimationEnd;
             cancelDelayedHide();
-            updateActionViews(stackBounds);
             if (mMenuContainerAnimator != null) {
                 mMenuContainerAnimator.cancel();
             }
@@ -279,6 +279,7 @@
                 setVisibility(VISIBLE);
                 mMenuContainerAnimator.start();
             }
+            updateActionViews(stackBounds);
         } else {
             // If we are already visible, then just start the delayed dismiss and unregister any
             // existing input consumers from the previous drag
@@ -365,6 +366,21 @@
         }
     }
 
+    /**
+     * @return Estimated minimum {@link Size} to hold the actions.
+     *         See also {@link #updateActionViews(Rect)}
+     */
+    Size getEstimatedMinMenuSize() {
+        final int pipActionSize = getResources().getDimensionPixelSize(R.dimen.pip_action_size);
+        // the minimum width would be (2 * pipActionSize) since we have settings and dismiss button
+        // on the top action container.
+        final int width = Math.max(2, mActions.size()) * pipActionSize;
+        final int height = getResources().getDimensionPixelSize(R.dimen.pip_expand_action_size)
+                + getResources().getDimensionPixelSize(R.dimen.pip_action_padding)
+                + getResources().getDimensionPixelSize(R.dimen.pip_expand_container_edge_margin);
+        return new Size(width, height);
+    }
+
     void setActions(Rect stackBounds, List<RemoteAction> actions) {
         mActions.clear();
         mActions.addAll(actions);
@@ -379,7 +395,7 @@
             return true;
         });
 
-        if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) {
+        if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE || mMenuState == MENU_STATE_NONE) {
             actionsContainer.setVisibility(View.INVISIBLE);
         } else {
             actionsContainer.setVisibility(View.VISIBLE);
@@ -412,17 +428,15 @@
                             d.setTint(Color.WHITE);
                             actionView.setImageDrawable(d);
                         }
-                    }, mHandler);
+                    }, mMainHandler);
                     actionView.setContentDescription(action.getContentDescription());
                     if (action.isEnabled()) {
                         actionView.setOnClickListener(v -> {
-                            mHandler.post(() -> {
-                                try {
-                                    action.getActionIntent().send();
-                                } catch (CanceledException e) {
-                                    Log.w(TAG, "Failed to send action", e);
-                                }
-                            });
+                            try {
+                                action.getActionIntent().send();
+                            } catch (CanceledException e) {
+                                Log.w(TAG, "Failed to send action", e);
+                            }
                         });
                     }
                     actionView.setEnabled(action.isEnabled());
@@ -480,13 +494,13 @@
     }
 
     private void cancelDelayedHide() {
-        mHandler.removeCallbacks(mHideMenuRunnable);
+        mMainExecutor.removeCallbacks(mHideMenuRunnable);
     }
 
     private void repostDelayedHide(int delay) {
         int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
                 FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
-        mHandler.removeCallbacks(mHideMenuRunnable);
-        mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout);
+        mMainExecutor.removeCallbacks(mHideMenuRunnable);
+        mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 8c8f5c6..b19dcae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -22,12 +22,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Debug;
-import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
 import android.view.Choreographer;
@@ -40,10 +38,12 @@
 import com.android.wm.shell.animation.FloatProperties;
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
 
 import java.util.function.Consumer;
 
@@ -61,6 +61,7 @@
 
     private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
     private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
+    private static final int UNSTASH_DURATION = 250;
     private static final int LEAVE_PIP_DURATION = 300;
     private static final int SHIFT_DURATION = 300;
 
@@ -74,8 +75,6 @@
     private PhonePipMenuController mMenuController;
     private PipSnapAlgorithm mSnapAlgorithm;
 
-    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-
     /** The region that all of PIP must stay within. */
     private final Rect mFloatingAllowedArea = new Rect();
 
@@ -130,10 +129,8 @@
                         SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
 
     private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
-        mMainHandler.post(() -> {
-            mMenuController.updateMenuLayout(newBounds);
-            mPipBoundsState.setBounds(newBounds);
-        });
+        mMenuController.updateMenuLayout(newBounds);
+        mPipBoundsState.setBounds(newBounds);
     };
 
     /**
@@ -155,13 +152,13 @@
      */
     private Runnable mPostPipTransitionCallback;
 
-    private final PipTaskOrganizer.PipTransitionCallback mPipTransitionCallback =
-            new PipTaskOrganizer.PipTransitionCallback() {
+    private final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
+            new PipTransitionController.PipTransitionCallback() {
         @Override
-        public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {}
+        public void onPipTransitionStarted(int direction, Rect pipBounds) {}
 
         @Override
-        public void onPipTransitionFinished(ComponentName activity, int direction) {
+        public void onPipTransitionFinished(int direction) {
             if (mPostPipTransitionCallback != null) {
                 mPostPipTransitionCallback.run();
                 mPostPipTransitionCallback = null;
@@ -169,23 +166,28 @@
         }
 
         @Override
-        public void onPipTransitionCanceled(ComponentName activity, int direction) {}
+        public void onPipTransitionCanceled(int direction) {}
     };
 
     public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
-            PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) {
+            PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
+            FloatingContentCoordinator floatingContentCoordinator, ShellExecutor mainExecutor) {
         mContext = context;
         mPipTaskOrganizer = pipTaskOrganizer;
         mPipBoundsState = pipBoundsState;
         mMenuController = menuController;
         mSnapAlgorithm = snapAlgorithm;
         mFloatingContentCoordinator = floatingContentCoordinator;
-        mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
+        pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
         mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
                 mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
-        mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
-                mSfAnimationHandlerThreadLocal.get());
+
+        // Need to get the shell main thread sf vsync animation handler
+        mainExecutor.execute(() -> {
+            mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
+                    mSfAnimationHandlerThreadLocal.get());
+        });
 
         mResizePipUpdateListener = (target, values) -> {
             if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
@@ -256,10 +258,8 @@
                 mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
                 mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
                         (Rect newBounds) -> {
-                            mMainHandler.post(() -> {
                                 mMenuController.updateMenuLayout(newBounds);
-                            });
-                    });
+                        });
             }
         } else {
             // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
@@ -326,11 +326,7 @@
         }
         cancelPhysicsAnimation();
         mMenuController.hideMenuWithoutResize();
-        mPipTaskOrganizer.getUpdateHandler().post(() -> {
-            mPipTaskOrganizer.exitPip(skipAnimation
-                    ? 0
-                    : LEAVE_PIP_DURATION);
-        });
+        mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
     }
 
     /**
@@ -393,7 +389,8 @@
                 .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
                 .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
                 .flingThenSpring(
-                        FloatProperties.RECT_X, velocityX, isStash ? mStashConfigX : mFlingConfigX,
+                        FloatProperties.RECT_X, velocityX,
+                        isStash ? mStashConfigX : mFlingConfigX,
                         mSpringConfig, true /* flingMustReachMinOrMax */)
                 .flingThenSpring(
                         FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig);
@@ -485,6 +482,13 @@
     }
 
     /**
+     * Animates the PiP from stashed state into un-stashed, popping it out from the edge.
+     */
+    void animateToUnStashedBounds(Rect unstashedBounds) {
+        resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION);
+    }
+
+    /**
      * Animates the PiP to offset it from the IME or shelf.
      */
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index 762b738..b91ba34 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -29,7 +29,6 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
-import android.os.Handler;
 import android.os.Looper;
 import android.provider.DeviceConfig;
 import android.view.BatchedInputEventReceiver;
@@ -45,6 +44,7 @@
 
 import com.android.internal.policy.TaskResizingAlgorithm;
 import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
@@ -52,7 +52,7 @@
 import com.android.wm.shell.pip.PipUiEventLogger;
 
 import java.io.PrintWriter;
-import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
@@ -64,6 +64,7 @@
     private static final String TAG = "PipResizeGestureHandler";
     private static final int PINCH_RESIZE_SNAP_DURATION = 250;
     private static final int PINCH_RESIZE_MAX_ANGLE_ROTATION = 45;
+    private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
 
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -73,7 +74,7 @@
     private final PhonePipMenuController mPhonePipMenuController;
     private final PipUiEventLogger mPipUiEventLogger;
     private final int mDisplayId;
-    private final Executor mMainExecutor;
+    private final ShellExecutor mMainExecutor;
     private final Region mTmpRegion = new Region();
 
     private final PointF mDownPoint = new PointF();
@@ -91,7 +92,6 @@
     private final Rect mDisplayBounds = new Rect();
     private final Function<Rect, Rect> mMovementBoundsSupplier;
     private final Runnable mUpdateMovementBoundsRunnable;
-    private final Handler mHandler;
 
     private int mDelta;
     private float mTouchSlop;
@@ -119,10 +119,10 @@
             PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
             PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
             Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger,
-            PhonePipMenuController menuActivityController) {
+            PhonePipMenuController menuActivityController, ShellExecutor mainExecutor) {
         mContext = context;
         mDisplayId = context.getDisplayId();
-        mMainExecutor = context.getMainExecutor();
+        mMainExecutor = mainExecutor;
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
         mMotionHelper = motionHelper;
@@ -131,7 +131,6 @@
         mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
         mPhonePipMenuController = menuActivityController;
         mPipUiEventLogger = pipUiEventLogger;
-        mHandler = new Handler(Looper.getMainLooper());
 
         context.getDisplay().getRealSize(mMaxSize);
         reloadResources();
@@ -140,7 +139,8 @@
                 DeviceConfig.NAMESPACE_SYSTEMUI,
                 PIP_PINCH_RESIZE,
                 /* defaultValue = */ false);
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                mMainExecutor,
                 new DeviceConfig.OnPropertiesChangedListener() {
                     @Override
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
@@ -213,8 +213,14 @@
             // Register input event receiver
             mInputMonitor = InputManager.getInstance().monitorGestureInput(
                     "pip-resize", mDisplayId);
-            mInputEventReceiver = new SysUiInputEventReceiver(
-                    mInputMonitor.getInputChannel(), Looper.getMainLooper());
+            try {
+                mMainExecutor.executeBlocking(() -> {
+                    mInputEventReceiver = new PipResizeInputEventReceiver(
+                            mInputMonitor.getInputChannel(), Looper.myLooper());
+                });
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Failed to create input event receiver", e);
+            }
         }
     }
 
@@ -523,7 +529,7 @@
 
     private void finishResize() {
         if (!mLastResizeBounds.isEmpty()) {
-            final Runnable callback = () -> {
+            final Consumer<Rect> callback = (rect) -> {
                 mUserResizeBounds.set(mLastResizeBounds);
                 mMotionHelper.synchronizePinnedStackBounds();
                 mUpdateMovementBoundsRunnable.run();
@@ -534,19 +540,18 @@
             // position correctly. Drag-resize does not need to move, so just finalize resize.
             if (mUsingPinchToZoom) {
                 final Rect startBounds = new Rect(mLastResizeBounds);
+                // If user resize is pretty close to max size, just auto resize to max.
+                if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
+                        || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
+                    mLastResizeBounds.set(0, 0, mMaxSize.x, mMaxSize.y);
+                }
                 mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
                         mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
                 mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
-                        PINCH_RESIZE_SNAP_DURATION, mAngle,
-                        (Rect rect) -> {
-                            mHandler.post(callback);
-                        });
+                        PINCH_RESIZE_SNAP_DURATION, -mAngle, callback);
             } else {
                 mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
-                        PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
-                        (Rect bounds) -> {
-                            mHandler.post(callback);
-                        });
+                        PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback);
             }
             mPipUiEventLogger.log(
                     PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
@@ -593,8 +598,8 @@
         pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
     }
 
-    class SysUiInputEventReceiver extends BatchedInputEventReceiver {
-        SysUiInputEventReceiver(InputChannel channel, Looper looper) {
+    class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
+        PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
             super(channel, looper, Choreographer.getSfInstance());
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 75d674e..e69c6f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -17,7 +17,9 @@
 package com.android.wm.shell.pip.phone;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
 import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
@@ -30,8 +32,8 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.provider.DeviceConfig;
+import android.util.Log;
 import android.util.Size;
 import android.view.InputEvent;
 import android.view.MotionEvent;
@@ -49,6 +51,7 @@
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUiEventLogger;
 
 import java.io.PrintWriter;
@@ -61,9 +64,8 @@
  */
 public class PipTouchHandler {
     private static final String TAG = "PipTouchHandler";
-
-    private static final float STASH_MINIMUM_VELOCITY_X = 3000.f;
     private static final float MINIMUM_SIZE_PERCENT = 0.4f;
+    private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
 
     // Allow PIP to resize to a slightly bigger state upon touch
     private final boolean mEnableResize;
@@ -86,6 +88,8 @@
      */
     private boolean mEnableStash = true;
 
+    private float mStashVelocityThreshold;
+
     // The reference inset bounds, used to determine the dismiss fraction
     private final Rect mInsetBounds = new Rect();
     private int mExpandedShortestEdgeSize;
@@ -95,7 +99,6 @@
     private int mDeferResizeToNormalBoundsUntilRotation = -1;
     private int mDisplayRotation;
 
-    private final Handler mHandler = new Handler();
     private final PipAccessibilityInteractionConnection mConnection;
 
     // Behaviour states
@@ -154,6 +157,7 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer,
+            PipTransitionController pipTransitionController,
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger,
             ShellExecutor mainExecutor) {
@@ -166,19 +170,29 @@
         mMenuController.addListener(new PipMenuListener());
         mGesture = new DefaultPipTouchGesture();
         mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
-                mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(),
-                floatingContentCoordinator);
+                mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(), pipTransitionController,
+                floatingContentCoordinator, mainExecutor);
         mPipResizeGestureHandler =
                 new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
                         mMotionHelper, pipTaskOrganizer, this::getMovementBounds,
-                        this::updateMovementBounds, pipUiEventLogger, menuController);
+                        this::updateMovementBounds, pipUiEventLogger, menuController,
+                        mainExecutor);
         mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
-                mMotionHelper, mHandler);
-        mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
-                () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL,
-                        mPipBoundsState.getBounds(), true /* allowMenuTimeout */, willResizeMenu(),
-                        shouldShowResizeHandle()),
-                menuController::hideMenu);
+                mMotionHelper, mainExecutor);
+        mTouchState = new PipTouchState(ViewConfiguration.get(context),
+                () -> {
+                    if (mPipBoundsState.isStashed()) {
+                        animateToUnStashedState();
+                        mPipBoundsState.setStashed(STASH_TYPE_NONE);
+                    } else {
+                        mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+                                mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+                                willResizeMenu(),
+                                shouldShowResizeHandle());
+                    }
+                },
+                menuController::hideMenu,
+                mainExecutor);
 
         Resources res = context.getResources();
         mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
@@ -196,13 +210,26 @@
                 PIP_STASHING,
                 /* defaultValue = */ true);
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
-                context.getMainExecutor(),
+                mainExecutor,
                 properties -> {
                     if (properties.getKeyset().contains(PIP_STASHING)) {
                         mEnableStash = properties.getBoolean(
                                 PIP_STASHING, /* defaultValue = */ true);
                     }
                 });
+        mStashVelocityThreshold = DeviceConfig.getFloat(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+                DEFAULT_STASH_VELOCITY_THRESHOLD);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                mainExecutor,
+                properties -> {
+                    if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
+                        mStashVelocityThreshold = properties.getFloat(
+                                PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+                                DEFAULT_STASH_VELOCITY_THRESHOLD);
+                    }
+                });
     }
 
     private void reloadResources() {
@@ -708,6 +735,17 @@
         mSavedSnapFraction = -1f;
     }
 
+    private void animateToUnStashedState() {
+        final Rect pipBounds = mPipBoundsState.getBounds();
+        final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
+        final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
+        unStashedBounds.left = onLeftEdge ? mInsetBounds.left
+                : mInsetBounds.right - pipBounds.width();
+        unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
+                : mInsetBounds.right;
+        mMotionHelper.animateToUnStashedBounds(unStashedBounds);
+    }
+
     /**
      * @return the motion helper.
      */
@@ -771,7 +809,7 @@
             }
 
             if (touchState.startedDragging()) {
-                mPipBoundsState.setStashed(PipBoundsState.STASH_TYPE_NONE);
+                mPipBoundsState.setStashed(STASH_TYPE_NONE);
                 mSavedSnapFraction = -1f;
                 mPipDismissTargetHandler.showDismissTargetMaybe();
             }
@@ -824,14 +862,8 @@
 
                 // Reset the touch state on up before the fling settles
                 mTouchState.reset();
-                // If user flings the PIP window above the minimum velocity, stash PIP.
-                // Only allow stashing to the edge if the user starts dragging the PIP from that
-                // edge.
                 if (mEnableStash && !mPipBoundsState.isStashed()
-                        && ((vel.x > STASH_MINIMUM_VELOCITY_X
-                        && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
-                        || (vel.x < -STASH_MINIMUM_VELOCITY_X
-                        && mDownSavedFraction > 3f && mDownSavedFraction < 4f))) {
+                        && shouldStash(vel, getPossiblyMotionBounds())) {
                     mMotionHelper.stashToEdge(vel.x, this::stashEndAction /* endAction */);
                 } else {
                     mMotionHelper.flingToSnapTarget(vel.x, vel.y,
@@ -845,24 +877,29 @@
                             && mPipBoundsState.getBounds().height()
                             < mPipBoundsState.getMaxSize().y;
                     if (toExpand) {
+                        mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
                         animateToMaximizedState(null);
                     } else {
-                        animateToMinimizedState();
+                        animateToUnexpandedState(getUserResizeBounds());
                     }
-                    mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
                 } else {
                     // Expand to fullscreen if this is a double tap
                     // the PiP should be frozen until the transition ends
                     setTouchEnabled(false);
                     mMotionHelper.expandLeavePip();
                 }
-            } else if (mMenuState != MENU_STATE_FULL && !mPipBoundsState.isStashed()) {
+            } else if (mMenuState != MENU_STATE_FULL) {
                 if (!mTouchState.isWaitingForDoubleTap()) {
-                    // User has stalled long enough for this not to be a drag or a double tap, just
-                    // expand the menu
-                    mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
-                            true /* allowMenuTimeout */, willResizeMenu(),
-                            shouldShowResizeHandle());
+                    if (mPipBoundsState.isStashed()) {
+                        animateToUnStashedState();
+                        mPipBoundsState.setStashed(STASH_TYPE_NONE);
+                    } else {
+                        // User has stalled long enough for this not to be a drag or a double tap,
+                        // just expand the menu
+                        mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+                                true /* allowMenuTimeout */, willResizeMenu(),
+                                shouldShowResizeHandle());
+                    }
                 } else {
                     // Next touch event _may_ be the second tap for the double-tap, schedule a
                     // fallback runnable to trigger the menu if no touch event occurs before the
@@ -893,6 +930,26 @@
                 mPipExclusionBoundsChangeListener.get().accept(new Rect());
             }
         }
+
+        private boolean shouldStash(PointF vel, Rect motionBounds) {
+            // If user flings the PIP window above the minimum velocity, stash PIP.
+            // Only allow stashing to the edge if the user starts dragging the PIP from the
+            // opposite edge.
+            final boolean stashFromFlingToEdge = ((vel.x < -mStashVelocityThreshold
+                    && mDownSavedFraction > 1f && mDownSavedFraction < 2f)
+                    || (vel.x > mStashVelocityThreshold
+                    && mDownSavedFraction > 3f && mDownSavedFraction < 4f));
+
+            // If User releases the PIP window while it's out of the display bounds, put
+            // PIP into stashed mode.
+            final int offset = motionBounds.width() / 2;
+            final boolean stashFromDroppingOnEdge =
+                    (motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset
+                            || motionBounds.left
+                            < mPipBoundsState.getDisplayBounds().left - offset);
+
+            return stashFromFlingToEdge || stashFromDroppingOnEdge;
+        }
     }
 
     void setPipExclusionBoundsChangeListener(Consumer<Rect> pipExclusionBoundsChangeListener) {
@@ -923,16 +980,21 @@
     }
 
     /**
-     * @return whether the menu will resize as a part of showing the full menu.
+     * @return {@code true} if the menu should be resized on tap because app explicitly specifies
+     * PiP window size that is too small to hold all the actions.
      */
     private boolean willResizeMenu() {
         if (!mEnableResize) {
             return false;
         }
-        return mPipBoundsState.getExpandedBounds().width()
-                != mPipBoundsState.getNormalBounds().width()
-                || mPipBoundsState.getExpandedBounds().height()
-                != mPipBoundsState.getNormalBounds().height();
+        final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
+        if (estimatedMinMenuSize == null) {
+            Log.wtf(TAG, "Failed to get estimated menu size");
+            return false;
+        }
+        final Rect currentBounds = mPipBoundsState.getBounds();
+        return currentBounds.width() < estimatedMinMenuSize.getWidth()
+                || currentBounds.height() < estimatedMinMenuSize.getHeight();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index 5f2327c..53303ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -25,6 +25,7 @@
 import android.view.ViewConfiguration;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.common.ShellExecutor;
 
 import java.io.PrintWriter;
 
@@ -39,7 +40,7 @@
     public static final long DOUBLE_TAP_TIMEOUT = 200;
     static final long HOVER_EXIT_TIMEOUT = 50;
 
-    private final Handler mHandler;
+    private final ShellExecutor mMainExecutor;
     private final ViewConfiguration mViewConfig;
     private final Runnable mDoubleTapTimeoutCallback;
     private final Runnable mHoverExitTimeoutCallback;
@@ -67,12 +68,12 @@
     private int mActivePointerId;
     private int mLastTouchDisplayId = Display.INVALID_DISPLAY;
 
-    public PipTouchState(ViewConfiguration viewConfig, Handler handler,
-            Runnable doubleTapTimeoutCallback, Runnable hoverExitTimeoutCallback) {
+    public PipTouchState(ViewConfiguration viewConfig, Runnable doubleTapTimeoutCallback,
+            Runnable hoverExitTimeoutCallback, ShellExecutor mainExecutor) {
         mViewConfig = viewConfig;
-        mHandler = handler;
         mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
         mHoverExitTimeoutCallback = hoverExitTimeoutCallback;
+        mMainExecutor = mainExecutor;
     }
 
     /**
@@ -116,7 +117,7 @@
                 mIsDragging = false;
                 mLastDownTouchTime = mDownTouchTime;
                 if (mDoubleTapTimeoutCallback != null) {
-                    mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
+                    mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
                 }
                 break;
             }
@@ -324,8 +325,8 @@
     public void scheduleDoubleTapTimeoutCallback() {
         if (mIsWaitingForDoubleTap) {
             long delay = getDoubleTapTimeoutCallbackDelay();
-            mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
-            mHandler.postDelayed(mDoubleTapTimeoutCallback, delay);
+            mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
+            mMainExecutor.executeDelayed(mDoubleTapTimeoutCallback, delay);
         }
     }
 
@@ -342,17 +343,17 @@
      */
     public void removeDoubleTapTimeoutCallback() {
         mIsWaitingForDoubleTap = false;
-        mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
+        mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
     }
 
     @VisibleForTesting
     public void scheduleHoverExitTimeoutCallback() {
-        mHandler.removeCallbacks(mHoverExitTimeoutCallback);
-        mHandler.postDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
+        mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
+        mMainExecutor.executeDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
     }
 
     void removeHoverExitTimeoutCallback() {
-        mHandler.removeCallbacks(mHoverExitTimeoutCallback);
+        mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
     }
 
     void addMovementToVelocityTracker(MotionEvent event) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java
deleted file mode 100644
index d686cac..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.pip.phone;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-
-/**
- * Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton
- * foreground thread for each process for updating PIP.
- */
-public final class PipUpdateThread extends HandlerThread {
-    private static PipUpdateThread sInstance;
-    private static Handler sHandler;
-
-    private PipUpdateThread() {
-        super("pip");
-    }
-
-    private static void ensureThreadLocked() {
-        if (sInstance == null) {
-            sInstance = new PipUpdateThread();
-            sInstance.start();
-            sHandler = new Handler(sInstance.getLooper());
-        }
-    }
-
-    /**
-     * @return the static update thread instance
-     */
-    public static PipUpdateThread get() {
-        synchronized (PipUpdateThread.class) {
-            ensureThreadLocked();
-            return sInstance;
-        }
-    }
-    /**
-     * @return the static update thread handler instance
-     */
-    public static Handler getHandler() {
-        synchronized (PipUpdateThread.class) {
-            ensureThreadLocked();
-            return sHandler;
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 8bc60f9..56f183f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -36,6 +36,8 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
@@ -44,6 +46,7 @@
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -51,7 +54,7 @@
 /**
  * Manages the picture-in-picture (PIP) UI and states.
  */
-public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallback,
+public class TvPipController implements PipTransitionController.PipTransitionCallback,
         TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
     private static final String TAG = "TvPipController";
     static final boolean DEBUG = true;
@@ -90,26 +93,58 @@
     private final PipMediaController mPipMediaController;
     private final TvPipNotificationController mPipNotificationController;
     private final TvPipMenuController mTvPipMenuController;
+    private final ShellExecutor mMainExecutor;
+    private final TvPipImpl mImpl = new TvPipImpl();
 
     private @State int mState = STATE_NO_PIP;
     private int mPinnedTaskId = NONEXISTENT_TASK_ID;
 
     private int mResizeAnimationDuration;
 
-    public TvPipController(
+    public static Pip create(
             Context context,
             PipBoundsState pipBoundsState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipTaskOrganizer pipTaskOrganizer,
+            PipTransitionController pipTransitionController,
             TvPipMenuController tvPipMenuController,
             PipMediaController pipMediaController,
             TvPipNotificationController pipNotificationController,
             TaskStackListenerImpl taskStackListener,
-            WindowManagerShellWrapper wmShell) {
+            WindowManagerShellWrapper wmShell,
+            ShellExecutor mainExecutor) {
+        return new TvPipController(
+                context,
+                pipBoundsState,
+                pipBoundsAlgorithm,
+                pipTaskOrganizer,
+                pipTransitionController,
+                tvPipMenuController,
+                pipMediaController,
+                pipNotificationController,
+                taskStackListener,
+                wmShell,
+                mainExecutor).mImpl;
+    }
+
+    private TvPipController(
+            Context context,
+            PipBoundsState pipBoundsState,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipTaskOrganizer pipTaskOrganizer,
+            PipTransitionController pipTransitionController,
+            TvPipMenuController tvPipMenuController,
+            PipMediaController pipMediaController,
+            TvPipNotificationController pipNotificationController,
+            TaskStackListenerImpl taskStackListener,
+            WindowManagerShellWrapper wmShell,
+            ShellExecutor mainExecutor) {
         mContext = context;
+        mMainExecutor = mainExecutor;
 
         mPipBoundsState = pipBoundsState;
-        mPipBoundsState.setDisplayInfo(getDisplayInfo());
+        mPipBoundsState.setDisplayId(context.getDisplayId());
+        mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
 
         mPipMediaController = pipMediaController;
@@ -121,7 +156,7 @@
         mTvPipMenuController.setDelegate(this);
 
         mPipTaskOrganizer = pipTaskOrganizer;
-        mPipTaskOrganizer.registerPipTransitionCallback(this);
+        pipTransitionController.registerPipTransitionCallback(this);
 
         loadConfigurations();
 
@@ -129,8 +164,7 @@
         registerWmShellPinnedStackListener(wmShell);
     }
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
+    private void onConfigurationChanged(Configuration newConfig) {
         if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState));
 
         if (isPipShown()) {
@@ -145,8 +179,7 @@
     /**
      * Returns {@code true} if Pip is shown.
      */
-    @Override
-    public boolean isPipShown() {
+    private boolean isPipShown() {
         return mState != STATE_NO_PIP;
     }
 
@@ -211,8 +244,7 @@
      * @param state the to determine the Pip bounds. IMPORTANT: should always match the current
      *              state of the Controller.
      */
-    @Override
-    public void resizePinnedStack(@State int state) {
+    private void resizePinnedStack(@State int state) {
         if (state != mState) {
             throw new IllegalArgumentException("The passed state should match the current state!");
         }
@@ -240,8 +272,7 @@
         mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
     }
 
-    @Override
-    public void registerSessionListenerForCurrentUser() {
+    private void registerSessionListenerForCurrentUser() {
         mPipMediaController.registerSessionListenerForCurrentUser();
     }
 
@@ -275,17 +306,17 @@
     }
 
     @Override
-    public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
+    public void onPipTransitionStarted(int direction, Rect pipBounds) {
         if (DEBUG) Log.d(TAG, "onPipTransition_Started(), state=" + stateToName(mState));
     }
 
     @Override
-    public void onPipTransitionCanceled(ComponentName activity, int direction) {
+    public void onPipTransitionCanceled(int direction) {
         if (DEBUG) Log.d(TAG, "onPipTransition_Canceled(), state=" + stateToName(mState));
     }
 
     @Override
-    public void onPipTransitionFinished(ComponentName activity, int direction) {
+    public void onPipTransitionFinished(int direction) {
         if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState));
 
         if (mState == STATE_PIP_MENU) {
@@ -418,4 +449,20 @@
                 throw new IllegalArgumentException("Unknown state " + state);
         }
     }
+
+    private class TvPipImpl implements Pip {
+        @Override
+        public void onConfigurationChanged(Configuration newConfig) {
+            mMainExecutor.execute(() -> {
+                TvPipController.this.onConfigurationChanged(newConfig);
+            });
+        }
+
+        @Override
+        public void registerSessionListenerForCurrentUser() {
+            mMainExecutor.execute(() -> {
+                TvPipController.this.registerSessionListenerForCurrentUser();
+            });
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 470ab0c..ee41b41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
+import android.os.Handler;
 import android.util.Log;
 import android.view.SurfaceControl;
 
@@ -47,6 +48,7 @@
     private final Context mContext;
     private final SystemWindows mSystemWindows;
     private final PipBoundsState mPipBoundsState;
+    private final Handler mMainHandler;
 
     private Delegate mDelegate;
     private SurfaceControl mLeash;
@@ -56,10 +58,12 @@
     private final List<RemoteAction> mAppActions = new ArrayList<>();
 
     public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
-            SystemWindows systemWindows, PipMediaController pipMediaController) {
+            SystemWindows systemWindows, PipMediaController pipMediaController,
+            Handler mainHandler) {
         mContext = context;
         mPipBoundsState = pipBoundsState;
         mSystemWindows = systemWindows;
+        mMainHandler = mainHandler;
 
         // We need to "close" the menu the platform call for all the system dialogs to close (for
         // example, on the Home button press).
@@ -69,8 +73,9 @@
                 hideMenu();
             }
         };
-        context.registerReceiver(closeSystemDialogsBroadcastReceiver,
-                new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+        context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
+                mainHandler);
 
         pipMediaController.addActionListener(this::onMediaActionsChanged);
     }
@@ -199,9 +204,9 @@
             return;
         }
         if (!mAppActions.isEmpty()) {
-            mMenuView.setAdditionalActions(mAppActions);
+            mMenuView.setAdditionalActions(mAppActions, mMainHandler);
         } else {
-            mMenuView.setAdditionalActions(mMediaActions);
+            mMenuView.setAdditionalActions(mMediaActions, mMainHandler);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index e08ca52..d6cd9ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -49,7 +49,7 @@
 /**
  * A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu
  * actions: Fullscreen and Close, but could also display "additional" actions, that may be set via
- * a {@link #setAdditionalActions(List)} call.
+ * a {@link #setAdditionalActions(List, Handler)} call.
  */
 public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
     private static final String TAG = "TvPipMenuView";
@@ -57,7 +57,6 @@
 
     private static final float DISABLED_ACTION_ALPHA = 0.54f;
 
-    private final Handler mUiThreadHandler;
     private final Animator mFadeInAnimation;
     private final Animator mFadeOutAnimation;
     @Nullable private Listener mListener;
@@ -80,7 +79,6 @@
     public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mUiThreadHandler = new Handler(Looper.getMainLooper());
 
         inflate(context, R.layout.tv_pip_menu, this);
 
@@ -132,7 +130,7 @@
         }
     }
 
-    void setAdditionalActions(List<RemoteAction> actions) {
+    void setAdditionalActions(List<RemoteAction> actions, Handler mainHandler) {
         if (DEBUG) Log.d(TAG, "setAdditionalActions()");
 
         // Make sure we exactly as many additional buttons as we have actions to display.
@@ -176,7 +174,7 @@
             action.getIcon().loadDrawableAsync(mContext, drawable -> {
                 drawable.setTint(Color.WHITE);
                 button.setImageDrawable(drawable);
-            }, mUiThreadHandler);
+            }, mainHandler);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
index ce4b608..a474831 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.media.MediaMetadata;
+import android.os.Handler;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -60,6 +61,7 @@
     private final NotificationManager mNotificationManager;
     private final Notification.Builder mNotificationBuilder;
     private final ActionBroadcastReceiver mActionBroadcastReceiver;
+    private final Handler mMainHandler;
     private Delegate mDelegate;
 
     private String mDefaultTitle;
@@ -70,10 +72,12 @@
     private String mMediaTitle;
     private Bitmap mArt;
 
-    public TvPipNotificationController(Context context, PipMediaController pipMediaController) {
+    public TvPipNotificationController(Context context, PipMediaController pipMediaController,
+            Handler mainHandler) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mNotificationManager = context.getSystemService(NotificationManager.class);
+        mMainHandler = mainHandler;
 
         mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
                 .setLocalOnly(true)
@@ -219,7 +223,8 @@
         void register() {
             if (mRegistered) return;
 
-            mContext.registerReceiver(this, mIntentFilter, UserHandle.USER_ALL);
+            mContext.registerReceiverForAllUsers(this, mIntentFilter, null /* permission */,
+                    mMainHandler);
             mRegistered = true;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
new file mode 100644
index 0000000..b7caf72
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.pip.tv;
+
+import android.app.TaskInfo;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * PiP Transition for TV.
+ * TODO: Implement animation once TV is using Transitions.
+ */
+public class TvPipTransition extends PipTransitionController {
+    public TvPipTransition(PipBoundsState pipBoundsState,
+            PipMenuController pipMenuController,
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipAnimationController pipAnimationController,
+            Transitions transitions,
+            @NonNull ShellTaskOrganizer shellTaskOrganizer) {
+        super(pipBoundsState, pipMenuController, pipBoundsAlgorithm, pipAnimationController,
+                transitions, shellTaskOrganizer);
+    }
+
+    @Override
+    public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, int direction,
+            SurfaceControl.Transaction tx) {
+
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction t,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        return null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
new file mode 100644
index 0000000..e47e1ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.sizecompatui;
+
+import android.app.ActivityClient;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+
+import com.android.wm.shell.R;
+
+/** Button to restart the size compat activity. */
+class SizeCompatRestartButton extends ImageButton implements View.OnClickListener,
+        View.OnLongClickListener {
+    private static final String TAG = "SizeCompatRestartButton";
+
+    final WindowManager.LayoutParams mWinParams;
+    final boolean mShouldShowHint;
+    final int mDisplayId;
+    final int mPopupOffsetX;
+    final int mPopupOffsetY;
+
+    private IBinder mLastActivityToken;
+    private PopupWindow mShowingHint;
+
+    SizeCompatRestartButton(Context context, int displayId, boolean hasShownHint) {
+        super(context);
+        mDisplayId = displayId;
+        mShouldShowHint = !hasShownHint;
+        final Drawable drawable = context.getDrawable(R.drawable.size_compat_restart_button);
+        setImageDrawable(drawable);
+        setContentDescription(context.getString(R.string.restart_button_description));
+
+        final int drawableW = drawable.getIntrinsicWidth();
+        final int drawableH = drawable.getIntrinsicHeight();
+        mPopupOffsetX = drawableW / 2;
+        mPopupOffsetY = drawableH * 2;
+
+        final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
+        final GradientDrawable mask = new GradientDrawable();
+        mask.setShape(GradientDrawable.OVAL);
+        mask.setColor(color);
+        setBackground(new RippleDrawable(color, null /* content */, mask));
+        setOnClickListener(this);
+        setOnLongClickListener(this);
+
+        mWinParams = new WindowManager.LayoutParams();
+        mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
+        mWinParams.width = drawableW * 2;
+        mWinParams.height = drawableH * 2;
+        mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+        mWinParams.format = PixelFormat.TRANSLUCENT;
+        mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        mWinParams.setTitle(SizeCompatRestartButton.class.getSimpleName()
+                + context.getDisplayId());
+    }
+
+    void updateLastTargetActivity(IBinder activityToken) {
+        mLastActivityToken = activityToken;
+    }
+
+    /** @return {@code false} if the target display is invalid. */
+    boolean show() {
+        try {
+            getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
+        } catch (WindowManager.InvalidDisplayException e) {
+            // The target display may have been removed when the callback has just arrived.
+            Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
+            return false;
+        }
+        return true;
+    }
+
+    void remove() {
+        if (mShowingHint != null) {
+            mShowingHint.dismiss();
+        }
+        getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken);
+    }
+
+    @Override
+    public boolean onLongClick(View v) {
+        showHint();
+        return true;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mShouldShowHint) {
+            showHint();
+        }
+    }
+
+    @Override
+    public void setLayoutDirection(int layoutDirection) {
+        final int gravity = getGravity(layoutDirection);
+        if (mWinParams.gravity != gravity) {
+            mWinParams.gravity = gravity;
+            if (mShowingHint != null) {
+                mShowingHint.dismiss();
+                showHint();
+            }
+            getContext().getSystemService(WindowManager.class).updateViewLayout(this,
+                    mWinParams);
+        }
+        super.setLayoutDirection(layoutDirection);
+    }
+
+    void showHint() {
+        if (mShowingHint != null) {
+            return;
+        }
+
+        final View popupView = LayoutInflater.from(getContext()).inflate(
+                R.layout.size_compat_mode_hint, null /* root */);
+        final PopupWindow popupWindow = new PopupWindow(popupView,
+                LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        popupWindow.setWindowLayoutType(mWinParams.type);
+        popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
+        popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
+        popupWindow.setClippingEnabled(false);
+        popupWindow.setOnDismissListener(() -> mShowingHint = null);
+        mShowingHint = popupWindow;
+
+        final Button gotItButton = popupView.findViewById(R.id.got_it);
+        gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
+                null /* content */, null /* mask */));
+        gotItButton.setOnClickListener(view -> popupWindow.dismiss());
+        popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
+    }
+
+    private static int getGravity(int layoutDirection) {
+        return Gravity.BOTTOM
+                | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
new file mode 100644
index 0000000..11f22ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.sizecompatui;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.IBinder;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+/**
+ * Interface to engage size compat mode UI.
+ */
+@ExternalThread
+public interface SizeCompatUI {
+    /**
+     * Called when the Task info changed. Creates and updates the restart button if there is an
+     * activity in size compat, or removes the restart button if there is no size compat activity.
+     *
+     * @param displayId display the task and activity are in.
+     * @param taskId task the activity is in.
+     * @param taskBounds task bounds to place the restart button in.
+     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
+     *                           top activity in this Task is not in size compat.
+     * @param taskListener listener to handle the Task Surface placement.
+     */
+    void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+            @Nullable IBinder sizeCompatActivity,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
new file mode 100644
index 0000000..286c3b6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.sizecompatui;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.ShellExecutor;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Shows a restart-activity button on Task when the foreground activity is in size compatibility
+ * mode.
+ */
+public class SizeCompatUIController implements DisplayController.OnDisplaysChangedListener,
+        DisplayImeController.ImePositionProcessor {
+    private static final String TAG = "SizeCompatUI";
+
+    /** The showing buttons by task id. */
+    private final SparseArray<SizeCompatRestartButton> mActiveButtons = new SparseArray<>(1);
+    /** Avoid creating display context frequently for non-default display. */
+    private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
+
+    @VisibleForTesting
+    final SizeCompatUI mImpl = new SizeCompatUIImpl();
+    private final Context mContext;
+    private final ShellExecutor mMainExecutor;
+    private final DisplayController mDisplayController;
+    private final DisplayImeController mImeController;
+
+    /** Only show once automatically in the process life. */
+    private boolean mHasShownHint;
+
+    /** Creates the {@link SizeCompatUIController}. */
+    public static SizeCompatUI create(Context context,
+            DisplayController displayController,
+            DisplayImeController imeController,
+            ShellExecutor mainExecutor) {
+        return new SizeCompatUIController(context, displayController, imeController, mainExecutor)
+                .mImpl;
+    }
+
+    @VisibleForTesting
+    SizeCompatUIController(Context context,
+            DisplayController displayController,
+            DisplayImeController imeController,
+            ShellExecutor mainExecutor) {
+        mContext = context;
+        mMainExecutor = mainExecutor;
+        mDisplayController = displayController;
+        mImeController = imeController;
+        mDisplayController.addDisplayWindowListener(this);
+        mImeController.addPositionProcessor(this);
+    }
+
+    private void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+            @Nullable IBinder sizeCompatActivity,
+            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+        // TODO Draw button on Task surface
+        if (taskBounds == null || sizeCompatActivity == null || taskListener == null) {
+            // Null token means the current foreground activity is not in size compatibility mode.
+            removeRestartButton(taskId);
+        } else {
+            updateRestartButton(displayId, taskId, sizeCompatActivity);
+        }
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        mDisplayContextCache.remove(displayId);
+        for (int i = 0; i < mActiveButtons.size(); i++) {
+            final int taskId = mActiveButtons.keyAt(i);
+            final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+            if (button != null && button.mDisplayId == displayId) {
+                removeRestartButton(taskId);
+            }
+        }
+    }
+
+    @Override
+    public void onImeVisibilityChanged(int displayId, boolean isShowing) {
+        final int newVisibility = isShowing ? View.GONE : View.VISIBLE;
+        for (int i = 0; i < mActiveButtons.size(); i++) {
+            final int taskId = mActiveButtons.keyAt(i);
+            final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+            if (button == null || button.mDisplayId != displayId) {
+                continue;
+            }
+
+            // Hide the button when input method is showing.
+            if (button.getVisibility() != newVisibility) {
+                button.setVisibility(newVisibility);
+            }
+        }
+    }
+
+    private void updateRestartButton(int displayId, int taskId, IBinder activityToken) {
+        SizeCompatRestartButton restartButton = mActiveButtons.get(taskId);
+        if (restartButton != null) {
+            restartButton.updateLastTargetActivity(activityToken);
+            return;
+        }
+
+        final Context context = getOrCreateDisplayContext(displayId);
+        if (context == null) {
+            Log.i(TAG, "Cannot get context for display " + displayId);
+            return;
+        }
+
+        restartButton = createRestartButton(context, displayId);
+        restartButton.updateLastTargetActivity(activityToken);
+        if (restartButton.show()) {
+            mActiveButtons.append(taskId, restartButton);
+        } else {
+            onDisplayRemoved(displayId);
+        }
+    }
+
+    @VisibleForTesting
+    SizeCompatRestartButton createRestartButton(Context context, int displayId) {
+        final SizeCompatRestartButton button = new SizeCompatRestartButton(context, displayId,
+                mHasShownHint);
+        // Only show hint for the first time.
+        mHasShownHint = true;
+        return button;
+    }
+
+    private void removeRestartButton(int taskId) {
+        final SizeCompatRestartButton button = mActiveButtons.get(taskId);
+        if (button != null) {
+            button.remove();
+            mActiveButtons.remove(taskId);
+        }
+    }
+
+    private Context getOrCreateDisplayContext(int displayId) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            return mContext;
+        }
+        Context context = null;
+        final WeakReference<Context> ref = mDisplayContextCache.get(displayId);
+        if (ref != null) {
+            context = ref.get();
+        }
+        if (context == null) {
+            Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
+            if (display != null) {
+                context = mContext.createDisplayContext(display);
+                mDisplayContextCache.put(displayId, new WeakReference<>(context));
+            }
+        }
+        return context;
+    }
+
+    private class SizeCompatUIImpl implements SizeCompatUI {
+        @Override
+        public void onSizeCompatInfoChanged(int displayId, int taskId, @Nullable Rect taskBounds,
+                @Nullable IBinder sizeCompatActivity,
+                @Nullable ShellTaskOrganizer.TaskListener taskListener) {
+            mMainExecutor.execute(() ->
+                    SizeCompatUIController.this.onSizeCompatInfoChanged(displayId, taskId,
+                            taskBounds, sizeCompatActivity, taskListener));
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 7c1b9d8..2c68092 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,12 +18,16 @@
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
+import android.app.PendingIntent;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 
 import java.io.PrintWriter;
 
@@ -31,46 +35,93 @@
  * Interface to engage split-screen feature.
  */
 @ExternalThread
-public interface SplitScreen {
+public interface SplitScreen extends DragAndDropPolicy.Starter {
     /**
-     * Specifies that the side-stage is positioned at the top half of the screen if
+     * Stage position isn't specified normally meaning to use what ever it is currently set to.
+     */
+    int STAGE_POSITION_UNDEFINED = -1;
+    /**
+     * Specifies that a stage is positioned at the top half of the screen if
      * in portrait mode or at the left half of the screen if in landscape mode.
      */
-    int SIDE_STAGE_POSITION_TOP_OR_LEFT = 0;
+    int STAGE_POSITION_TOP_OR_LEFT = 0;
 
     /**
-     * Specifies that the side-stage is positioned at the bottom half of the screen if
+     * Specifies that a stage is positioned at the bottom half of the screen if
      * in portrait mode or at the right half of the screen if in landscape mode.
      */
-    int SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+    int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
 
-    @IntDef(prefix = { "SIDE_STAGE_POSITION_" }, value = {
-            SIDE_STAGE_POSITION_TOP_OR_LEFT,
-            SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT
+    @IntDef(prefix = { "STAGE_POSITION_" }, value = {
+            STAGE_POSITION_UNDEFINED,
+            STAGE_POSITION_TOP_OR_LEFT,
+            STAGE_POSITION_BOTTOM_OR_RIGHT
     })
-    @interface SideStagePosition {}
+    @interface StagePosition {}
+
+    /**
+     * Stage type isn't specified normally meaning to use what ever the default is.
+     * E.g. exit split-screen and launch the app in fullscreen.
+     */
+    int STAGE_TYPE_UNDEFINED = -1;
+    /**
+     * The main stage type.
+     * @see MainStage
+     */
+    int STAGE_TYPE_MAIN = 0;
+
+    /**
+     * The side stage type.
+     * @see SideStage
+     */
+    int STAGE_TYPE_SIDE = 1;
+
+    @IntDef(prefix = { "STAGE_TYPE_" }, value = {
+            STAGE_TYPE_UNDEFINED,
+            STAGE_TYPE_MAIN,
+            STAGE_TYPE_SIDE
+    })
+    @interface StageType {}
+
+    /** Callback interface for listening to changes in a split-screen stage. */
+    interface SplitScreenListener {
+        void onStagePositionChanged(@StageType int stage, @StagePosition int position);
+        void onTaskStageChanged(int taskId, @StageType int stage);
+    }
 
     /** @return {@code true} if split-screen is currently visible. */
     boolean isSplitScreenVisible();
     /** Moves a task in the side-stage of split-screen. */
-    boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition);
+    boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition);
     /** Moves a task in the side-stage of split-screen. */
     boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SideStagePosition int sideStagePosition);
+            @StagePosition int sideStagePosition);
     /** Removes a task from the side-stage of split-screen. */
     boolean removeFromSideStage(int taskId);
     /** Sets the position of the side-stage. */
-    void setSideStagePosition(@SideStagePosition int sideStagePosition);
+    void setSideStagePosition(@StagePosition int sideStagePosition);
     /** Hides the side-stage if it is currently visible. */
     void setSideStageVisibility(boolean visible);
+    default void enterSplitScreen(int taskId, boolean leftOrTop) {
+        moveToSideStage(taskId,
+                leftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT);
+    }
     /** Removes the split-screen stages. */
     void exitSplitScreen();
     /** Gets the stage bounds. */
     void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds);
-    /** Updates the launch activity options for the split position we want to launch it in. */
-    void updateActivityOptions(Bundle opts, @SideStagePosition int position);
     /** Dumps current status of split-screen. */
     void dump(@NonNull PrintWriter pw, String prefix);
     /** Called when the shell organizer has been registered. */
     void onOrganizerRegistered();
+
+    void registerSplitScreenListener(SplitScreenListener listener);
+    void unregisterSplitScreenListener(SplitScreenListener listener);
+
+    void startTask(int taskId,
+            @StageType int stage, @StagePosition int position, @Nullable Bundle options);
+    void startShortcut(String packageName, String shortcutId, @StageType int stage,
+            @StagePosition int position, @Nullable Bundle options, UserHandle user);
+    void startIntent(PendingIntent intent,
+            @StageType int stage, @StagePosition int position, @Nullable Bundle options);
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 27d3b81..18dd53b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -19,11 +19,19 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -69,7 +77,7 @@
     }
 
     @Override
-    public boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition) {
+    public boolean moveToSideStage(int taskId, @StagePosition int sideStagePosition) {
         final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
         if (task == null) {
             throw new IllegalArgumentException("Unknown taskId" + taskId);
@@ -79,7 +87,7 @@
 
     @Override
     public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SideStagePosition int sideStagePosition) {
+            @StagePosition int sideStagePosition) {
         return mStageCoordinator.moveToSideStage(task, sideStagePosition);
     }
 
@@ -89,7 +97,7 @@
     }
 
     @Override
-    public void setSideStagePosition(@SideStagePosition int sideStagePosition) {
+    public void setSideStagePosition(@StagePosition int sideStagePosition) {
         mStageCoordinator.setSideStagePosition(sideStagePosition);
     }
 
@@ -109,8 +117,103 @@
     }
 
     @Override
-    public void updateActivityOptions(Bundle opts, @SideStagePosition int position) {
-        mStageCoordinator.updateActivityOptions(opts, position);
+    public void registerSplitScreenListener(SplitScreenListener listener) {
+        mStageCoordinator.registerSplitScreenListener(listener);
+    }
+
+    @Override
+    public void unregisterSplitScreenListener(SplitScreenListener listener) {
+        mStageCoordinator.unregisterSplitScreenListener(listener);
+    }
+
+    @Override
+    public void startTask(int taskId,
+            @StageType int stage, @StagePosition int position, @Nullable Bundle options) {
+        options = resolveStartStage(stage, position, options);
+
+        try {
+            ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to launch task", e);
+        }
+    }
+
+    @Override
+    public void startShortcut(String packageName, String shortcutId, @StageType int stage,
+            @StagePosition int position, @Nullable Bundle options, UserHandle user) {
+        options = resolveStartStage(stage, position, options);
+
+        try {
+            LauncherApps launcherApps =
+                    mContext.getSystemService(LauncherApps.class);
+            launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+                    options, user);
+        } catch (ActivityNotFoundException e) {
+            Slog.e(TAG, "Failed to launch shortcut", e);
+        }
+    }
+
+    @Override
+    public void startIntent(PendingIntent intent,
+            @StageType int stage, @StagePosition int position, @Nullable Bundle options) {
+        options = resolveStartStage(stage, position, options);
+
+        try {
+            intent.send(null, 0, null, null, null, null, options);
+        } catch (PendingIntent.CanceledException e) {
+            Slog.e(TAG, "Failed to launch activity", e);
+        }
+    }
+
+    private Bundle resolveStartStage(@StageType int stage, @StagePosition int position,
+            @Nullable Bundle options) {
+        switch (stage) {
+            case STAGE_TYPE_UNDEFINED: {
+                // Use the stage of the specified position is valid.
+                if (position != STAGE_POSITION_UNDEFINED) {
+                    if (position == mStageCoordinator.getSideStagePosition()) {
+                        options = resolveStartStage(STAGE_TYPE_SIDE, position, options);
+                    } else {
+                        options = resolveStartStage(STAGE_TYPE_MAIN, position, options);
+                    }
+                } else {
+                    // Exit split-screen and launch fullscreen since stage wasn't specified.
+                    mStageCoordinator.exitSplitScreen();
+                }
+                break;
+            }
+            case STAGE_TYPE_SIDE: {
+                if (position != STAGE_POSITION_UNDEFINED) {
+                    mStageCoordinator.setSideStagePosition(position);
+                } else {
+                    position = mStageCoordinator.getSideStagePosition();
+                }
+                if (options == null) {
+                    options = new Bundle();
+                }
+                mStageCoordinator.updateActivityOptions(options, position);
+                break;
+            }
+            case STAGE_TYPE_MAIN: {
+                if (position != STAGE_POSITION_UNDEFINED) {
+                    // Set the side stage opposite of what we want to the main stage.
+                    final int sideStagePosition = position == STAGE_POSITION_TOP_OR_LEFT
+                            ? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT;
+                    mStageCoordinator.setSideStagePosition(sideStagePosition);
+                } else {
+                    position = mStageCoordinator.getMainStagePosition();
+                }
+                if (options == null) {
+                    options = new Bundle();
+                }
+                mStageCoordinator.updateActivityOptions(options, position);
+                break;
+            }
+            default:
+                throw new IllegalArgumentException("Unknown stage=" + stage);
+        }
+
+        return options;
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index d571e75..176852b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -17,12 +17,14 @@
 package com.android.wm.shell.splitscreen;
 
 import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import android.app.ActivityManager;
 import android.content.Context;
@@ -41,6 +43,8 @@
 import com.android.wm.shell.common.split.SplitLayout;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -64,8 +68,7 @@
     private final StageListenerImpl mMainStageListener = new StageListenerImpl();
     private final SideStage mSideStage;
     private final StageListenerImpl mSideStageListener = new StageListenerImpl();
-    private @SplitScreen.SideStagePosition int mSideStagePosition =
-            SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+    private @SplitScreen.StagePosition int mSideStagePosition = STAGE_POSITION_BOTTOM_OR_RIGHT;
 
     private final int mDisplayId;
     private SplitLayout mSplitLayout;
@@ -75,6 +78,7 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private DisplayAreaInfo mDisplayAreaInfo;
     private final Context mContext;
+    private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
 
     StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) {
@@ -107,7 +111,7 @@
     }
 
     boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
-            @SplitScreen.SideStagePosition int sideStagePosition) {
+            @SplitScreen.StagePosition int sideStagePosition) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         mSideStagePosition = sideStagePosition;
         mMainStage.activate(getMainStageBounds(), wct);
@@ -130,7 +134,16 @@
         return result;
     }
 
-    void setSideStagePosition(@SplitScreen.SideStagePosition int sideStagePosition) {
+    @SplitScreen.StagePosition int getSideStagePosition() {
+        return mSideStagePosition;
+    }
+
+    @SplitScreen.StagePosition int getMainStagePosition() {
+        return mSideStagePosition ==  STAGE_POSITION_TOP_OR_LEFT
+                ? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT;
+    }
+
+    void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) {
         mSideStagePosition = sideStagePosition;
         if (mSideStageListener.mVisible) {
             onStageVisibilityChanged(mSideStageListener);
@@ -163,7 +176,7 @@
         outBottomOrRightBounds.set(mSplitLayout.getBounds2());
     }
 
-    void updateActivityOptions(Bundle opts, @SplitScreen.SideStagePosition int position) {
+    void updateActivityOptions(Bundle opts, @SplitScreen.StagePosition int position) {
         final StageTaskListener stage = position == mSideStagePosition ? mSideStage : mMainStage;
         opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
 
@@ -176,6 +189,35 @@
         }
     }
 
+    void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+        if (mListeners.contains(listener)) return;
+        mListeners.add(listener);
+        listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+        listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+        mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+        mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+    }
+
+    void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
+        mListeners.remove(listener);
+    }
+
+    private void onStageChildTaskStatusChanged(
+            StageListenerImpl stageListener, int taskId, boolean present) {
+
+        int stage;
+        if (present) {
+            stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+        } else {
+            // No longer on any stage
+            stage = STAGE_TYPE_UNDEFINED;
+        }
+
+        for (int i = mListeners.size() - 1; i >= 0; --i) {
+            mListeners.get(i).onTaskStageChanged(taskId, stage);
+        }
+    }
+
     private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
         if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
             final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -299,7 +341,7 @@
     @Override
     public void onSnappedToDismiss(boolean bottomOrRight) {
         final boolean mainStageToTop = bottomOrRight
-                && mSideStagePosition == SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+                && mSideStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
         exitSplitScreen(mainStageToTop ? mMainStage : mSideStage);
     }
 
@@ -326,8 +368,8 @@
 
     @Override
     public void onDoubleTappedDivider() {
-        setSideStagePosition(mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
-                ? SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT : SIDE_STAGE_POSITION_TOP_OR_LEFT);
+        setSideStagePosition(mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
+                ? STAGE_POSITION_BOTTOM_OR_RIGHT : STAGE_POSITION_TOP_OR_LEFT);
     }
 
     @Override
@@ -380,12 +422,12 @@
     }
 
     private Rect getSideStageBounds() {
-        return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
+        return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
                 ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
     }
 
     private Rect getMainStageBounds() {
-        return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
+        return mSideStagePosition == STAGE_POSITION_TOP_OR_LEFT
                 ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
     }
 
@@ -429,6 +471,11 @@
         }
 
         @Override
+        public void onChildTaskStatusChanged(int taskId, boolean present) {
+            StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present);
+        }
+
+        @Override
         public void onRootTaskVanished() {
             reset();
             StageCoordinator.this.onStageRootTaskVanished(this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 1aa7552..6532993 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -58,6 +58,7 @@
     public interface StageListenerCallbacks {
         void onRootTaskAppeared();
         void onStatusChanged(boolean visible, boolean hasChildren);
+        void onChildTaskStatusChanged(int taskId, boolean present);
         void onRootTaskVanished();
     }
     private final StageListenerCallbacks mCallbacks;
@@ -83,9 +84,11 @@
             mRootTaskInfo = taskInfo;
             mCallbacks.onRootTaskAppeared();
         } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
-            mChildrenLeashes.put(taskInfo.taskId, leash);
-            mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+            final int taskId = taskInfo.taskId;
+            mChildrenLeashes.put(taskId, leash);
+            mChildrenTaskInfo.put(taskId, taskInfo);
             updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
+            mCallbacks.onChildTaskStatusChanged(taskId, true /* present */);
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -120,6 +123,7 @@
             mChildrenTaskInfo.remove(taskId);
             mChildrenLeashes.remove(taskId);
             sendStatusChanged();
+            mCallbacks.onChildTaskStatusChanged(taskId, false /* present */);
         } else {
             throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
                     + "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -134,6 +138,13 @@
         wct.reorder(mRootTaskInfo.token, visible /* onTop */);
     }
 
+    void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
+            @SplitScreen.StageType int stage) {
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+            listener.onTaskStageChanged(mChildrenTaskInfo.keyAt(i), stage);
+        }
+    }
+
     private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl leash, boolean firstAppeared) {
         final Point taskPositionInParent = taskInfo.positionInParent;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 3198725..b5e1896 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -220,9 +220,8 @@
         final InputChannel tmpInputChannel = new InputChannel();
         mainExecutor.execute(() -> {
             try {
-                final int res = session.addToDisplay(window, layoutParams, View.GONE,
-                        displayId, mTmpInsetsState, tmpFrames.frame, tmpInputChannel,
-                        mTmpInsetsState, mTempControls);
+                final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
+                        mTmpInsetsState, tmpInputChannel, mTmpInsetsState, mTempControls);
                 if (res < 0) {
                     Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
                     return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 8271b06..ac93a17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -31,7 +31,9 @@
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.util.ArrayList;
 
@@ -71,14 +73,20 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         IRemoteTransition pendingRemote = mPendingRemotes.remove(transition);
         if (pendingRemote == null) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have "
+                    + "explicit remote, search filters for match for %s", transition, info);
             // If no explicit remote, search filters until one matches
             for (int i = mFilters.size() - 1; i >= 0; --i) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
+                        mFilters.get(i));
                 if (mFilters.get(i).first.matches(info)) {
                     pendingRemote = mFilters.get(i).second;
                     break;
                 }
             }
         }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for %s to %s",
+                transition, pendingRemote);
 
         if (pendingRemote == null) return false;
 
@@ -121,6 +129,8 @@
         IRemoteTransition remote = request.getRemoteTransition();
         if (remote == null) return null;
         mPendingRemotes.put(transition, remote);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested"
+                + " for %s: %s", transition, remote);
         return new WindowContainerTransaction();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 2ab4e0b..d8687bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -232,6 +232,8 @@
         if (!info.getRootLeash().isValid()) {
             // Invalid root-leash implies that the transition is empty/no-op, so just do
             // housekeeping and return.
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
+                    transitionToken, info);
             t.apply();
             onFinish(transitionToken, null /* wct */, null /* wctCB */);
             return;
@@ -241,14 +243,22 @@
 
         final TransitionFinishCallback finishCb = (wct, cb) -> onFinish(transitionToken, wct, cb);
         // If a handler chose to uniquely run this animation, try delegating to it.
-        if (active.mFirstHandler != null && active.mFirstHandler.startAnimation(
-                transitionToken, info, t, finishCb)) {
-            return;
+        if (active.mFirstHandler != null) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
+                    active.mFirstHandler);
+            if (active.mFirstHandler.startAnimation(transitionToken, info, t, finishCb)) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
+                return;
+            }
         }
         // Otherwise give every other handler a chance (in order)
         for (int i = mHandlers.size() - 1; i >= 0; --i) {
             if (mHandlers.get(i) == active.mFirstHandler) continue;
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
+                    mHandlers.get(i));
             if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishCb)) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
+                        mHandlers.get(i));
                 return;
             }
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 4a498d2..a57ac35 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -18,7 +18,7 @@
     name: "WMShellFlickerTests",
     srcs: ["src/**/*.java", "src/**/*.kt"],
     manifest: "AndroidManifest.xml",
-    test_config: "AndroidTestPhysicalDevices.xml",
+    test_config: "AndroidTest.xml",
     platform_apis: true,
     certificate: "platform",
     test_suites: ["device-tests"],
@@ -36,25 +36,3 @@
         "wmshell-flicker-test-components",
     ],
 }
-
-android_test {
-    name: "WMShellFlickerTestsVirtual",
-    srcs: ["src/**/*.java", "src/**/*.kt"],
-    manifest: "AndroidManifest.xml",
-    test_config: "AndroidTestVirtualDevices.xml",
-    platform_apis: true,
-    certificate: "platform",
-    libs: ["android.test.runner"],
-    static_libs: [
-        "androidx.test.ext.junit",
-        "flickerlib",
-        "truth-prebuilt",
-        "app-helpers-core",
-        "launcher-helper-lib",
-        "launcher-aosp-tapl",
-        "wm-flicker-common-assertions",
-        "wm-flicker-common-app-helpers",
-        "platform-test-annotations",
-        "wmshell-flicker-test-components",
-    ],
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/AndroidTestPhysicalDevices.xml
rename to libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
deleted file mode 100644
index 0738608..0000000
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestVirtualDevices.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2020 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Shell Flicker Tests">
-    <option name="test-tag" value="FlickerTests" />
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <!-- keeps the screen on during tests -->
-        <option name="screen-always-on" value="on" />
-        <!-- prevents the phone from restarting -->
-        <option name="force-skip-system-props" value="true" />
-        <!-- set WM tracing verbose level to all -->
-        <option name="run-command" value="cmd window tracing level all" />
-        <!-- inform WM to log all transactions -->
-        <option name="run-command" value="cmd window tracing transaction" />
-        <!-- restart launcher to activate TAPL -->
-        <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner">
-        <!-- reboot the device to teardown any crashed tests -->
-        <option name="cleanup-action" value="REBOOT" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="WMShellFlickerTests.apk"/>
-        <option name="test-file-name" value="WMShellFlickerTestApp.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.wm.shell.flicker"/>
-        <option name="exclude-annotation" value="androidx.test.filters.RequiresDevice" />
-        <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
-        <option name="shell-timeout" value="6600s" />
-        <option name="test-timeout" value="6000s" />
-        <option name="hidden-api-checks" value="false" />
-    </test>
-    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/sdcard/flicker" />
-        <option name="collect-on-run-ended-only" value="true" />
-        <option name="clean-up" value="true" />
-    </metrics_collector>
-</configuration>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index ccfdce6..7ad7553 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -18,33 +18,34 @@
 
 import android.graphics.Region
 import android.view.Surface
-import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
+import com.android.server.wm.flicker.dsl.LayersAssertionBuilderLegacy
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase.Companion.DOCKED_STACK_DIVIDER
 
 @JvmOverloads
-fun LayersAssertionBuilder.appPairsDividerIsVisible(
+fun LayersAssertionBuilderLegacy.appPairsDividerIsVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     end("appPairsDividerIsVisible", bugId, enabled) {
-        this.isVisible(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+        this.isVisible(APP_PAIR_SPLIT_DIVIDER)
     }
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.appPairsDividerIsInvisible(
+fun LayersAssertionBuilderLegacy.appPairsDividerIsInvisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     end("appPairsDividerIsInVisible", bugId, enabled) {
-        this.notExists(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+        this.notExists(APP_PAIR_SPLIT_DIVIDER)
     }
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.appPairsDividerBecomesVisible(
+fun LayersAssertionBuilderLegacy.appPairsDividerBecomesVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -56,7 +57,7 @@
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.dockedStackDividerIsVisible(
+fun LayersAssertionBuilderLegacy.dockedStackDividerIsVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -66,7 +67,7 @@
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.dockedStackDividerBecomesVisible(
+fun LayersAssertionBuilderLegacy.dockedStackDividerBecomesVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -78,7 +79,7 @@
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.dockedStackDividerBecomesInvisible(
+fun LayersAssertionBuilderLegacy.dockedStackDividerBecomesInvisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -90,7 +91,7 @@
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.dockedStackDividerIsInvisible(
+fun LayersAssertionBuilderLegacy.dockedStackDividerIsInvisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -100,33 +101,33 @@
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.appPairsPrimaryBoundsIsVisible(
+fun LayersAssertionBuilderLegacy.appPairsPrimaryBoundsIsVisible(
     rotation: Int,
     primaryLayerName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     end("PrimaryAppBounds", bugId, enabled) {
-        val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+        val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
         this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation))
     }
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.appPairsSecondaryBoundsIsVisible(
+fun LayersAssertionBuilderLegacy.appPairsSecondaryBoundsIsVisible(
     rotation: Int,
     secondaryLayerName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     end("SecondaryAppBounds", bugId, enabled) {
-        val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+        val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
         this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation))
     }
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.dockedStackPrimaryBoundsIsVisible(
+fun LayersAssertionBuilderLegacy.dockedStackPrimaryBoundsIsVisible(
     rotation: Int,
     primaryLayerName: String,
     bugId: Int = 0,
@@ -139,7 +140,7 @@
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.dockedStackSecondaryBoundsIsVisible(
+fun LayersAssertionBuilderLegacy.dockedStackSecondaryBoundsIsVisible(
     rotation: Int,
     secondaryLayerName: String,
     bugId: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 3953c1c..89bbdb0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -135,12 +135,4 @@
             throw RuntimeException(e)
         }
     }
-
-    companion object {
-        const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
-        const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
-        const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
-        const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
-        const val IMAGE_WALLPAPER = "ImageWallpaper"
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
index b5d5d0f..d257749 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
@@ -21,7 +21,6 @@
 import android.os.SystemClock
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
@@ -46,15 +45,13 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class AppPairsTestCannotPairNonResizeableApps(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object : AppPairsTransition(InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
-            val testTag = "testAppPairs_unpairPrimaryAndSecondaryApps"
+            val testTag = "testAppPairs_cannotPairNonResizeableApps"
             val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
                 withTestName {
                     buildTestTag(testTag, configuration)
@@ -71,7 +68,7 @@
                         appPairsDividerIsInvisible()
                     }
                     windowManagerTrace {
-                        end {
+                        end("onlyResizeableAppWindowVisible") {
                             val nonResizeableApp = nonResizeableApp
                             require(nonResizeableApp != null) {
                                 "Non resizeable app not initialized"
@@ -83,9 +80,8 @@
                 }
             }
 
-            return FlickerTestRunnerFactory(instrumentation,
-                repetitions = AppPairsHelper.TEST_REPETITIONS)
-                .buildTest(transition, testSpec)
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                transition, testSpec, repetitions = AppPairsHelper.TEST_REPETITIONS)
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index 54e074c..257350b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -21,13 +21,12 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase
 import com.android.wm.shell.flicker.appPairsDividerIsVisible
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import org.junit.FixMethodOrder
@@ -44,10 +43,8 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class AppPairsTestPairPrimaryAndSecondaryApps(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object : AppPairsTransition(InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
@@ -67,8 +64,7 @@
                     layersTrace {
                         appPairsDividerIsVisible()
                         end("appsEndingBounds", enabled = false) {
-                            val dividerRegion = entry.getVisibleBounds(
-                                    FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+                            val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
                             this.hasVisibleRegion(primaryApp.defaultWindowName,
                                 appPairsHelper.getPrimaryBounds(dividerRegion))
                                 .hasVisibleRegion(secondaryApp.defaultWindowName,
@@ -76,15 +72,15 @@
                         }
                     }
                     windowManagerTrace {
-                        end {
+                        end("bothAppWindowsVisible") {
                             isVisible(primaryApp.defaultWindowName)
                             isVisible(secondaryApp.defaultWindowName)
                         }
                     }
                 }
             }
-            return FlickerTestRunnerFactory(instrumentation,
-                repetitions = AppPairsHelper.TEST_REPETITIONS).buildTest(transition, testSpec)
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, transition,
+                testSpec, repetitions = AppPairsHelper.TEST_REPETITIONS)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
index 854a504..0b001f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
@@ -21,13 +21,12 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.buildTestTag
 import com.android.server.wm.flicker.traces.layers.getVisibleBounds
-import com.android.wm.shell.flicker.FlickerTestBase
 import com.android.wm.shell.flicker.appPairsDividerIsInvisible
 import com.android.wm.shell.flicker.helpers.AppPairsHelper
 import org.junit.FixMethodOrder
@@ -44,10 +43,8 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class AppPairsTestUnpairPrimaryAndSecondaryApps(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object : AppPairsTransition(InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
@@ -72,8 +69,7 @@
                     layersTrace {
                         appPairsDividerIsInvisible()
                         start("appsStartingBounds", enabled = false) {
-                            val dividerRegion = entry.getVisibleBounds(
-                                FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+                            val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
                             this.hasVisibleRegion(primaryApp.defaultWindowName,
                                 appPairsHelper.getPrimaryBounds(dividerRegion))
                                 .hasVisibleRegion(secondaryApp.defaultWindowName,
@@ -85,15 +81,15 @@
                         }
                     }
                     windowManagerTrace {
-                        end {
+                        end("bothAppWindowsInvisible") {
                             isInvisible(primaryApp.defaultWindowName)
                             isInvisible(secondaryApp.defaultWindowName)
                         }
                     }
                 }
             }
-            return FlickerTestRunnerFactory(instrumentation,
-                repetitions = AppPairsHelper.TEST_REPETITIONS).buildTest(transition, testSpec)
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, transition,
+                testSpec, repetitions = AppPairsHelper.TEST_REPETITIONS)
         }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
index c436eb2..aafa9bf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
@@ -22,17 +22,14 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.endRotation
 import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.isRotated
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.startRotation
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
 import com.android.wm.shell.flicker.appPairsDividerIsVisible
@@ -54,19 +51,16 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class RotateTwoLaunchedAppsInAppPairsMode(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object : RotateTwoLaunchedAppsTransition(
         InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
                 withTestName {
-                    buildTestTag("testRotateAndEnterAppPairsMode", configuration)
+                    buildTestTag("testRotateTwoLaunchedAppsInAppPairsMode", configuration)
                 }
                 transitions {
                     executeShellCommand(composePairsCommand(
@@ -77,18 +71,19 @@
                 assertions {
                     layersTrace {
                         navBarLayerRotatesAndScales(Surface.ROTATION_0, configuration.endRotation,
-                            enabled = !configuration.startRotation.isRotated())
-                        statusBarLayerRotatesScales(Surface.ROTATION_0, configuration.endRotation)
-                        appPairsDividerIsVisible()
+                            enabled = false)
+                        statusBarLayerRotatesScales(Surface.ROTATION_0, configuration.endRotation,
+                            enabled = false)
+                        appPairsDividerIsVisible(enabled = false)
                         appPairsPrimaryBoundsIsVisible(configuration.endRotation,
-                            primaryApp.defaultWindowName, 172776659)
+                            primaryApp.defaultWindowName, bugId = 172776659)
                         appPairsSecondaryBoundsIsVisible(configuration.endRotation,
-                            secondaryApp.defaultWindowName, 172776659)
+                            secondaryApp.defaultWindowName, bugId = 172776659)
                     }
                     windowManagerTrace {
                         navBarWindowIsAlwaysVisible()
                         statusBarWindowIsAlwaysVisible()
-                        end {
+                        end("bothAppWindowsVisible") {
                             isVisible(primaryApp.defaultWindowName)
                                 .isVisible(secondaryApp.defaultWindowName)
                         }
@@ -96,10 +91,10 @@
                 }
             }
 
-            return FlickerTestRunnerFactory(instrumentation,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                transition, testSpec,
                 repetitions = SplitScreenHelper.TEST_REPETITIONS,
-                supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270)
-            ).buildTest(transition, testSpec)
+                supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270))
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
index cd4f4f6..19ca31f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
@@ -22,7 +22,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
@@ -54,16 +53,13 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object : RotateTwoLaunchedAppsTransition(
         InstrumentationRegistry.getInstrumentation()) {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
                 withTestName {
                     buildTestTag("testRotateAndEnterAppPairsMode", configuration)
@@ -88,7 +84,7 @@
                     windowManagerTrace {
                         navBarWindowIsAlwaysVisible()
                         statusBarWindowIsAlwaysVisible()
-                        end {
+                        end("bothAppWindowsVisible") {
                             isVisible(primaryApp.defaultWindowName)
                             isVisible(secondaryApp.defaultWindowName)
                         }
@@ -96,10 +92,11 @@
                 }
             }
 
-            return FlickerTestRunnerFactory(instrumentation,
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                transition, testSpec,
                 repetitions = SplitScreenHelper.TEST_REPETITIONS,
                 supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270)
-            ).buildTest(transition, testSpec)
+            )
         }
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
deleted file mode 100644
index af99543..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.canSplitScreen
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickstep
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
-import org.junit.Assert
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test SplitScreen launch.
- * To run this test: `atest WMShellFlickerTests:EnterLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    private val splitScreenSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            val testLaunchActivity = "launch_splitScreen_test_activity"
-            withTestName {
-                testLaunchActivity
-            }
-            setup {
-                eachRun {
-                    uiDevice.wakeUpAndGoToHomeScreen()
-                    uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                }
-            }
-            teardown {
-                eachRun {
-                    if (uiDevice.isInSplitScreen()) {
-                        uiDevice.exitSplitScreen()
-                    }
-                    splitScreenApp.exit()
-                    secondaryApp.exit()
-                    nonResizeableApp.exit()
-                }
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    statusBarLayerIsAlwaysVisible()
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                }
-            }
-        }
-
-    @Test
-    fun testEnterSplitScreen_dockActivity() {
-        val testTag = "testEnterSplitScreen_dockActivity"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                    dockedStackDividerBecomesVisible()
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    LIVE_WALLPAPER_PACKAGE_NAME)
-                    )
-                }
-                windowManagerTrace {
-                    end {
-                        isVisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testEnterSplitScreen_launchToSide() {
-        val testTag = "testEnterSplitScreen_launchToSide"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                secondaryApp.launchViaIntent()
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-                splitScreenApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackPrimaryBoundsIsVisible(
-                        rotation, splitScreenApp.defaultWindowName, 169271943)
-                    dockedStackSecondaryBoundsIsVisible(
-                        rotation, secondaryApp.defaultWindowName, 169271943)
-                    dockedStackDividerBecomesVisible()
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                        listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                            secondaryApp.defaultWindowName)
-                    )
-                }
-                windowManagerTrace {
-                    end {
-                        isVisible(splitScreenApp.defaultWindowName)
-                            .isVisible(secondaryApp.defaultWindowName)
-                    }
-                    visibleWindowsShownMoreThanOneConsecutiveEntry(
-                        listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                            secondaryApp.defaultWindowName))
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testNonResizeableNotDocked() {
-        val testTag = "testNonResizeableNotDocked"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                nonResizeableApp.launchViaIntent()
-                uiDevice.openQuickstep()
-                if (uiDevice.canSplitScreen()) {
-                    Assert.fail("Non-resizeable app should not enter split screen")
-                }
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                        listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName)
-                    )
-                }
-                windowManagerTrace {
-                    end {
-                        isInvisible(nonResizeableApp.defaultWindowName)
-                    }
-                    visibleWindowsShownMoreThanOneConsecutiveEntry(
-                        listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName))
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
new file mode 100644
index 0000000..2c29220
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.WALLPAPER_TITLE
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open activity and dock to primary split screen
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenDockActivity`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+// @FlakyTest(bugId = 179116910)
+class EnterSplitScreenDockActivity(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testLegacySplitScreenDockActivity", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 169271943)
+                        dockedStackDividerBecomesVisible()
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME,
+                                WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+                                splitScreenApp.defaultWindowName),
+                            bugId = 178531736
+                        )
+                    }
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME,
+                                WALLPAPER_TITLE, LIVE_WALLPAPER_PACKAGE_NAME,
+                                splitScreenApp.defaultWindowName),
+                            bugId = 178531736
+                        )
+                        end("appWindowIsVisible") {
+                            isVisible(splitScreenApp.defaultWindowName)
+                        }
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
new file mode 100644
index 0000000..903971e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open activity to primary split screen and dock secondary activity to side
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenLaunchToSide`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenLaunchToSide(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testLegacySplitScreenLaunchToSide", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    secondaryApp.reopenAppFromOverview()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 169271943)
+                        dockedStackSecondaryBoundsIsVisible(
+                            configuration.startRotation,
+                            secondaryApp.defaultWindowName, bugId = 169271943)
+                        dockedStackDividerBecomesVisible()
+                        // TODO(b/178447631) Remove Splash Screen from white list when flicker lib
+                        //                   add a wait for splash screen be gone
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
+                                splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(secondaryApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, SPLASH_SCREEN_NAME,
+                                splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName)
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0) // bugId = 175687842
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
new file mode 100644
index 0000000..e361923
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.WALLPAPER_TITLE
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.canSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickstep
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.Assert
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open non-resizable activity will auto exit split screen mode
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNonResizableNotDock`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 173875043)
+class EnterSplitScreenNonResizableNotDock(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testLegacySplitScreenNonResizeableActivityNotDock", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    nonResizeableApp.launchViaIntent(wmHelper)
+                    device.openQuickstep()
+                    if (device.canSplitScreen()) {
+                        Assert.fail("Non-resizeable app should not enter split screen")
+                    }
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsInvisible()
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME,
+                                SPLASH_SCREEN_NAME,
+                                nonResizeableApp.defaultWindowName,
+                                splitScreenApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(WALLPAPER_TITLE,
+                                LAUNCHER_PACKAGE_NAME,
+                                SPLASH_SCREEN_NAME,
+                                nonResizeableApp.defaultWindowName,
+                                splitScreenApp.defaultWindowName)
+                        )
+                        end("appWindowIsVisible") {
+                            isInvisible(nonResizeableApp.defaultWindowName)
+                        }
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
new file mode 100644
index 0000000..4933665
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open resizeable activity split in primary, and drag divider to bottom exit split screen
+ * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottom`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitLegacySplitScreenFromBottom(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testExitLegacySplitScreenFromBottom", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    device.exitSplitScreenFromBottom()
+                }
+                assertions {
+                    layersTrace {
+                        layerBecomesInvisible(DOCKED_STACK_DIVIDER)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesInVisible(secondaryApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0) // bugId = 175687842
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
deleted file mode 100644
index 4ddfb3e..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
-import com.android.server.wm.flicker.FlickerTestRunner
-import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.repetitions
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottomTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitLegacySplitScreenFromBottomTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
-            // TODO(b/162923992) Use of multiple segments of flicker spec for testing
-            return FlickerTestRunnerFactory(instrumentation,
-                    listOf(Surface.ROTATION_0, Surface.ROTATION_90))
-                    .buildTest { configuration ->
-                        withTestName {
-                            buildTestTag("exitSplitScreenFromBottom", configuration)
-                        }
-                        repeat { configuration.repetitions }
-                        setup {
-                            eachRun {
-                                device.wakeUpAndGoToHomeScreen()
-                                device.openQuickStepAndClearRecentAppsFromOverview()
-                                splitScreenApp.launchViaIntent(wmHelper)
-                                device.launchSplitScreen()
-                                device.waitForIdle()
-                                this.setRotation(configuration.endRotation)
-                            }
-                        }
-                        teardown {
-                            eachRun {
-                                if (device.isInSplitScreen()) {
-                                    device.exitSplitScreen()
-                                }
-                                splitScreenApp.exit()
-                            }
-                        }
-                        transitions {
-                            device.exitSplitScreenFromBottom()
-                        }
-                    }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
deleted file mode 100644
index cd9a3c9..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.util.Rational
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.resizeSplitScreen
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper.Companion.TEST_REPETITIONS
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test exit SplitScreen mode.
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    private val splitScreenSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            val testLaunchActivity = "launch_splitScreen_test_activity"
-            withTestName {
-                testLaunchActivity
-            }
-            setup {
-                eachRun {
-                    uiDevice.wakeUpAndGoToHomeScreen()
-                    uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                    secondaryApp.launchViaIntent()
-                    splitScreenApp.launchViaIntent()
-                    uiDevice.launchSplitScreen()
-                }
-            }
-            teardown {
-                eachRun {
-                    splitScreenApp.exit()
-                    secondaryApp.exit()
-                }
-            }
-            assertions {
-                windowManagerTrace {
-                    visibleWindowsShownMoreThanOneConsecutiveEntry()
-                }
-                layersTrace {
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME))
-                }
-            }
-        }
-
-    @Test
-    fun testEnterSplitScreen_exitPrimarySplitScreenMode() {
-        val testTag = "testEnterSplitScreen_exitPrimarySplitScreenMode"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                uiDevice.exitSplitScreen()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    layerBecomesInvisible(splitScreenApp.defaultWindowName)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end {
-                        isInvisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    @FlakyTest(bugId = 172811376)
-    fun testEnterSplitScreen_exitPrimary_showSecondaryAppFullScreen() {
-        val testTag = "testEnterSplitScreen_exitPrimary_showSecondaryAppFullScreen"
-        runWithFlicker(splitScreenSetup) {
-            withTestName { testTag }
-            repeat {
-                TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.reopenAppFromOverview()
-                uiDevice.resizeSplitScreen(startRatio)
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end {
-                        isVisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        private val startRatio = Rational(1, 3)
-
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
new file mode 100644
index 0000000..ff3a979
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dock activity to primary split screen, and open secondary to side, exit primary split
+ * and test secondary activity become full screen.
+ * To run this test: `atest WMShellFlickerTests:ExitPrimarySplitScreenShowSecondaryFullscreen`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ExitPrimarySplitScreenShowSecondaryFullscreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testExitPrimarySplitScreenShowSecondaryFullscreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    secondaryApp.reopenAppFromOverview()
+                    // TODO(b/175687842) Can not find Split screen divider, use exit() instead
+                    splitScreenApp.exit()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsInvisible(bugId = 175687842)
+                        layerBecomesInvisible(splitScreenApp.defaultWindowName)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+                                secondaryApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
new file mode 100644
index 0000000..03b6edf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.focusDoesNotChange
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
+import com.android.wm.shell.flicker.helpers.SimpleAppHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncher`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class LegacySplitScreenToLauncher(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
+                .launcherStrategy.supportedLauncherPackage
+            val testApp = SimpleAppHelper(instrumentation)
+
+            // b/161435597 causes the test not to work on 90 degrees
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
+                withTestName {
+                    buildTestTag("splitScreenToLauncher", configuration)
+                }
+                repeat { configuration.repetitions }
+                setup {
+                    test {
+                        device.wakeUpAndGoToHomeScreen()
+                        device.openQuickStepAndClearRecentAppsFromOverview()
+                    }
+                    eachRun {
+                        testApp.launchViaIntent(wmHelper)
+                        this.setRotation(configuration.endRotation)
+                        device.launchSplitScreen()
+                        device.waitForIdle()
+                    }
+                }
+                teardown {
+                    eachRun {
+                        testApp.exit()
+                    }
+                    test {
+                        if (device.isInSplitScreen()) {
+                            device.exitSplitScreen()
+                        }
+                    }
+                }
+                transitions {
+                    device.exitSplitScreen()
+                }
+                assertions {
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        visibleWindowsShownMoreThanOneConsecutiveEntry()
+                    }
+
+                    layersTrace {
+                        navBarLayerIsAlwaysVisible()
+                        statusBarLayerIsAlwaysVisible()
+                        noUncoveredRegions(configuration.endRotation)
+                        navBarLayerRotatesAndScales(configuration.endRotation)
+                        statusBarLayerRotatesScales(configuration.endRotation)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(launcherPackageName))
+
+                        // b/161435597 causes the test not to work on 90 degrees
+                        dockedStackDividerBecomesInvisible()
+
+                        layerBecomesInvisible(testApp.getPackage())
+                    }
+
+                    eventLog {
+                        focusDoesNotChange(bugId = 151179149)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt
deleted file mode 100644
index 43a7565..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncherTest.kt
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
-import com.android.server.wm.flicker.FlickerTestRunner
-import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.focusDoesNotChange
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.layerBecomesInvisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
-import com.android.wm.shell.flicker.helpers.SimpleAppHelper
-import org.junit.FixMethodOrder
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncherTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LegacySplitScreenToLauncherTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val instrumentation = InstrumentationRegistry.getInstrumentation()
-            val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
-                    .launcherStrategy.supportedLauncherPackage
-            val testApp = SimpleAppHelper(instrumentation)
-
-            // b/161435597 causes the test not to work on 90 degrees
-            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
-                .buildTest { configuration ->
-                    withTestName {
-                        buildTestTag("splitScreenToLauncher", configuration)
-                    }
-                    repeat { configuration.repetitions }
-                    setup {
-                        test {
-                            device.wakeUpAndGoToHomeScreen()
-                            device.openQuickStepAndClearRecentAppsFromOverview()
-                        }
-                        eachRun {
-                            testApp.launchViaIntent(wmHelper)
-                            this.setRotation(configuration.endRotation)
-                            device.launchSplitScreen()
-                            device.waitForIdle()
-                        }
-                    }
-                    teardown {
-                        eachRun {
-                            testApp.exit()
-                        }
-                        test {
-                            if (device.isInSplitScreen()) {
-                                device.exitSplitScreen()
-                            }
-                        }
-                    }
-                    transitions {
-                        device.exitSplitScreen()
-                    }
-                    assertions {
-                        windowManagerTrace {
-                            navBarWindowIsAlwaysVisible()
-                            statusBarWindowIsAlwaysVisible()
-                            visibleWindowsShownMoreThanOneConsecutiveEntry()
-                        }
-
-                        layersTrace {
-                            navBarLayerIsAlwaysVisible()
-                            statusBarLayerIsAlwaysVisible()
-                            noUncoveredRegions(configuration.endRotation)
-                            navBarLayerRotatesAndScales(configuration.endRotation)
-                            statusBarLayerRotatesScales(configuration.endRotation)
-                            visibleLayersShownMoreThanOneConsecutiveEntry(
-                                    listOf(launcherPackageName))
-
-                            // b/161435597 causes the test not to work on 90 degrees
-                            dockedStackDividerBecomesInvisible()
-
-                            layerBecomesInvisible(testApp.getPackage())
-                        }
-
-                        eventLog {
-                            focusDoesNotChange(bugId = 151179149)
-                        }
-                    }
-                }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
new file mode 100644
index 0000000..328ff88
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2020 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/LICENSE2.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.wm.shell.flicker.legacysplitscreen
+
+import android.app.Instrumentation
+import android.os.Bundle
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.view.Surface
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.startRotation
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+
+abstract class LegacySplitScreenTransition(
+    protected val instrumentation: Instrumentation
+) {
+    internal val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
+    internal val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+    internal val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
+    internal val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
+        .launcherStrategy.supportedLauncherPackage
+    internal val LIVE_WALLPAPER_PACKAGE_NAME =
+        "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
+    internal val LETTERBOX_NAME = "Letterbox"
+    internal val TOAST_NAME = "Toast"
+    internal val SPLASH_SCREEN_NAME = "Splash Screen"
+
+    internal open val defaultTransitionSetup: FlickerBuilder.(Bundle) -> Unit
+        get() = { configuration ->
+            setup {
+                eachRun {
+                    device.wakeUpAndGoToHomeScreen()
+                    device.openQuickStepAndClearRecentAppsFromOverview()
+                    secondaryApp.launchViaIntent(wmHelper)
+                    splitScreenApp.launchViaIntent(wmHelper)
+                    this.setRotation(configuration.startRotation)
+                }
+            }
+            teardown {
+                eachRun {
+                    splitScreenApp.exit()
+                    secondaryApp.exit()
+                    this.setRotation(Surface.ROTATION_0)
+                }
+            }
+        }
+
+    internal open val cleanSetup: FlickerBuilder.(Bundle) -> Unit
+        get() = { configuration ->
+            setup {
+                eachRun {
+                    device.wakeUpAndGoToHomeScreen()
+                    device.openQuickStepAndClearRecentAppsFromOverview()
+                    this.setRotation(configuration.startRotation)
+                }
+            }
+            teardown {
+                eachRun {
+                    nonResizeableApp.exit()
+                    this.setRotation(Surface.ROTATION_0)
+                }
+            }
+        }
+
+    internal open val customRotateSetup: FlickerBuilder.(Bundle) -> Unit
+        get() = { configuration ->
+            setup {
+                eachRun {
+                    device.wakeUpAndGoToHomeScreen()
+                    device.openQuickStepAndClearRecentAppsFromOverview()
+                    secondaryApp.launchViaIntent(wmHelper)
+                    splitScreenApp.launchViaIntent(wmHelper)
+                }
+            }
+            teardown {
+                eachRun {
+                    splitScreenApp.exit()
+                    secondaryApp.exit()
+                    this.setRotation(Surface.ROTATION_0)
+                }
+            }
+        }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
new file mode 100644
index 0000000..f2a7cda
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non resizable activity in split screen mode will trigger exit split screen mode
+ * (Non resizable activity launch via recent overview)
+ * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableDismissInLegacySplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testNonResizableDismissInLegacySplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    nonResizeableApp.launchViaIntent(wmHelper)
+                    splitScreenApp.launchViaIntent(wmHelper)
+                    device.launchSplitScreen()
+                    nonResizeableApp.reopenAppFromOverview()
+                    wmHelper.waitForAppTransitionIdle()
+                }
+                assertions {
+                    layersTrace {
+                        layerBecomesVisible(nonResizeableApp.defaultWindowName)
+                        layerBecomesInvisible(splitScreenApp.defaultWindowName)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
+                                LETTERBOX_NAME, TOAST_NAME,
+                                splitScreenApp.defaultWindowName,
+                                nonResizeableApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+                        appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(DOCKED_STACK_DIVIDER, LAUNCHER_PACKAGE_NAME,
+                                LETTERBOX_NAME, TOAST_NAME,
+                                splitScreenApp.defaultWindowName,
+                                nonResizeableApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, cleanSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
deleted file mode 100644
index ed305a2..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableDismissInLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-
-    @Test
-    fun testNonResizableDismissInLegacySplitScreenTest() {
-        val testTag = "testNonResizableDismissInLegacySplitScreenTest"
-
-        runWithFlicker(transitionSetup) {
-            withTestName { testTag }
-            repeat { SplitScreenHelper.TEST_REPETITIONS }
-            transitions {
-                nonResizeableApp.launchViaIntent(wmHelper)
-                splitScreenApp.launchViaIntent(wmHelper)
-                device.launchSplitScreen()
-                nonResizeableApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    end("appsEndingBounds", enabled = false) {
-                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-                        this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
-                    }
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    nonResizeableApp.defaultWindowName, LETTER_BOX_NAME,
-                                    TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME)
-                    )
-                }
-                windowManagerTrace {
-                    end {
-                        isVisible(nonResizeableApp.defaultWindowName)
-                            .isInvisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
new file mode 100644
index 0000000..421ecff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesInVisible
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesInvisible
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test launch non resizable activity in split screen mode will trigger exit split screen mode
+ * (Non resizable activity launch via intent)
+ * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableLaunchInLegacySplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testNonResizableLaunchInLegacySplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    splitScreenApp.launchViaIntent(wmHelper)
+                    device.launchSplitScreen()
+                    nonResizeableApp.launchViaIntent(wmHelper)
+                    wmHelper.waitForAppTransitionIdle()
+                }
+                assertions {
+                    layersTrace {
+                        layerBecomesVisible(nonResizeableApp.defaultWindowName)
+                        layerBecomesInvisible(splitScreenApp.defaultWindowName)
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                                listOf(DOCKED_STACK_DIVIDER,
+                                        LAUNCHER_PACKAGE_NAME,
+                                        LETTERBOX_NAME,
+                                        nonResizeableApp.defaultWindowName,
+                                        splitScreenApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(nonResizeableApp.defaultWindowName)
+                        appWindowBecomesInVisible(splitScreenApp.defaultWindowName)
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                                listOf(DOCKED_STACK_DIVIDER,
+                                        LAUNCHER_PACKAGE_NAME,
+                                        LETTERBOX_NAME,
+                                        nonResizeableApp.defaultWindowName,
+                                        splitScreenApp.defaultWindowName),
+                            bugId = 178447631
+                        )
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, cleanSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
deleted file mode 100644
index 88dab51..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class NonResizableLaunchInLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-
-    @Test
-    fun testNonResizableLaunchInLegacySplitScreenTest() {
-        val testTag = "testNonResizableLaunchInLegacySplitScreenTest"
-
-        runWithFlicker(transitionSetup) {
-            withTestName { testTag }
-            repeat { SplitScreenHelper.TEST_REPETITIONS }
-            transitions {
-                nonResizeableApp.launchViaIntent(wmHelper)
-                splitScreenApp.launchViaIntent(wmHelper)
-                device.launchSplitScreen()
-                nonResizeableApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    dockedStackDividerIsInvisible()
-                    end("appsEndingBounds", enabled = false) {
-                        val displayBounds = WindowUtils.getDisplayBounds(rotation)
-                        this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
-                    }
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    nonResizeableApp.defaultWindowName, LETTER_BOX_NAME,
-                                    TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME),
-                        enabled = false
-                    )
-                }
-                windowManagerTrace {
-                    end {
-                        isVisible(nonResizeableApp.defaultWindowName)
-                            .isInvisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
new file mode 100644
index 0000000..7edef93
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.layerBecomesVisible
+import com.android.server.wm.flicker.noUncoveredRegions
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppToLegacySplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val wmHelper = WindowManagerStateHelper()
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testOpenAppToLegacySplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    wmHelper.waitForAppTransitionIdle()
+                }
+                assertions {
+                    windowManagerTrace {
+                        visibleWindowsShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName),
+                            bugId = 178447631)
+                        appWindowBecomesVisible(splitScreenApp.getPackage())
+                    }
+
+                    layersTrace {
+                        noUncoveredRegions(configuration.startRotation, enabled = false)
+                        statusBarLayerIsAlwaysVisible()
+                        appPairsDividerBecomesVisible()
+                        layerBecomesVisible(splitScreenApp.getPackage())
+                        visibleLayersShownMoreThanOneConsecutiveEntry(
+                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName),
+                            bugId = 178447631)
+                    }
+
+                    eventLog {
+                        focusChanges(splitScreenApp.`package`,
+                            "recents_animation_input_consumer", "NexusLauncherActivity",
+                            bugId = 151179149)
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, defaultTransitionSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
+            )
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
deleted file mode 100644
index 6200a69..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreenTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppToLegacySplitScreenTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    @Test
-    fun OpenAppToLegacySplitScreenTest() {
-        val testTag = "OpenAppToLegacySplitScreenTest"
-        val helper = WindowManagerStateHelper()
-        runWithFlicker(transitionSetup) {
-            withTestName { testTag }
-            repeat { SplitScreenHelper.TEST_REPETITIONS }
-            setup {
-                eachRun {
-                    splitScreenApp.launchViaIntent(wmHelper)
-                    device.pressHome()
-                    this.setRotation(rotation)
-                }
-            }
-            transitions {
-                device.launchSplitScreen()
-                helper.waitForAppTransitionIdle()
-            }
-            assertions {
-                windowManagerTrace {
-                    visibleWindowsShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    LETTER_BOX_NAME)
-                    )
-                    appWindowBecomesVisible(splitScreenApp.getPackage())
-                }
-
-                layersTrace {
-                    navBarLayerIsAlwaysVisible()
-                    noUncoveredRegions(rotation, enabled = false)
-                    statusBarLayerIsAlwaysVisible()
-                    visibleLayersShownMoreThanOneConsecutiveEntry(
-                            listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
-                                    LETTER_BOX_NAME))
-                    appPairsDividerBecomesVisible()
-                    layerBecomesVisible(splitScreenApp.getPackage())
-                }
-
-                eventLog {
-                    focusChanges(splitScreenApp.`package`,
-                            "recents_animation_input_consumer", "NexusLauncherActivity",
-                            bugId = 151179149)
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            // TODO(b/161435597) causes the test not to work on 90 degrees
-            val supportedRotations = intArrayOf(Surface.ROTATION_0)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
index f6b0772..54a37d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
@@ -25,7 +25,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.By
 import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.endRotation
@@ -59,7 +58,7 @@
 
 /**
  * Test split screen resizing window transitions.
- * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreenTest`
+ * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreen`
  *
  * Currently it runs only in 0 degrees because of b/156100803
  */
@@ -68,11 +67,9 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 159096424)
-class ResizeLegacySplitScreenTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+class ResizeLegacySplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         private const val sSimpleActivity = "SimpleActivity"
         private const val sImeActivity = "ImeActivity"
@@ -86,9 +83,8 @@
             val testAppTop = SimpleAppHelper(instrumentation)
             val testAppBottom = ImeAppHelper(instrumentation)
 
-            return FlickerTestRunnerFactory(instrumentation,
-                supportedRotations = listOf(Surface.ROTATION_0))
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
                     withTestName {
                         val description = (startRatio.toString().replace("/", "-") + "_to_" +
                             stopRatio.toString().replace("/", "-"))
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
new file mode 100644
index 0000000..214269e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test dock activity to primary split screen and rotate
+ * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppAndEnterSplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateOneLaunchedAppAndEnterSplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testRotateOneLaunchedAppAndEnterSplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    device.launchSplitScreen()
+                    this.setRotation(configuration.startRotation)
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsVisible(bugId = 175687842)
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 175687842)
+                        navBarLayerRotatesAndScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                        statusBarLayerRotatesScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                    }
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, customRotateSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
new file mode 100644
index 0000000..4290c92
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Rotate
+ * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppInSplitScreenMode`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateOneLaunchedAppInSplitScreenMode(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testRotateOneLaunchedAppInSplitScreenMode", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    this.setRotation(configuration.startRotation)
+                    device.launchSplitScreen()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsVisible(bugId = 175687842)
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 175687842)
+                        navBarLayerRotatesAndScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                        statusBarLayerRotatesScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                    }
+                    windowManagerTrace {
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                        appWindowBecomesVisible(splitScreenApp.defaultWindowName)
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, customRotateSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt
deleted file mode 100644
index fdf88df..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppTest.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RotateOneLaunchedAppTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    private val splitScreenRotationSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            val testSetupRotation = "testSetupRotation"
-            withTestName {
-                testSetupRotation
-            }
-            setup {
-                test {
-                    uiDevice.wakeUpAndGoToHomeScreen()
-                    uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                }
-            }
-            teardown {
-                eachRun {
-                    if (uiDevice.isInSplitScreen()) {
-                        uiDevice.exitSplitScreen()
-                    }
-                    setRotation(Surface.ROTATION_0)
-                    splitScreenApp.exit()
-                    secondaryApp.exit()
-                }
-            }
-        }
-
-    @Test
-    fun testRotateInSplitScreenMode() {
-        val testTag = "testEnterSplitScreen_launchToSide"
-        runWithFlicker(splitScreenRotationSetup) {
-            withTestName { testTag }
-            repeat {
-                SplitScreenHelper.TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-                setRotation(rotation)
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
-                    dockedStackDividerIsVisible()
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end {
-                        isVisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testRotateAndEnterSplitScreenMode() {
-        val testTag = "testRotateAndEnterSplitScreenMode"
-        runWithFlicker(splitScreenRotationSetup) {
-            withTestName { testTag }
-            repeat {
-                SplitScreenHelper.TEST_REPETITIONS
-            }
-            transitions {
-                splitScreenApp.launchViaIntent()
-                setRotation(rotation)
-                uiDevice.launchSplitScreen()
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
-                    dockedStackDividerIsVisible()
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end {
-                        isVisible(splitScreenApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_90, Surface.ROTATION_270)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
new file mode 100644
index 0000000..4095b9a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppAndEnterSplitScreen`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateTwoLaunchedAppAndEnterSplitScreen(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testRotateTwoLaunchedAppAndEnterSplitScreen", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                transitions {
+                    this.setRotation(configuration.startRotation)
+                    device.launchSplitScreen()
+                    secondaryApp.reopenAppFromOverview()
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsVisible(bugId = 175687842)
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, 175687842)
+                        dockedStackSecondaryBoundsIsVisible(
+                            configuration.startRotation,
+                            secondaryApp.defaultWindowName, bugId = 175687842)
+                        navBarLayerRotatesAndScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                        statusBarLayerRotatesScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(secondaryApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, customRotateSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
new file mode 100644
index 0000000..aebf606
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
+
+import android.os.Bundle
+import android.platform.test.annotations.Presubmit
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.appWindowBecomesVisible
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.endRotation
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.startRotation
+import com.android.server.wm.flicker.statusBarLayerRotatesScales
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.dockedStackDividerIsVisible
+import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
+import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppInSplitScreenMode`
+ */
+@Presubmit
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class RotateTwoLaunchedAppInSplitScreenMode(
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
+    companion object : LegacySplitScreenTransition(InstrumentationRegistry.getInstrumentation()) {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
+                withTestName {
+                    buildTestTag("testRotateTwoLaunchedAppInSplitScreenMode", configuration)
+                }
+                repeat { SplitScreenHelper.TEST_REPETITIONS }
+                setup {
+                    eachRun {
+                        device.launchSplitScreen()
+                        splitScreenApp.reopenAppFromOverview()
+                        this.setRotation(configuration.startRotation)
+                    }
+                }
+                transitions {
+                    this.setRotation(configuration.startRotation)
+                }
+                assertions {
+                    layersTrace {
+                        dockedStackDividerIsVisible(bugId = 175687842)
+                        dockedStackPrimaryBoundsIsVisible(
+                            configuration.startRotation,
+                            splitScreenApp.defaultWindowName, bugId = 175687842)
+                        dockedStackSecondaryBoundsIsVisible(
+                            configuration.startRotation,
+                            secondaryApp.defaultWindowName, bugId = 175687842)
+                        navBarLayerRotatesAndScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                        statusBarLayerRotatesScales(
+                            configuration.startRotation,
+                            configuration.endRotation, bugId = 169271943)
+                    }
+                    windowManagerTrace {
+                        appWindowBecomesVisible(secondaryApp.defaultWindowName)
+                        navBarWindowIsAlwaysVisible()
+                        statusBarWindowIsAlwaysVisible()
+                    }
+                }
+            }
+            return FlickerTestRunnerFactory.getInstance().buildTest(
+                instrumentation, customRotateSetup, testSpec,
+                repetitions = SplitScreenHelper.TEST_REPETITIONS,
+                supportedRotations = listOf(Surface.ROTATION_0 /* bugId = 178685668 */))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt
deleted file mode 100644
index 785ccf0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppTest.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppTest`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class RotateTwoLaunchedAppTest(
-    rotationName: String,
-    rotation: Int
-) : SplitScreenTestBase(rotationName, rotation) {
-    private val splitScreenRotationSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-            val testSetupRotation = "testSetupRotation"
-            withTestName {
-                testSetupRotation
-            }
-            setup {
-                test {
-                    uiDevice.wakeUpAndGoToHomeScreen()
-                    uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                }
-            }
-            teardown {
-                eachRun {
-                    if (uiDevice.isInSplitScreen()) {
-                        uiDevice.exitSplitScreen()
-                    }
-                    setRotation(Surface.ROTATION_0)
-                    splitScreenApp.exit()
-                    secondaryApp.exit()
-                }
-            }
-        }
-
-    @Test
-    fun testRotateInSplitScreenMode() {
-        val testTag = "testRotateInSplitScreenMode"
-        runWithFlicker(splitScreenRotationSetup) {
-            withTestName { testTag }
-            repeat {
-                SplitScreenHelper.TEST_REPETITIONS
-            }
-            transitions {
-                secondaryApp.launchViaIntent()
-                splitScreenApp.launchViaIntent()
-                uiDevice.launchSplitScreen()
-                splitScreenApp.reopenAppFromOverview()
-                setRotation(rotation)
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
-                    dockedStackDividerIsVisible()
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                    dockedStackSecondaryBoundsIsVisible(
-                            rotation, secondaryApp.defaultWindowName, 169271943)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end {
-                        isVisible(splitScreenApp.defaultWindowName)
-                            .isVisible(secondaryApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    fun testRotateAndEnterSplitScreenMode() {
-        val testTag = "testRotateAndEnterSplitScreenMode"
-        runWithFlicker(splitScreenRotationSetup) {
-            withTestName { testTag }
-            repeat {
-                SplitScreenHelper.TEST_REPETITIONS
-            }
-            transitions {
-                secondaryApp.launchViaIntent()
-                splitScreenApp.launchViaIntent()
-                setRotation(rotation)
-                uiDevice.launchSplitScreen()
-                splitScreenApp.reopenAppFromOverview()
-            }
-            assertions {
-                layersTrace {
-                    navBarLayerRotatesAndScales(Surface.ROTATION_0, rotation, 169271943)
-                    statusBarLayerRotatesScales(Surface.ROTATION_0, rotation, 169271943)
-                    dockedStackDividerIsVisible()
-                    dockedStackPrimaryBoundsIsVisible(
-                            rotation, splitScreenApp.defaultWindowName, 169271943)
-                    dockedStackSecondaryBoundsIsVisible(
-                            rotation, secondaryApp.defaultWindowName, 169271943)
-                }
-                windowManagerTrace {
-                    navBarWindowIsAlwaysVisible()
-                    statusBarWindowIsAlwaysVisible()
-                    end {
-                        isVisible(splitScreenApp.defaultWindowName)
-                                .isVisible(secondaryApp.defaultWindowName)
-                    }
-                }
-            }
-        }
-    }
-
-    companion object {
-        @Parameterized.Parameters(name = "{0}")
-        @JvmStatic
-        fun getParams(): Collection<Array<Any>> {
-            val supportedRotations = intArrayOf(Surface.ROTATION_90, Surface.ROTATION_270)
-            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
-        }
-    }
-}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
deleted file mode 100644
index 01db4ed6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.legacysplitscreen
-
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.NonRotationTestBase
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-
-abstract class SplitScreenTestBase(
-    rotationName: String,
-    rotation: Int
-) : NonRotationTestBase(rotationName, rotation) {
-    protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
-    protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
-    protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
-    protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
-            .launcherStrategy.supportedLauncherPackage
-    protected val LIVE_WALLPAPER_PACKAGE_NAME =
-            "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
-    protected val LETTER_BOX_NAME = "Letterbox"
-    protected val TOAST_NAME = "Toast"
-
-    protected val transitionSetup: FlickerBuilder
-        get() = FlickerBuilder(instrumentation).apply {
-                setup {
-                    eachRun {
-                        uiDevice.wakeUpAndGoToHomeScreen()
-                        uiDevice.openQuickStepAndClearRecentAppsFromOverview()
-                    }
-                }
-                teardown {
-                    eachRun {
-                        if (uiDevice.isInSplitScreen()) {
-                            uiDevice.exitSplitScreen()
-                        }
-                        splitScreenApp.exit()
-                        nonResizeableApp.exit()
-                    }
-                }
-                assertions {
-                    layersTrace {
-                        navBarLayerIsAlwaysVisible()
-                        statusBarLayerIsAlwaysVisible()
-                    }
-                    windowManagerTrace {
-                        navBarWindowIsAlwaysVisible()
-                        statusBarWindowIsAlwaysVisible()
-                    }
-                }
-            }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 2ffc338..af62eb9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.buildTestTag
@@ -53,19 +52,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 152738416)
 class EnterPipTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = PipAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation,
-                supportedRotations = listOf(Surface.ROTATION_0))
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
                     withTestName { buildTestTag("enterPip", testApp, configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 812353f..c21b594 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -131,7 +131,7 @@
             }
             assertions {
                 windowManagerTrace {
-                    end {
+                    end("imeWindowAboveApp") {
                         isAboveWindow(IME_WINDOW_NAME, testApp.defaultWindowName)
                     }
                 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
index 292d0ef..5e0760c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt
@@ -127,7 +127,7 @@
                         hasVisibleRegion(pipApp.defaultWindowName, startingBounds)
                         isInvisible(testApp.defaultWindowName)
                     }
-                    end("testApp layer covers fullscreen") {
+                    end("testApp layer covers fullscreen", enabled = false) {
                         hasVisibleRegion(testApp.defaultWindowName, endingBounds)
                     }
                     navBarLayerIsAlwaysVisible(bugId = 140855415)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 4a6a5b5..a00c5f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -20,7 +20,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.endRotation
@@ -54,10 +53,8 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class PipRotationTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
@@ -65,9 +62,9 @@
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = FixedAppHelper(instrumentation)
             val pipApp = PipAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation,
-                    listOf(Surface.ROTATION_0, Surface.ROTATION_90))
-                    .buildRotationTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation,
+                supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)) {
+                configuration ->
                         withTestName { buildTestTag("PipRotationTest", testApp, configuration) }
                         repeat { configuration.repetitions }
                         setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
index 1502bcd..3e7eb134 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.focusChanges
@@ -54,18 +53,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 152738416)
 class PipToAppTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = PipAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
                     withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
index df88a2d..5d3bc13 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt
@@ -20,7 +20,6 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.focusChanges
@@ -53,18 +52,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 152738416)
 class PipToHomeTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = PipAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation,
+                supportedRotations = listOf(Surface.ROTATION_0)) { configuration ->
                     withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index dca2732..c0ab20d 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -15,7 +15,10 @@
 android_test {
     name: "WMShellUnitTests",
 
-    srcs: ["**/*.java"],
+    srcs: [
+        "**/*.java",
+        "**/*.kt",
+    ],
 
     static_libs: [
         "WindowManager-Shell",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 862776e..80ea9b9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,11 +16,14 @@
 
 package com.android.wm.shell;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
 
@@ -48,6 +51,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -59,6 +63,9 @@
 
 /**
  * Tests for the shell task organizer.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:ShellTaskOrganizerTests
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -68,6 +75,8 @@
     private ITaskOrganizerController mTaskOrganizerController;
     @Mock
     private Context mContext;
+    @Mock
+    private SizeCompatUI mSizeCompatUI;
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -102,7 +111,8 @@
             doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
                     .when(mTaskOrganizerController).registerTaskOrganizer(any());
         } catch (RemoteException e) {}
-        mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext));
+        mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
+                mSizeCompatUI));
     }
 
     @Test
@@ -257,6 +267,39 @@
         assertTrue(mwListener.appeared.contains(task2));
     }
 
+    @Test
+    public void testOnSizeCompatActivityChanged() {
+        final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN);
+        taskInfo1.displayId = DEFAULT_DISPLAY;
+        taskInfo1.topActivityToken = mock(IBinder.class);
+        taskInfo1.topActivityInSizeCompat = false;
+        final TrackingTaskListener taskListener = new TrackingTaskListener();
+        mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN);
+        mOrganizer.onTaskAppeared(taskInfo1, null);
+
+        // sizeCompatActivity is null if top activity is not in size compat.
+        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+                taskInfo1.configuration.windowConfiguration.getBounds(),
+                null /* sizeCompatActivity*/ , taskListener);
+
+        // sizeCompatActivity is non-null if top activity is in size compat.
+        final RunningTaskInfo taskInfo2 =
+                createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
+        taskInfo2.displayId = taskInfo1.displayId;
+        taskInfo2.topActivityToken = taskInfo1.topActivityToken;
+        taskInfo2.topActivityInSizeCompat = true;
+        mOrganizer.onTaskInfoChanged(taskInfo2);
+        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+                taskInfo1.configuration.windowConfiguration.getBounds(),
+                taskInfo1.topActivityToken,
+                taskListener);
+
+        mOrganizer.onTaskVanished(taskInfo1);
+        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+                null /* taskConfig */, null /* sizeCompatActivity*/,
+                null /* taskListener */);
+    }
+
     private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
index 9eb13fb..bf84a6e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -40,23 +40,23 @@
     }
 
     @Override
+    public void removeAllCallbacks() {
+        mRunnables.clear();
+    }
+
+    @Override
     public void removeCallbacks(Runnable r) {
         mRunnables.remove(r);
     }
 
     @Override
     public boolean hasCallback(Runnable r) {
-        return !mRunnables.isEmpty();
-    }
-
-    @Override
-    public Looper getLooper() {
-        return null;
+        return mRunnables.contains(r);
     }
 
     public void flushAll() {
-        for (int i = mRunnables.size() - 1; i >= 0; --i) {
-            mRunnables.get(i).run();
+        for (Runnable r : mRunnables) {
+            r.run();
         }
         mRunnables.clear();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
index 4bd9bed..17ed396 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/animation/PhysicsAnimatorTest.kt
@@ -26,7 +26,7 @@
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import androidx.dynamicanimation.animation.SpringForce
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
+import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.animation.PhysicsAnimator.EndListener
 import com.android.wm.shell.animation.PhysicsAnimator.UpdateListener
 import com.android.wm.shell.animation.PhysicsAnimatorTestUtils.clearAnimationUpdateFrames
@@ -54,8 +54,7 @@
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
-@Ignore("Blocking presubmits - investigating in b/158697054")
-class PhysicsAnimatorTest : SysuiTestCase() {
+class PhysicsAnimatorTest : ShellTestCase() {
     private lateinit var viewGroup: ViewGroup
     private lateinit var testView: View
     private lateinit var testView2: View
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 7c9b9c3..d3a736e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -41,6 +41,7 @@
 
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.bubbles.BubbleData.TimeSource;
+import com.android.wm.shell.common.ShellExecutor;
 
 import com.google.common.collect.ImmutableList;
 
@@ -98,6 +99,8 @@
     private PendingIntent mDeleteIntent;
     @Mock
     private BubbleLogger mBubbleLogger;
+    @Mock
+    private ShellExecutor mMainExecutor;
 
     @Captor
     private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
@@ -124,21 +127,31 @@
                 mock(NotificationListenerService.Ranking.class);
         when(ranking.visuallyInterruptive()).thenReturn(true);
         mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
-        mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null);
+        mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null,
+                mMainExecutor);
 
         mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null);
-        mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null);
+        mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null,
+                mMainExecutor);
 
-        mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener);
-        mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener);
-        mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener);
-        mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener);
-        mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener);
-        mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener);
-        mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener);
+        mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener,
+                mMainExecutor);
+        mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener,
+                mMainExecutor);
+        mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener,
+                mMainExecutor);
+        mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener,
+                mMainExecutor);
+        mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener,
+                mMainExecutor);
+        mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener,
+                mMainExecutor);
+        mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener,
+                mMainExecutor);
         TestableBubblePositioner positioner = new TestableBubblePositioner(mContext,
                 mock(WindowManager.class));
-        mBubbleData = new BubbleData(getContext(), mBubbleLogger, positioner);
+        mBubbleData = new BubbleData(getContext(), mBubbleLogger, positioner,
+                mMainExecutor);
 
         // Used by BubbleData to set lastAccessedTime
         when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
@@ -796,7 +809,7 @@
         assertWithMessage("addedBubble").that(update.addedBubble).isEqualTo(expected);
     }
 
-    private void assertBubbleRemoved(Bubble expected, @BubbleController.DismissReason int reason) {
+    private void assertBubbleRemoved(Bubble expected, @Bubbles.DismissReason int reason) {
         BubbleData.Update update = mUpdateCaptor.getValue();
         assertWithMessage("removedBubbles").that(update.removedBubbles)
                 .isEqualTo(ImmutableList.of(Pair.create(expected, reason)));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index bde04b6..fc828b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -39,6 +39,7 @@
 
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -54,6 +55,8 @@
     private Notification mNotif;
     @Mock
     private StatusBarNotification mSbn;
+    @Mock
+    private ShellExecutor mMainExecutor;
 
     private BubbleEntry mBubbleEntry;
     private Bundle mExtras;
@@ -78,7 +81,7 @@
         when(mNotif.getBubbleMetadata()).thenReturn(metadata);
         when(mSbn.getKey()).thenReturn("mock");
         mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false);
-        mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null);
+        mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null, mMainExecutor);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 4fab9a5..dd1a6a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -20,12 +20,12 @@
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.util.mockito.eq
 import com.android.wm.shell.ShellTestCase
 import junit.framework.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 5e0d518..4cedc48 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -19,11 +19,13 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.Surface.ROTATION_0;
+import static android.view.WindowInsets.Type.ime;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
@@ -40,18 +42,22 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.Executor;
+
 @SmallTest
 public class DisplayImeControllerTest {
 
     private SurfaceControl.Transaction mT;
     private DisplayImeController.PerDisplay mPerDisplay;
     private IInputMethodManager mMock;
+    private Executor mExecutor;
 
     @Before
     public void setUp() throws Exception {
         mT = mock(SurfaceControl.Transaction.class);
         mMock = mock(IInputMethodManager.class);
-        mPerDisplay = new DisplayImeController(null, null, Runnable::run, new TransactionPool() {
+        mExecutor = spy(Runnable::run);
+        mPerDisplay = new DisplayImeController(null, null, mExecutor, new TransactionPool() {
             @Override
             public SurfaceControl.Transaction acquire() {
                 return mT;
@@ -65,10 +71,36 @@
             public IInputMethodManager getImms() {
                 return mMock;
             }
+            @Override
+            void removeImeSurface() { }
         }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
     }
 
     @Test
+    public void insetsControlChanged_schedulesNoWorkOnExecutor() {
+        mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl());
+        verifyZeroInteractions(mExecutor);
+    }
+
+    @Test
+    public void insetsChanged_schedulesNoWorkOnExecutor() {
+        mPerDisplay.insetsChanged(insetsStateWithIme(false));
+        verifyZeroInteractions(mExecutor);
+    }
+
+    @Test
+    public void showInsets_schedulesNoWorkOnExecutor() {
+        mPerDisplay.showInsets(ime(), true);
+        verifyZeroInteractions(mExecutor);
+    }
+
+    @Test
+    public void hideInsets_schedulesNoWorkOnExecutor() {
+        mPerDisplay.hideInsets(ime(), true);
+        verifyZeroInteractions(mExecutor);
+    }
+
+    @Test
     public void reappliesVisibilityToChangedLeash() {
         verifyZeroInteractions(mT);
         mPerDisplay.mImeShowing = true;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
index 495be41..21bc32c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
@@ -27,7 +27,6 @@
 import android.app.IActivityTaskManager;
 import android.content.ComponentName;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
@@ -234,14 +233,6 @@
         verify(mOtherCallback).onActivityRotation(eq(123));
     }
 
-    @Test
-    public void testOnSizeCompatModeActivityChanged() {
-        IBinder b = mock(IBinder.class);
-        mImpl.onSizeCompatModeActivityChanged(123, b);
-        verify(mCallback).onSizeCompatModeActivityChanged(eq(123), eq(b));
-        verify(mOtherCallback).onSizeCompatModeActivityChanged(eq(123), eq(b));
-    }
-
     /**
      * Handler that synchronously calls TaskStackListenerImpl#handleMessage() when it receives a
      * message.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
index fe53641..9f1ee6c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
@@ -21,8 +21,8 @@
 import android.view.View
 import androidx.dynamicanimation.animation.FloatPropertyCompat
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.animation.PhysicsAnimatorTestUtils
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.animation.PhysicsAnimatorTestUtils
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -43,7 +43,7 @@
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
-class MagnetizedObjectTest : SysuiTestCase() {
+class MagnetizedObjectTest : ShellTestCase() {
     /** Incrementing value for fake MotionEvent timestamps. */
     private var time = 0L
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 79bdaf4..2572106 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -29,6 +29,10 @@
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
@@ -86,11 +90,9 @@
     @Mock
     private ActivityTaskManager mActivityTaskManager;
 
+    // Both the split-screen and start interface.
     @Mock
-    private SplitScreen mSplitScreen;
-
-    @Mock
-    private DragAndDropPolicy.Starter mStarter;
+    private SplitScreen mSplitScreenStarter;
 
     private DisplayLayout mLandscapeDisplayLayout;
     private DisplayLayout mPortraitDisplayLayout;
@@ -126,7 +128,7 @@
         mInsets = Insets.of(0, 0, 0, 0);
 
         mPolicy = new DragAndDropPolicy(
-                mContext, mActivityTaskManager, mSplitScreen, mStarter);
+                mContext, mActivityTaskManager, mSplitScreenStarter, mSplitScreenStarter);
         mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
         mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
         setClipDataResizeable(mNonResizeableActivityClipData, false);
@@ -191,7 +193,7 @@
     }
 
     private void setInSplitScreen(boolean inSplitscreen) {
-        doReturn(inSplitscreen).when(mSplitScreen).isSplitScreenVisible();
+        doReturn(inSplitscreen).when(mSplitScreenStarter).isSplitScreenVisible();
     }
 
     @Test
@@ -202,7 +204,8 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
     }
 
     @Test
@@ -213,12 +216,13 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mStarter).exitSplitScreen();
-        verify(mStarter).startIntent(any(), any());
-        reset(mStarter);
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
+        reset(mSplitScreenStarter);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
     }
 
     @Test
@@ -229,12 +233,13 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mStarter).exitSplitScreen();
-        verify(mStarter).startIntent(any(), any());
-        reset(mStarter);
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
+        reset(mSplitScreenStarter);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
     }
 
     @Test
@@ -245,7 +250,8 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
     }
 
     @Test
@@ -256,7 +262,8 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
     }
 
     @Test
@@ -268,12 +275,14 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
-        reset(mStarter);
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
+        reset(mSplitScreenStarter);
 
         // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
     }
 
     @Test
@@ -285,12 +294,14 @@
                 mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
-        reset(mStarter);
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_UNDEFINED), eq(STAGE_POSITION_UNDEFINED));
+        reset(mSplitScreenStarter);
 
         // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
-        verify(mStarter).startIntent(any(), any());
+        verify(mSplitScreenStarter).startClipDescription(any(), any(),
+                eq(STAGE_TYPE_SIDE), eq(STAGE_POSITION_BOTTOM_OR_RIGHT));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
index 17fc057..8d5139b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
@@ -22,6 +22,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.SurfaceControl;
+import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
 
@@ -50,6 +51,8 @@
 
     @Mock
     private SurfaceControl mMockLeash;
+    @Mock
+    private WindowContainerToken mMockToken;
 
     @Mock
     private ShellExecutor mMainExecutor;
@@ -69,7 +72,7 @@
         destinationBounds.offset(0, 300);
         final OneHandedAnimationController.OneHandedTransitionAnimator animator =
                 mOneHandedAnimationController
-                        .getAnimator(mMockLeash, originalBounds, destinationBounds);
+                        .getAnimator(mMockToken, mMockLeash, originalBounds, destinationBounds);
 
         assertNotNull(animator);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index 6cfd0c4..01162b5c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -24,13 +24,14 @@
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.res.Configuration;
-import android.os.Handler;
+import android.os.Binder;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Display;
@@ -89,12 +90,14 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
+        Binder binder = new Binder();
+        doReturn(binder).when(mMockRealToken).asBinder();
         mToken = new WindowContainerToken(mMockRealToken);
         mLeash = new SurfaceControl();
         mDisplay = mContext.getDisplay();
         mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, FEATURE_ONE_HANDED);
         mDisplayAreaInfo.configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
-        when(mMockAnimationController.getAnimator(any(), any(), any())).thenReturn(null);
+        when(mMockAnimationController.getAnimator(any(), any(), any(), any())).thenReturn(null);
         when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
         when(mMockSurfaceTransactionHelper.translate(any(), any(), anyFloat())).thenReturn(
                 mMockSurfaceTransactionHelper);
@@ -121,7 +124,7 @@
     public void testOnDisplayAreaAppeared() {
         mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
 
-        verify(mMockAnimationController, never()).getAnimator(any(), any(), any());
+        verify(mMockAnimationController, never()).getAnimator(any(), any(), any(), any());
     }
 
     @Test
@@ -129,7 +132,7 @@
         mDisplayAreaOrganizer.onDisplayAreaAppeared(mDisplayAreaInfo, mLeash);
         mDisplayAreaOrganizer.onDisplayAreaVanished(mDisplayAreaInfo);
 
-        assertThat(mDisplayAreaOrganizer.mDisplayAreaMap).isEmpty();
+        assertThat(mDisplayAreaOrganizer.mDisplayAreaTokenMap).isEmpty();
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
index 9219f15..bbe8891 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandlerTest.java
@@ -33,6 +33,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.Before;
@@ -48,7 +49,7 @@
 @RunWith(AndroidTestingRunner.class)
 public class OneHandedTimeoutHandlerTest extends OneHandedTestCase {
     private OneHandedTimeoutHandler mTimeoutHandler;
-    private ShellExecutor mMainExecutor;
+    private TestShellExecutor mMainExecutor;
 
     @Before
     public void setUp() throws Exception {
@@ -104,34 +105,4 @@
         mTimeoutHandler.resetTimer();
         assertTrue(mTimeoutHandler.hasScheduledTimeout());
     }
-
-    private class TestShellExecutor implements ShellExecutor {
-        private ArrayList<Runnable> mExecuted = new ArrayList<>();
-        private ArrayList<Runnable> mDelayed = new ArrayList<>();
-
-        @Override
-        public void execute(Runnable runnable) {
-            mExecuted.add(runnable);
-        }
-
-        @Override
-        public void executeDelayed(Runnable r, long delayMillis) {
-            mDelayed.add(r);
-        }
-
-        @Override
-        public void removeCallbacks(Runnable r) {
-            mDelayed.remove(r);
-        }
-
-        @Override
-        public boolean hasCallback(Runnable r) {
-            return mDelayed.contains(r);
-        }
-
-        @Override
-        public Looper getLooper() {
-            return Looper.myLooper();
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index c565a4c..0087d91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 
+import android.app.TaskInfo;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
@@ -54,6 +55,9 @@
     private SurfaceControl mLeash;
 
     @Mock
+    private TaskInfo mTaskInfo;
+
+    @Mock
     private PipAnimationController.PipAnimationCallback mPipAnimationCallback;
 
     @Before
@@ -70,7 +74,7 @@
     @Test
     public void getAnimator_withAlpha_returnFloatAnimator() {
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
-                .getAnimator(mLeash, new Rect(), 0f, 1f);
+                .getAnimator(mTaskInfo, mLeash, new Rect(), 0f, 1f);
 
         assertEquals("Expect ANIM_TYPE_ALPHA animation",
                 animator.getAnimationType(), PipAnimationController.ANIM_TYPE_ALPHA);
@@ -79,7 +83,7 @@
     @Test
     public void getAnimator_withBounds_returnBoundsAnimator() {
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
-                .getAnimator(mLeash, new Rect(), new Rect(), new Rect(), null,
+                .getAnimator(mTaskInfo, mLeash, new Rect(), new Rect(), new Rect(), null,
                         TRANSITION_DIRECTION_TO_PIP, 0);
 
         assertEquals("Expect ANIM_TYPE_BOUNDS animation",
@@ -93,13 +97,13 @@
         final Rect endValue1 = new Rect(100, 100, 200, 200);
         final Rect endValue2 = new Rect(200, 200, 300, 300);
         final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
-                .getAnimator(mLeash, baseValue, startValue, endValue1, null,
+                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
                         TRANSITION_DIRECTION_TO_PIP, 0);
         oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
         oldAnimator.start();
 
         final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
-                .getAnimator(mLeash, baseValue, startValue, endValue2, null,
+                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue2, null,
                         TRANSITION_DIRECTION_TO_PIP, 0);
 
         assertEquals("getAnimator with same type returns same animator",
@@ -111,13 +115,13 @@
     @Test
     public void getAnimator_setTransitionDirection() {
         PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
-                .getAnimator(mLeash, new Rect(), 0f, 1f)
+                .getAnimator(mTaskInfo, mLeash, new Rect(), 0f, 1f)
                 .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
         assertEquals("Transition to PiP mode",
                 animator.getTransitionDirection(), TRANSITION_DIRECTION_TO_PIP);
 
         animator = mPipAnimationController
-                .getAnimator(mLeash, new Rect(), 0f, 1f)
+                .getAnimator(mTaskInfo, mLeash, new Rect(), 0f, 1f)
                 .setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP);
         assertEquals("Transition to fullscreen mode",
                 animator.getTransitionDirection(), TRANSITION_DIRECTION_LEAVE_PIP);
@@ -131,7 +135,7 @@
         final Rect endValue1 = new Rect(100, 100, 200, 200);
         final Rect endValue2 = new Rect(200, 200, 300, 300);
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
-                .getAnimator(mLeash, baseValue, startValue, endValue1, null,
+                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
                         TRANSITION_DIRECTION_TO_PIP, 0);
 
         animator.updateEndValue(endValue2);
@@ -145,7 +149,7 @@
         final Rect startValue = new Rect(0, 0, 100, 100);
         final Rect endValue = new Rect(100, 100, 200, 200);
         final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
-                .getAnimator(mLeash, baseValue, startValue, endValue, null,
+                .getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
                         TRANSITION_DIRECTION_TO_PIP, 0);
         animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
 
@@ -153,16 +157,16 @@
 
         // onAnimationStart triggers onPipAnimationStart
         animator.onAnimationStart(animator);
-        verify(mPipAnimationCallback).onPipAnimationStart(animator);
+        verify(mPipAnimationCallback).onPipAnimationStart(mTaskInfo, animator);
 
         // onAnimationCancel triggers onPipAnimationCancel
         animator.onAnimationCancel(animator);
-        verify(mPipAnimationCallback).onPipAnimationCancel(animator);
+        verify(mPipAnimationCallback).onPipAnimationCancel(mTaskInfo, animator);
 
         // onAnimationEnd triggers onPipAnimationEnd
         animator.onAnimationEnd(animator);
-        verify(mPipAnimationCallback).onPipAnimationEnd(any(SurfaceControl.Transaction.class),
-                eq(animator));
+        verify(mPipAnimationCallback).onPipAnimationEnd(eq(mTaskInfo),
+                any(SurfaceControl.Transaction.class), eq(animator));
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index ef99235..babfc5c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -24,12 +24,14 @@
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 import android.util.Size;
+import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -62,7 +64,8 @@
         mPipBoundsState = new PipBoundsState(mContext);
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState);
 
-        mPipBoundsState.setDisplayInfo(mDefaultDisplayInfo);
+        mPipBoundsState.setDisplayLayout(
+                new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true));
     }
 
     private void initializeMockResources() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index b5d10d7..9430af9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -18,20 +18,17 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
 import android.os.RemoteException;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -43,9 +40,11 @@
 
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.pip.phone.PhonePipMenuController;
+import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.pip.phone.PhonePipMenuController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -65,13 +64,17 @@
     private PipTaskOrganizer mSpiedPipTaskOrganizer;
 
     @Mock private DisplayController mMockdDisplayController;
-    @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+
     @Mock private PhonePipMenuController mMockPhonePipMenuController;
+    @Mock private PipAnimationController mMockPipAnimationController;
+    @Mock private PipTransitionController mMockPipTransitionController;
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
     @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
+    private TestShellExecutor mMainExecutor;
     private PipBoundsState mPipBoundsState;
+    private PipBoundsAlgorithm mPipBoundsAlgorithm;
 
     private ComponentName mComponent1;
     private ComponentName mComponent2;
@@ -82,10 +85,14 @@
         mComponent1 = new ComponentName(mContext, "component1");
         mComponent2 = new ComponentName(mContext, "component2");
         mPipBoundsState = new PipBoundsState(mContext);
+        mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState);
+        mMainExecutor = new TestShellExecutor();
         mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState,
-                mMockPipBoundsAlgorithm, mMockPhonePipMenuController,
-                mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController,
-                mMockPipUiEventLogger, mMockShellTaskOrganizer));
+                mPipBoundsAlgorithm, mMockPhonePipMenuController,
+                mMockPipAnimationController, mMockPipSurfaceTransactionHelper,
+                mMockPipTransitionController, mMockOptionalSplitScreen, mMockdDisplayController,
+                mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor));
+        mMainExecutor.flushAll();
         preparePipTaskOrg();
     }
 
@@ -110,7 +117,7 @@
 
     @Test
     public void startSwipePipToHome_updatesLastPipComponentName() {
-        mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, null, null);
+        mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, null, createPipParams(null));
 
         assertEquals(mComponent1, mPipBoundsState.getLastPipComponentName());
     }
@@ -119,7 +126,8 @@
     public void startSwipePipToHome_updatesOverrideMinSize() {
         final Size minSize = new Size(100, 80);
 
-        mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, createActivityInfo(minSize), null);
+        mSpiedPipTaskOrganizer.startSwipePipToHome(mComponent1, createActivityInfo(minSize),
+                createPipParams(null));
 
         assertEquals(minSize, mPipBoundsState.getOverrideMinSize());
     }
@@ -191,11 +199,8 @@
 
     private void preparePipTaskOrg() {
         final DisplayInfo info = new DisplayInfo();
-        mPipBoundsState.setDisplayInfo(info);
-        when(mMockPipBoundsAlgorithm.getEntryDestinationBounds()).thenReturn(new Rect());
-        when(mMockPipBoundsAlgorithm.getAdjustedDestinationBounds(any(), anyFloat()))
-                .thenReturn(new Rect());
-        mPipBoundsState.setDisplayInfo(info);
+        mPipBoundsState.setDisplayLayout(new DisplayLayout(info,
+                mContext.getResources(), true, true));
         mSpiedPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
         doNothing().when(mSpiedPipTaskOrganizer).enterPipWithAlphaAnimation(any(), anyLong());
         doNothing().when(mSpiedPipTaskOrganizer).scheduleAnimateResizePip(any(), anyInt(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 62ffac4..cfe8463 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -46,6 +46,7 @@
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -68,6 +69,7 @@
     @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
     @Mock private PipMediaController mMockPipMediaController;
     @Mock private PipTaskOrganizer mMockPipTaskOrganizer;
+    @Mock private PipTransitionController mMockPipTransitionController;
     @Mock private PipTouchHandler mMockPipTouchHandler;
     @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
     @Mock private PipBoundsState mMockPipBoundsState;
@@ -80,8 +82,8 @@
         mPipController = new PipController(mContext, mMockDisplayController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
                 mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
-                mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener,
-                mMockExecutor);
+                mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
+                mMockTaskStackListener, mMockExecutor);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArgument(0)).run();
             return null;
@@ -90,7 +92,7 @@
 
     @Test
     public void instantiatePipController_registersPipTransitionCallback() {
-        verify(mMockPipTaskOrganizer).registerPipTransitionCallback(any());
+        verify(mMockPipTransitionController).registerPipTransitionCallback(any());
     }
 
     @Test
@@ -113,8 +115,8 @@
         assertNull(PipController.create(spyContext, mMockDisplayController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipBoundsState,
                 mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
-                mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener,
-                mMockExecutor));
+                mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
+                mMockTaskStackListener, mMockExecutor));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index b4cfbc2..449ad88 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -38,6 +38,7 @@
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUiEventLogger;
 
 import org.junit.Before;
@@ -67,6 +68,9 @@
     private PipTaskOrganizer mPipTaskOrganizer;
 
     @Mock
+    private PipTransitionController mMockPipTransitionController;
+
+    @Mock
     private FloatingContentCoordinator mFloatingContentCoordinator;
 
     @Mock
@@ -98,7 +102,8 @@
         mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipTouchHandler = new PipTouchHandler(mContext, mPhonePipMenuController,
                 mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer,
-                mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+                mMockPipTransitionController, mFloatingContentCoordinator, mPipUiEventLogger,
+                mMainExecutor);
         mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
         mPipResizeGestureHandler = Mockito.spy(mPipTouchHandler.getPipResizeGestureHandler());
         mPipTouchHandler.setPipMotionHelper(mMotionHelper);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
index 000f7e8..35656bd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
@@ -24,18 +24,15 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import android.os.Handler;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -45,23 +42,22 @@
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
-@RunWithLooper
 public class PipTouchStateTest extends ShellTestCase {
 
     private PipTouchState mTouchState;
     private CountDownLatch mDoubleTapCallbackTriggeredLatch;
     private CountDownLatch mHoverExitCallbackTriggeredLatch;
+    private TestShellExecutor mMainExecutor;
 
     @Before
     public void setUp() throws Exception {
+        mMainExecutor = new TestShellExecutor();
         mDoubleTapCallbackTriggeredLatch = new CountDownLatch(1);
         mHoverExitCallbackTriggeredLatch = new CountDownLatch(1);
         mTouchState = new PipTouchState(ViewConfiguration.get(getContext()),
-                Handler.createAsync(Looper.myLooper()), () -> {
-            mDoubleTapCallbackTriggeredLatch.countDown();
-        }, () -> {
-            mHoverExitCallbackTriggeredLatch.countDown();
-        });
+                mDoubleTapCallbackTriggeredLatch::countDown,
+                mHoverExitCallbackTriggeredLatch::countDown,
+                mMainExecutor);
         assertFalse(mTouchState.isDoubleTap());
         assertFalse(mTouchState.isWaitingForDoubleTap());
     }
@@ -91,9 +87,7 @@
         assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == 10);
         mTouchState.scheduleDoubleTapTimeoutCallback();
 
-        // TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
-        Thread.sleep(15);
-        TestableLooper.get(this).processAllMessages();
+        mMainExecutor.flushAll();
         assertTrue(mDoubleTapCallbackTriggeredLatch.getCount() == 0);
     }
 
@@ -128,17 +122,13 @@
     @Test
     public void testHoverExitTimeout_timeoutCallbackCalled() throws Exception {
         mTouchState.scheduleHoverExitTimeoutCallback();
-
-        // TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
-        Thread.sleep(50);
-        TestableLooper.get(this).processAllMessages();
+        mMainExecutor.flushAll();
         assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 0);
     }
 
     @Test
     public void testHoverExitTimeout_timeoutCallbackNotCalled() throws Exception {
         mTouchState.scheduleHoverExitTimeoutCallback();
-        TestableLooper.get(this).processAllMessages();
         assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
     }
 
@@ -147,14 +137,12 @@
         mTouchState.scheduleHoverExitTimeoutCallback();
         mTouchState.onTouchEvent(createMotionEvent(ACTION_BUTTON_PRESS, SystemClock.uptimeMillis(),
                 0, 0));
-
-        // TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
-        Thread.sleep(50);
-        TestableLooper.get(this).processAllMessages();
+        mMainExecutor.flushAll();
         assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
     }
 
     private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
         return MotionEvent.obtain(0, eventTime, action, x, y, 0);
     }
+
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
new file mode 100644
index 0000000..98f01ff
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2021 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.wm.shell.sizecompatui;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link SizeCompatUIController}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:SizeCompatUIControllerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class SizeCompatUIControllerTest extends ShellTestCase {
+    private static final int DISPLAY_ID = 0;
+
+    private final TestShellExecutor mShellMainExecutor = new TestShellExecutor();
+
+    private SizeCompatUIController mController;
+    private @Mock DisplayController mMockDisplayController;
+    private @Mock DisplayImeController mMockImeController;
+    private @Mock SizeCompatRestartButton mMockButton;
+    private @Mock IBinder mMockActivityToken;
+    private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(true).when(mMockButton).show();
+
+        mController = new SizeCompatUIController(mContext, mMockDisplayController,
+                mMockImeController, mShellMainExecutor) {
+            @Override
+            SizeCompatRestartButton createRestartButton(Context context, int displayId) {
+                return mMockButton;
+            }
+        };
+    }
+
+    @Test
+    public void testListenerRegistered() {
+        verify(mMockDisplayController).addDisplayWindowListener(mController);
+        verify(mMockImeController).addPositionProcessor(mController);
+    }
+
+    @Test
+    public void testOnSizeCompatInfoChanged() {
+        final int taskId = 12;
+        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+
+        // Verify that the restart button is added with non-null size compat activity.
+        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+                mMockActivityToken, mMockTaskListener);
+        mShellMainExecutor.flushAll();
+
+        verify(mMockButton).show();
+        verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
+
+        // Verify that the restart button is removed with null size compat activity.
+        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, null, null, null);
+
+        mShellMainExecutor.flushAll();
+        verify(mMockButton).remove();
+    }
+
+    @Test
+    public void testChangeButtonVisibilityOnImeShowHide() {
+        final int taskId = 12;
+        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+        mController.mImpl.onSizeCompatInfoChanged(DISPLAY_ID, taskId, taskBounds,
+                mMockActivityToken, mMockTaskListener);
+        mShellMainExecutor.flushAll();
+
+        // Verify that the restart button is hidden when IME is visible.
+        doReturn(View.VISIBLE).when(mMockButton).getVisibility();
+        mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+
+        verify(mMockButton).setVisibility(eq(View.GONE));
+
+        // Verify that the restart button is visible when IME is hidden.
+        doReturn(View.GONE).when(mMockButton).getVisibility();
+        mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+
+        verify(mMockButton).setVisibility(eq(View.VISIBLE));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 168e0df..d2d1812 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -19,7 +19,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 
-import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -71,7 +71,7 @@
     public void testMoveToSideStage() {
         final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
 
-        mStageCoordinator.moveToSideStage(task, SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT);
+        mStageCoordinator.moveToSideStage(task, STAGE_POSITION_BOTTOM_OR_RIGHT);
 
         verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class));
         verify(mSideStage).addTask(eq(task), any(Rect.class),
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index b54f7d8..4b4284a 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -41,6 +41,7 @@
         "AssetDir.cpp",
         "AssetManager.cpp",
         "AssetManager2.cpp",
+        "AssetsProvider.cpp",
         "AttributeResolution.cpp",
         "ChunkIterator.cpp",
         "ConfigDescription.cpp",
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 011a0de..ca5981c0 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -16,533 +16,145 @@
 
 #include "androidfw/ApkAssets.h"
 
-#include <algorithm>
-
 #include "android-base/errors.h"
-#include "android-base/file.h"
 #include "android-base/logging.h"
-#include "android-base/stringprintf.h"
-#include "android-base/unique_fd.h"
-#include "android-base/utf8.h"
-#include "utils/Compat.h"
-#include "ziparchive/zip_archive.h"
-
-#include "androidfw/Asset.h"
-#include "androidfw/Idmap.h"
-#include "androidfw/misc.h"
-#include "androidfw/Util.h"
 
 namespace android {
 
 using base::SystemErrorCodeToString;
 using base::unique_fd;
 
-static const std::string kResourcesArsc("resources.arsc");
+constexpr const char* kResourcesArsc = "resources.arsc";
 
-ApkAssets::ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
-                     std::string path,
-                     time_t last_mod_time,
-                     package_property_t property_flags)
-    : assets_provider_(std::move(assets_provider)),
-      path_(std::move(path)),
-      last_mod_time_(last_mod_time),
-      property_flags_(property_flags) {
+ApkAssets::ApkAssets(std::unique_ptr<Asset> resources_asset,
+                     std::unique_ptr<LoadedArsc> loaded_arsc,
+                     std::unique_ptr<AssetsProvider> assets,
+                     package_property_t property_flags,
+                     std::unique_ptr<Asset> idmap_asset,
+                     std::unique_ptr<LoadedIdmap> loaded_idmap)
+    : resources_asset_(std::move(resources_asset)),
+      loaded_arsc_(std::move(loaded_arsc)),
+      assets_provider_(std::move(assets)),
+      property_flags_(property_flags),
+      idmap_asset_(std::move(idmap_asset)),
+      loaded_idmap_(std::move(loaded_idmap)) {}
+
+std::unique_ptr<ApkAssets> ApkAssets::Load(const std::string& path, package_property_t flags) {
+  return Load(ZipAssetsProvider::Create(path), flags);
 }
 
-// Provides asset files from a zip file.
-class ZipAssetsProvider : public AssetsProvider {
- public:
-  ~ZipAssetsProvider() override = default;
-
-  static std::unique_ptr<const AssetsProvider> Create(const std::string& path) {
-    ::ZipArchiveHandle unmanaged_handle;
-    const int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
-    if (result != 0) {
-      LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
-      ::CloseArchive(unmanaged_handle);
-      return {};
-    }
-
-    return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider(path, path, unmanaged_handle));
-  }
-
-  static std::unique_ptr<const AssetsProvider> Create(
-      unique_fd fd, const std::string& friendly_name, const off64_t offset = 0,
-      const off64_t length = ApkAssets::kUnknownLength) {
-
-    ::ZipArchiveHandle unmanaged_handle;
-    const int32_t result = (length == ApkAssets::kUnknownLength)
-        ? ::OpenArchiveFd(fd.release(), friendly_name.c_str(), &unmanaged_handle)
-        : ::OpenArchiveFdRange(fd.release(), friendly_name.c_str(), &unmanaged_handle, length,
-                               offset);
-
-    if (result != 0) {
-      LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
-                 << " and length " << length << ": " << ::ErrorCodeString(result);
-      ::CloseArchive(unmanaged_handle);
-      return {};
-    }
-
-    return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider({}, friendly_name,
-                                                                 unmanaged_handle));
-  }
-
-  // Iterate over all files and directories within the zip. The order of iteration is not
-  // guaranteed to be the same as the order of elements in the central directory but is stable for a
-  // given zip file.
-  bool ForEachFile(const std::string& root_path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override {
-    // If this is a resource loader from an .arsc, there will be no zip handle
-    if (zip_handle_ == nullptr) {
-      return false;
-    }
-
-    std::string root_path_full = root_path;
-    if (root_path_full.back() != '/') {
-      root_path_full += '/';
-    }
-
-    void* cookie;
-    if (::StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
-      return false;
-    }
-
-    std::string name;
-    ::ZipEntry entry{};
-
-    // We need to hold back directories because many paths will contain them and we want to only
-    // surface one.
-    std::set<std::string> dirs{};
-
-    int32_t result;
-    while ((result = ::Next(cookie, &entry, &name)) == 0) {
-      StringPiece full_file_path(name);
-      StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
-
-      if (!leaf_file_path.empty()) {
-        auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
-        if (iter != leaf_file_path.end()) {
-          std::string dir =
-              leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
-          dirs.insert(std::move(dir));
-        } else {
-          f(leaf_file_path, kFileTypeRegular);
-        }
-      }
-    }
-    ::EndIteration(cookie);
-
-    // Now present the unique directories.
-    for (const std::string& dir : dirs) {
-      f(dir, kFileTypeDirectory);
-    }
-
-    // -1 is end of iteration, anything else is an error.
-    return result == -1;
-  }
-
- protected:
-  std::unique_ptr<Asset> OpenInternal(
-      const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
-    if (file_exists) {
-      *file_exists = false;
-    }
-
-    ::ZipEntry entry;
-    int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
-    if (result != 0) {
-      return {};
-    }
-
-    if (file_exists) {
-      *file_exists = true;
-    }
-
-    const int fd = ::GetFileDescriptor(zip_handle_.get());
-    const off64_t fd_offset = ::GetFileDescriptorOffset(zip_handle_.get());
-    incfs::IncFsFileMap asset_map;
-    if (entry.method == kCompressDeflated) {
-      if (!asset_map.Create(fd, entry.offset + fd_offset, entry.compressed_length, GetPath())) {
-        LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
-        return {};
-      }
-
-      std::unique_ptr<Asset> asset =
-          Asset::createFromCompressedMap(std::move(asset_map), entry.uncompressed_length, mode);
-      if (asset == nullptr) {
-        LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << friendly_name_ << "'";
-        return {};
-      }
-      return asset;
-    }
-
-    if (!asset_map.Create(fd, entry.offset + fd_offset, entry.uncompressed_length, GetPath())) {
-      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
-      return {};
-    }
-
-    unique_fd ufd;
-    if (!GetPath()) {
-      // If the `path` is not set, create a new `fd` for the new Asset to own in order to create
-      // new file descriptors using Asset::openFileDescriptor. If the path is set, it will be used
-      // to create new file descriptors.
-      ufd = unique_fd(dup(fd));
-      if (!ufd.ok()) {
-        LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << friendly_name_ << "'";
-        return {};
-      }
-    }
-
-    auto asset = Asset::createFromUncompressedMap(std::move(asset_map), mode, std::move(ufd));
-    if (asset == nullptr) {
-      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
-      return {};
-    }
-    return asset;
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ZipAssetsProvider);
-
-  explicit ZipAssetsProvider(std::string path,
-                             std::string friendly_name,
-                             ZipArchiveHandle unmanaged_handle)
-                             : zip_handle_(unmanaged_handle, ::CloseArchive),
-                               path_(std::move(path)),
-                               friendly_name_(std::move(friendly_name)) { }
-
-  const char* GetPath() const {
-    return path_.empty() ? nullptr : path_.c_str();
-  }
-
-  using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
-  ZipArchivePtr zip_handle_;
-  std::string path_;
-  std::string friendly_name_;
-};
-
-class DirectoryAssetsProvider : AssetsProvider {
- public:
-  ~DirectoryAssetsProvider() override = default;
-
-  static std::unique_ptr<const AssetsProvider> Create(const std::string& path) {
-    struct stat sb{};
-    const int result = stat(path.c_str(), &sb);
-    if (result == -1) {
-      LOG(ERROR) << "Failed to find directory '" << path << "'.";
-      return nullptr;
-    }
-
-    if (!S_ISDIR(sb.st_mode)) {
-      LOG(ERROR) << "Path '" << path << "' is not a directory.";
-      return nullptr;
-    }
-
-    return std::unique_ptr<AssetsProvider>(new DirectoryAssetsProvider(path));
-  }
-
- protected:
-  std::unique_ptr<Asset> OpenInternal(
-      const std::string& path, Asset::AccessMode /* mode */, bool* file_exists) const override {
-    const std::string resolved_path = ResolvePath(path);
-    if (file_exists) {
-      struct stat sb{};
-      const int result = stat(resolved_path.c_str(), &sb);
-      *file_exists = result != -1 && S_ISREG(sb.st_mode);
-    }
-
-    return ApkAssets::CreateAssetFromFile(resolved_path);
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DirectoryAssetsProvider);
-
-  explicit DirectoryAssetsProvider(std::string path) : path_(std::move(path)) { }
-
-  inline std::string ResolvePath(const std::string& path) const {
-    return base::StringPrintf("%s%c%s", path_.c_str(), OS_PATH_SEPARATOR, path.c_str());
-  }
-
-  const std::string path_;
-};
-
-// AssetProvider implementation that does not provide any assets. Used for ApkAssets::LoadEmpty.
-class EmptyAssetsProvider : public AssetsProvider {
- public:
-  EmptyAssetsProvider() = default;
-  ~EmptyAssetsProvider() override = default;
-
- protected:
-  std::unique_ptr<Asset> OpenInternal(const std::string& /*path */,
-                                      Asset::AccessMode /* mode */,
-                                      bool* file_exists) const override {
-    if (file_exists) {
-      *file_exists = false;
-    }
-    return nullptr;
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(EmptyAssetsProvider);
-};
-
-// AssetProvider implementation
-class MultiAssetsProvider : public AssetsProvider {
- public:
-  ~MultiAssetsProvider() override = default;
-
-  static std::unique_ptr<const AssetsProvider> Create(
-      std::unique_ptr<const AssetsProvider> child, std::unique_ptr<const AssetsProvider> parent) {
-    CHECK(parent != nullptr) << "parent provider must not be null";
-    return (!child) ? std::move(parent)
-                    : std::unique_ptr<const AssetsProvider>(new MultiAssetsProvider(
-                        std::move(child), std::move(parent)));
-  }
-
-  bool ForEachFile(const std::string& root_path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const override {
-    // TODO: Only call the function once for files defined in the parent and child
-    return child_->ForEachFile(root_path, f) && parent_->ForEachFile(root_path, f);
-  }
-
- protected:
-  std::unique_ptr<Asset> OpenInternal(
-      const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
-    auto asset = child_->Open(path, mode, file_exists);
-    return (asset) ? std::move(asset) : parent_->Open(path, mode, file_exists);
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MultiAssetsProvider);
-
-  MultiAssetsProvider(std::unique_ptr<const AssetsProvider> child,
-                      std::unique_ptr<const AssetsProvider> parent)
-                      : child_(std::move(child)), parent_(std::move(parent)) { }
-
-  std::unique_ptr<const AssetsProvider> child_;
-  std::unique_ptr<const AssetsProvider> parent_;
-};
-
-// Opens the archive using the file path. Calling CloseArchive on the zip handle will close the
-// file.
-std::unique_ptr<const ApkAssets> ApkAssets::Load(
-    const std::string& path, const package_property_t flags,
-    std::unique_ptr<const AssetsProvider> override_asset) {
-  auto assets = ZipAssetsProvider::Create(path);
-  return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset))
-                  : nullptr;
+std::unique_ptr<ApkAssets> ApkAssets::LoadFromFd(base::unique_fd fd,
+                                                 const std::string& debug_name,
+                                                 package_property_t flags,
+                                                 off64_t offset,
+                                                 off64_t len) {
+  return Load(ZipAssetsProvider::Create(std::move(fd), debug_name, offset, len), flags);
 }
 
-// Opens the archive using the file file descriptor with the specified file offset and read length.
-// If the `assume_ownership` parameter is 'true' calling CloseArchive will close the file.
-std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(
-    unique_fd fd, const std::string& friendly_name, const package_property_t flags,
-    std::unique_ptr<const AssetsProvider> override_asset, const off64_t offset,
-    const off64_t length) {
-  CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
-  CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
-                                                 << kUnknownLength;
-
-  auto assets = ZipAssetsProvider::Create(std::move(fd), friendly_name, offset, length);
-  return (assets) ? LoadImpl(std::move(assets), friendly_name, flags, std::move(override_asset))
-                  : nullptr;
+std::unique_ptr<ApkAssets> ApkAssets::Load(std::unique_ptr<AssetsProvider> assets,
+                                           package_property_t flags) {
+  return LoadImpl(std::move(assets), flags, nullptr /* idmap_asset */, nullptr /* loaded_idmap */);
 }
 
-std::unique_ptr<const ApkAssets> ApkAssets::LoadTable(
-    const std::string& path, const package_property_t flags,
-    std::unique_ptr<const AssetsProvider> override_asset) {
-
-  auto assets = CreateAssetFromFile(path);
-  return (assets) ? LoadTableImpl(std::move(assets), path, flags, std::move(override_asset))
-                  : nullptr;
+std::unique_ptr<ApkAssets> ApkAssets::LoadTable(std::unique_ptr<Asset> resources_asset,
+                                                std::unique_ptr<AssetsProvider> assets,
+                                                package_property_t flags) {
+  if (resources_asset == nullptr) {
+    return {};
+  }
+  return LoadImpl(std::move(resources_asset), std::move(assets), flags, nullptr /* idmap_asset */,
+                  nullptr /* loaded_idmap */);
 }
 
-std::unique_ptr<const ApkAssets> ApkAssets::LoadTableFromFd(
-    unique_fd fd, const std::string& friendly_name, const package_property_t flags,
-    std::unique_ptr<const AssetsProvider> override_asset, const off64_t offset,
-    const off64_t length) {
-
-  auto assets = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length);
-  return (assets) ? LoadTableImpl(std::move(assets), friendly_name, flags,
-                                  std::move(override_asset))
-                  : nullptr;
-}
-
-std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
-                                                        const package_property_t flags) {
+std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
+                                                  package_property_t flags) {
   CHECK((flags & PROPERTY_LOADER) == 0U) << "Cannot load RROs through loaders";
-  std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path);
+  auto idmap_asset = AssetsProvider::CreateAssetFromFile(idmap_path);
   if (idmap_asset == nullptr) {
+    LOG(ERROR) << "failed to read IDMAP " << idmap_path;
     return {};
   }
 
-  const StringPiece idmap_data(
-      reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)),
-      static_cast<size_t>(idmap_asset->getLength()));
-  std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_path, idmap_data);
+  StringPiece idmap_data(reinterpret_cast<const char*>(idmap_asset->getBuffer(true /* aligned */)),
+                         static_cast<size_t>(idmap_asset->getLength()));
+  auto loaded_idmap = LoadedIdmap::Load(idmap_path, idmap_data);
   if (loaded_idmap == nullptr) {
     LOG(ERROR) << "failed to load IDMAP " << idmap_path;
     return {};
   }
-  
-  auto overlay_path = std::string(loaded_idmap->OverlayApkPath());
-  auto assets = ZipAssetsProvider::Create(overlay_path);
-  return (assets) ? LoadImpl(std::move(assets), overlay_path, flags | PROPERTY_OVERLAY,
-                             nullptr /* override_asset */, std::move(idmap_asset),
-                             std::move(loaded_idmap))
-                  : nullptr;
-}
 
-std::unique_ptr<const ApkAssets> ApkAssets::LoadFromDir(
-    const std::string& path, const package_property_t flags,
-    std::unique_ptr<const AssetsProvider> override_asset) {
-
-  auto assets = DirectoryAssetsProvider::Create(path);
-  return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset))
-                  : nullptr;
-}
-
-std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(
-    const package_property_t flags, std::unique_ptr<const AssetsProvider> override_asset) {
-
-  auto assets = (override_asset) ? std::move(override_asset)
-                                 : std::unique_ptr<const AssetsProvider>(new EmptyAssetsProvider());
-  std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(std::move(assets), "empty" /* path */,
-                                                      -1 /* last_mod-time */, flags));
-  loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
-  // Need to force a move for mingw32.
-  return std::move(loaded_apk);
-}
-
-std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
-  unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
-  if (!fd.ok()) {
-    LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno);
+  const std::string overlay_path(loaded_idmap->OverlayApkPath());
+  auto overlay_assets = ZipAssetsProvider::Create(overlay_path);
+  if (overlay_assets == nullptr) {
     return {};
   }
 
-  return CreateAssetFromFd(std::move(fd), path.c_str());
+  return LoadImpl(std::move(overlay_assets), flags | PROPERTY_OVERLAY, std::move(idmap_asset),
+                  std::move(loaded_idmap));
 }
 
-std::unique_ptr<Asset> ApkAssets::CreateAssetFromFd(base::unique_fd fd,
-                                                    const char* path,
-                                                    off64_t offset,
-                                                    off64_t length) {
-  CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
-  CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
-                                                 << kUnknownLength;
-  if (length == kUnknownLength) {
-    length = lseek64(fd, 0, SEEK_END);
-    if (length < 0) {
-      LOG(ERROR) << "Failed to get size of file '" << ((path) ? path : "anon") << "': "
-                 << SystemErrorCodeToString(errno);
-      return {};
-    }
-  }
-
-  incfs::IncFsFileMap file_map;
-  if (!file_map.Create(fd, offset, static_cast<size_t>(length), path)) {
-    LOG(ERROR) << "Failed to mmap file '" << ((path) ? path : "anon") << "': "
-               << SystemErrorCodeToString(errno);
+std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<AssetsProvider> assets,
+                                               package_property_t property_flags,
+                                               std::unique_ptr<Asset> idmap_asset,
+                                               std::unique_ptr<LoadedIdmap> loaded_idmap) {
+  if (assets == nullptr) {
     return {};
   }
 
-  // If `path` is set, do not pass ownership of the `fd` to the new Asset since
-  // Asset::openFileDescriptor can use `path` to create new file descriptors.
-  return Asset::createFromUncompressedMap(std::move(file_map),
-                                          Asset::AccessMode::ACCESS_RANDOM,
-                                          (path) ? base::unique_fd(-1) : std::move(fd));
-}
-
-std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
-    std::unique_ptr<const AssetsProvider> assets, const std::string& path,
-    package_property_t property_flags, std::unique_ptr<const AssetsProvider> override_assets,
-    std::unique_ptr<Asset> idmap_asset, std::unique_ptr<const LoadedIdmap> idmap) {
-
-  const time_t last_mod_time = getFileModDate(path.c_str());
-
   // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
   bool resources_asset_exists = false;
-  auto resources_asset_ = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER,
-                                       &resources_asset_exists);
-
-  assets = MultiAssetsProvider::Create(std::move(override_assets), std::move(assets));
-
-  // Wrap the handle in a unique_ptr so it gets automatically closed.
-  std::unique_ptr<ApkAssets>
-      loaded_apk(new ApkAssets(std::move(assets), path, last_mod_time, property_flags));
-
-  if (!resources_asset_exists) {
-    loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
-    return std::move(loaded_apk);
-  }
-
-  loaded_apk->resources_asset_ = std::move(resources_asset_);
-  if (!loaded_apk->resources_asset_) {
-    LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
+  auto resources_asset = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER,
+                                      &resources_asset_exists);
+  if (resources_asset == nullptr && resources_asset_exists) {
+    LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << assets->GetDebugName()
+               << "'.";
     return {};
   }
 
-  // Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
-  loaded_apk->idmap_asset_ = std::move(idmap_asset);
-  loaded_apk->loaded_idmap_ = std::move(idmap);
-
-  const auto data = loaded_apk->resources_asset_->getIncFsBuffer(true /* aligned */);
-  const size_t length = loaded_apk->resources_asset_->getLength();
-  if (!data || length == 0) {
-    LOG(ERROR) << "Failed to read '" << kResourcesArsc << "' data in APK '" << path << "'.";
-    return {};
-  }
-
-  loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, length, loaded_apk->loaded_idmap_.get(),
-                                              property_flags);
-  if (!loaded_apk->loaded_arsc_) {
-    LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
-    return {};
-  }
-
-  // Need to force a move for mingw32.
-  return std::move(loaded_apk);
+  return LoadImpl(std::move(resources_asset), std::move(assets), property_flags,
+                  std::move(idmap_asset), std::move(loaded_idmap));
 }
 
-std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(
-    std::unique_ptr<Asset> resources_asset, const std::string& path,
-    package_property_t property_flags, std::unique_ptr<const AssetsProvider> override_assets) {
-
-  const time_t last_mod_time = getFileModDate(path.c_str());
-
-  auto assets = (override_assets) ? std::move(override_assets)
-                                  : std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider());
-
-  std::unique_ptr<ApkAssets> loaded_apk(
-      new ApkAssets(std::move(assets), path, last_mod_time, property_flags));
-  loaded_apk->resources_asset_ = std::move(resources_asset);
-
-  const auto data = loaded_apk->resources_asset_->getIncFsBuffer(true /* aligned */);
-  const size_t length = loaded_apk->resources_asset_->getLength();
-  if (!data || length == 0) {
-    LOG(ERROR) << "Failed to read resources table data in '" << path << "'.";
+std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_asset,
+                                               std::unique_ptr<AssetsProvider> assets,
+                                               package_property_t property_flags,
+                                               std::unique_ptr<Asset> idmap_asset,
+                                               std::unique_ptr<LoadedIdmap> loaded_idmap) {
+  if (assets == nullptr ) {
     return {};
   }
 
-  loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, length, nullptr /* loaded_idmap */,
-                                              property_flags);
-  if (loaded_apk->loaded_arsc_ == nullptr) {
-    LOG(ERROR) << "Failed to read resources table in '" << path << "'.";
+  std::unique_ptr<LoadedArsc> loaded_arsc;
+  if (resources_asset != nullptr) {
+    const auto data = resources_asset->getIncFsBuffer(true /* aligned */);
+    const size_t length = resources_asset->getLength();
+    if (!data || length == 0) {
+      LOG(ERROR) << "Failed to read resources table in APK '" << assets->GetDebugName() << "'.";
+      return {};
+    }
+    loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
+  } else {
+    loaded_arsc = LoadedArsc::CreateEmpty();
+  }
+
+  if (loaded_arsc == nullptr) {
+    LOG(ERROR) << "Failed to load resources table in APK '" << assets->GetDebugName() << "'.";
     return {};
   }
 
-  // Need to force a move for mingw32.
-  return std::move(loaded_apk);
+  return std::unique_ptr<ApkAssets>(new ApkAssets(std::move(resources_asset),
+                                                  std::move(loaded_arsc), std::move(assets),
+                                                  property_flags, std::move(idmap_asset),
+                                                  std::move(loaded_idmap)));
+}
+
+const std::string& ApkAssets::GetPath() const {
+  return assets_provider_->GetDebugName();
 }
 
 bool ApkAssets::IsUpToDate() const {
-  if (IsLoader()) {
-    // Loaders are invalidated by the app, not the system, so assume they are up to date.
-    return true;
-  }
-  return (!loaded_idmap_ || loaded_idmap_->IsUpToDate()) &&
-      last_mod_time_ == getFileModDate(path_.c_str());
+  // Loaders are invalidated by the app, not the system, so assume they are up to date.
+  return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate())
+                        && assets_provider_->IsUpToDate());
 }
-
 }  // namespace android
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
new file mode 100644
index 0000000..23cacf8
--- /dev/null
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/AssetsProvider.h"
+
+#include <sys/stat.h>
+
+#include <android-base/errors.h>
+#include <android-base/stringprintf.h>
+#include <android-base/utf8.h>
+#include <ziparchive/zip_archive.h>
+
+namespace android {
+namespace {
+constexpr const char* kEmptyDebugString = "<empty>";
+} // namespace
+
+std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
+                                            bool* file_exists) const {
+  return OpenInternal(path, mode, file_exists);
+}
+
+std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFile(const std::string& path) {
+  base::unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_CLOEXEC));
+  if (!fd.ok()) {
+    LOG(ERROR) << "Failed to open file '" << path << "': " << base::SystemErrorCodeToString(errno);
+    return {};
+  }
+
+  return CreateAssetFromFd(std::move(fd), path.c_str());
+}
+
+std::unique_ptr<Asset> AssetsProvider::CreateAssetFromFd(base::unique_fd fd,
+                                                         const char* path,
+                                                         off64_t offset,
+                                                         off64_t length) {
+  CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
+  CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
+                                                 << kUnknownLength;
+  if (length == kUnknownLength) {
+    length = lseek64(fd, 0, SEEK_END);
+    if (length < 0) {
+      LOG(ERROR) << "Failed to get size of file '" << ((path) ? path : "anon") << "': "
+                 << base::SystemErrorCodeToString(errno);
+      return {};
+    }
+  }
+
+  incfs::IncFsFileMap file_map;
+  if (!file_map.Create(fd, offset, static_cast<size_t>(length), path)) {
+    LOG(ERROR) << "Failed to mmap file '" << ((path != nullptr) ? path : "anon") << "': "
+               << base::SystemErrorCodeToString(errno);
+    return {};
+  }
+
+  // If `path` is set, do not pass ownership of the `fd` to the new Asset since
+  // Asset::openFileDescriptor can use `path` to create new file descriptors.
+  return Asset::createFromUncompressedMap(std::move(file_map),
+                                          Asset::AccessMode::ACCESS_RANDOM,
+                                          (path != nullptr) ? base::unique_fd(-1) : std::move(fd));
+}
+
+ZipAssetsProvider::PathOrDebugName::PathOrDebugName(std::string&& value, bool is_path)
+    : value_(std::forward<std::string>(value)), is_path_(is_path) {}
+
+const std::string* ZipAssetsProvider::PathOrDebugName::GetPath() const {
+  return is_path_ ? &value_ : nullptr;
+}
+
+const std::string& ZipAssetsProvider::PathOrDebugName::GetDebugName() const {
+  return value_;
+}
+
+ZipAssetsProvider::ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path,
+                                     time_t last_mod_time)
+    : zip_handle_(handle, ::CloseArchive),
+      name_(std::forward<PathOrDebugName>(path)),
+      last_mod_time_(last_mod_time) {}
+
+std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path) {
+  ZipArchiveHandle handle;
+  if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
+    LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
+    CloseArchive(handle);
+    return {};
+  }
+
+  struct stat sb{.st_mtime = -1};
+  if (stat(path.c_str(), &sb) < 0) {
+    // Stat requires execute permissions on all directories path to the file. If the process does
+    // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+    // always have to return true.
+    LOG(WARNING) << "Failed to stat file '" << path << "': "
+                 << base::SystemErrorCodeToString(errno);
+  }
+
+  return std::unique_ptr<ZipAssetsProvider>(
+      new ZipAssetsProvider(handle, PathOrDebugName{std::move(path),
+                                                    true /* is_path */}, sb.st_mtime));
+}
+
+std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
+                                                             std::string friendly_name,
+                                                             off64_t offset,
+                                                             off64_t len) {
+  ZipArchiveHandle handle;
+  const int released_fd = fd.release();
+  const int32_t result = (len == AssetsProvider::kUnknownLength)
+      ? ::OpenArchiveFd(released_fd, friendly_name.c_str(), &handle)
+      : ::OpenArchiveFdRange(released_fd, friendly_name.c_str(), &handle, len, offset);
+
+  if (result != 0) {
+    LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
+               << " and length " << len << ": " << ::ErrorCodeString(result);
+    CloseArchive(handle);
+    return {};
+  }
+
+  struct stat sb{.st_mtime = -1};
+  if (fstat(released_fd, &sb) < 0) {
+    // Stat requires execute permissions on all directories path to the file. If the process does
+    // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+    // always have to return true.
+    LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
+                 << base::SystemErrorCodeToString(errno);
+  }
+
+  return std::unique_ptr<ZipAssetsProvider>(
+      new ZipAssetsProvider(handle, PathOrDebugName{std::move(friendly_name),
+                                                    false /* is_path */}, sb.st_mtime));
+}
+
+std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
+                                                       Asset::AccessMode mode,
+                                                       bool* file_exists) const {
+    if (file_exists != nullptr) {
+      *file_exists = false;
+    }
+
+    ZipEntry entry;
+    if (FindEntry(zip_handle_.get(), path, &entry) != 0) {
+      return {};
+    }
+
+    if (file_exists != nullptr) {
+      *file_exists = true;
+    }
+
+    const int fd = GetFileDescriptor(zip_handle_.get());
+    const off64_t fd_offset = GetFileDescriptorOffset(zip_handle_.get());
+    incfs::IncFsFileMap asset_map;
+    if (entry.method == kCompressDeflated) {
+      if (!asset_map.Create(fd, entry.offset + fd_offset, entry.compressed_length,
+                            name_.GetDebugName().c_str())) {
+        LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName()
+                   << "'";
+        return {};
+      }
+
+      std::unique_ptr<Asset> asset =
+          Asset::createFromCompressedMap(std::move(asset_map), entry.uncompressed_length, mode);
+      if (asset == nullptr) {
+        LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << name_.GetDebugName()
+                   << "'";
+        return {};
+      }
+      return asset;
+    }
+
+    if (!asset_map.Create(fd, entry.offset + fd_offset, entry.uncompressed_length,
+                          name_.GetDebugName().c_str())) {
+      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName() << "'";
+      return {};
+    }
+
+    base::unique_fd ufd;
+    if (name_.GetPath() == nullptr) {
+      // If the zip name does not represent a path, create a new `fd` for the new Asset to own in
+      // order to create new file descriptors using Asset::openFileDescriptor. If the zip name is a
+      // path, it will be used to create new file descriptors.
+      ufd = base::unique_fd(dup(fd));
+      if (!ufd.ok()) {
+        LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << name_.GetDebugName() << "'";
+        return {};
+      }
+    }
+
+    auto asset = Asset::createFromUncompressedMap(std::move(asset_map), mode, std::move(ufd));
+    if (asset == nullptr) {
+      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << name_.GetDebugName() << "'";
+      return {};
+    }
+    return asset;
+}
+
+bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
+                                    const std::function<void(const StringPiece&, FileType)>& f)
+                                    const {
+    std::string root_path_full = root_path;
+    if (root_path_full.back() != '/') {
+      root_path_full += '/';
+    }
+
+    void* cookie;
+    if (StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
+      return false;
+    }
+
+    std::string name;
+    ::ZipEntry entry{};
+
+    // We need to hold back directories because many paths will contain them and we want to only
+    // surface one.
+    std::set<std::string> dirs{};
+
+    int32_t result;
+    while ((result = Next(cookie, &entry, &name)) == 0) {
+      StringPiece full_file_path(name);
+      StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
+
+      if (!leaf_file_path.empty()) {
+        auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
+        if (iter != leaf_file_path.end()) {
+          std::string dir =
+              leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+          dirs.insert(std::move(dir));
+        } else {
+          f(leaf_file_path, kFileTypeRegular);
+        }
+      }
+    }
+    EndIteration(cookie);
+
+    // Now present the unique directories.
+    for (const std::string& dir : dirs) {
+      f(dir, kFileTypeDirectory);
+    }
+
+    // -1 is end of iteration, anything else is an error.
+    return result == -1;
+}
+
+const std::string& ZipAssetsProvider::GetDebugName() const {
+  return name_.GetDebugName();
+}
+
+bool ZipAssetsProvider::IsUpToDate() const {
+  struct stat sb{};
+  if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
+    // If fstat fails on the zip archive, return true so the zip archive the resource system does
+    // attempt to refresh the ApkAsset.
+    return true;
+  }
+  return last_mod_time_ == sb.st_mtime;
+}
+
+DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
+    : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
+
+std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
+  struct stat sb{};
+  const int result = stat(path.c_str(), &sb);
+  if (result == -1) {
+    LOG(ERROR) << "Failed to find directory '" << path << "'.";
+    return nullptr;
+  }
+
+  if (!S_ISDIR(sb.st_mode)) {
+    LOG(ERROR) << "Path '" << path << "' is not a directory.";
+    return nullptr;
+  }
+
+  if (path[path.size() - 1] != OS_PATH_SEPARATOR) {
+    path += OS_PATH_SEPARATOR;
+  }
+
+  return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
+                                                                              sb.st_mtime));
+}
+
+std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
+                                                             Asset::AccessMode /* mode */,
+                                                             bool* file_exists) const {
+  const std::string resolved_path = dir_ + path;
+  if (file_exists != nullptr) {
+    struct stat sb{};
+    *file_exists = (stat(resolved_path.c_str(), &sb) != -1) && S_ISREG(sb.st_mode);
+  }
+
+  return CreateAssetFromFile(resolved_path);
+}
+
+bool DirectoryAssetsProvider::ForEachFile(
+    const std::string& /* root_path */,
+    const std::function<void(const StringPiece&, FileType)>& /* f */)
+    const {
+  return true;
+}
+
+const std::string& DirectoryAssetsProvider::GetDebugName() const {
+  return dir_;
+}
+
+bool DirectoryAssetsProvider::IsUpToDate() const {
+  struct stat sb{};
+  if (stat(dir_.c_str(), &sb) < 0) {
+    // If stat fails on the zip archive, return true so the zip archive the resource system does
+    // attempt to refresh the ApkAsset.
+    return true;
+  }
+  return last_mod_time_ == sb.st_mtime;
+}
+
+MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
+                                         std::unique_ptr<AssetsProvider>&& secondary)
+                      : primary_(std::forward<std::unique_ptr<AssetsProvider>>(primary)),
+                        secondary_(std::forward<std::unique_ptr<AssetsProvider>>(secondary)) {
+  if (primary_->GetDebugName() == kEmptyDebugString) {
+    debug_name_ = secondary_->GetDebugName();
+  } else if (secondary_->GetDebugName() == kEmptyDebugString) {
+    debug_name_ = primary_->GetDebugName();
+  } else {
+    debug_name_ = primary_->GetDebugName() + " and " + secondary_->GetDebugName();
+  }
+}
+
+std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create(
+    std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) {
+  if (primary == nullptr || secondary == nullptr) {
+    return nullptr;
+  }
+  return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary),
+                                                                      std::move(secondary)));
+}
+
+std::unique_ptr<Asset> MultiAssetsProvider::OpenInternal(const std::string& path,
+                                                         Asset::AccessMode mode,
+                                                         bool* file_exists) const {
+  auto asset = primary_->Open(path, mode, file_exists);
+  return (asset) ? std::move(asset) : secondary_->Open(path, mode, file_exists);
+}
+
+bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
+                                      const std::function<void(const StringPiece&, FileType)>& f)
+                                      const {
+  return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
+}
+
+const std::string& MultiAssetsProvider::GetDebugName() const {
+  return debug_name_;
+}
+
+bool MultiAssetsProvider::IsUpToDate() const {
+  return primary_->IsUpToDate() && secondary_->IsUpToDate();
+}
+
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create() {
+  return std::make_unique<EmptyAssetsProvider>();
+}
+
+std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
+                                                         Asset::AccessMode /* mode */,
+                                                         bool* file_exists) const {
+  if (file_exists) {
+    *file_exists = false;
+  }
+  return nullptr;
+}
+
+bool EmptyAssetsProvider::ForEachFile(
+    const std::string& /* root_path */,
+    const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+  return true;
+}
+
+const std::string& EmptyAssetsProvider::GetDebugName() const {
+  const static std::string kEmpty = kEmptyDebugString;
+  return kEmpty;
+}
+
+bool EmptyAssetsProvider::IsUpToDate() const {
+  return true;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index adb383f95..f216f55 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -257,8 +257,8 @@
        target_apk_path_(target_apk_path),
        idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
 
-std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
-                                                     const StringPiece& idmap_data) {
+std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
+                                               const StringPiece& idmap_data) {
   ATRACE_CALL();
   size_t data_size = idmap_data.size();
   auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 996b424..2a70f0d 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -767,10 +767,10 @@
   return true;
 }
 
-std::unique_ptr<const LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
-                                                   const size_t length,
-                                                   const LoadedIdmap* loaded_idmap,
-                                                   const package_property_t property_flags) {
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
+                                             const size_t length,
+                                             const LoadedIdmap* loaded_idmap,
+                                             const package_property_t property_flags) {
   ATRACE_NAME("LoadedArsc::Load");
 
   // Not using make_unique because the constructor is private.
@@ -799,11 +799,10 @@
     }
   }
 
-  // Need to force a move for mingw32.
-  return std::move(loaded_arsc);
+  return loaded_arsc;
 }
 
-std::unique_ptr<const LoadedArsc> LoadedArsc::CreateEmpty() {
+std::unique_ptr<LoadedArsc> LoadedArsc::CreateEmpty() {
   return std::unique_ptr<LoadedArsc>(new LoadedArsc());
 }
 
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index e57490a..d0019ed 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -24,104 +24,49 @@
 #include "android-base/unique_fd.h"
 
 #include "androidfw/Asset.h"
+#include "androidfw/AssetsProvider.h"
 #include "androidfw/Idmap.h"
 #include "androidfw/LoadedArsc.h"
 #include "androidfw/misc.h"
 
-struct ZipArchive;
-typedef ZipArchive* ZipArchiveHandle;
-
 namespace android {
 
-class LoadedIdmap;
-
-// Interface for retrieving assets provided by an ApkAssets.
-class AssetsProvider {
- public:
-  virtual ~AssetsProvider() = default;
-
-  // Opens a file for reading.
-  std::unique_ptr<Asset> Open(const std::string& path,
-                              Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM,
-                              bool* file_exists = nullptr) const {
-    return OpenInternal(path, mode, file_exists);
-  }
-
-  // Iterate over all files and directories provided by the zip. The order of iteration is stable.
-  virtual bool ForEachFile(const std::string& /* path */,
-                           const std::function<void(const StringPiece&, FileType)>& /* f */) const {
-    return true;
-  }
-
- protected:
-  AssetsProvider() = default;
-
-  virtual std::unique_ptr<Asset> OpenInternal(const std::string& path,
-                                              Asset::AccessMode mode,
-                                              bool* file_exists) const = 0;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(AssetsProvider);
-};
-
-class ZipAssetsProvider;
-
 // Holds an APK.
 class ApkAssets {
  public:
-  // This means the data extends to the end of the file.
-  static constexpr off64_t kUnknownLength = -1;
 
-  // Creates an ApkAssets.
-  // If `system` is true, the package is marked as a system package, and allows some functions to
-  // filter out this package when computing what configurations/resources are available.
-  static std::unique_ptr<const ApkAssets> Load(
-      const std::string& path, package_property_t flags = 0U,
-      std::unique_ptr<const AssetsProvider> override_asset = nullptr);
+  // Creates an ApkAssets from a path on device.
+  static std::unique_ptr<ApkAssets> Load(const std::string& path,
+                                         package_property_t flags = 0U);
 
-  // Creates an ApkAssets from the given file descriptor, and takes ownership of the file
-  // descriptor. The `friendly_name` is some name that will be used to identify the source of
-  // this ApkAssets in log messages and other debug scenarios.
-  // If `length` equals kUnknownLength, offset must equal 0; otherwise, the apk data will be read
-  // using the `offset` into the file descriptor and will be `length` bytes long.
-  static std::unique_ptr<const ApkAssets> LoadFromFd(
-      base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U,
-      std::unique_ptr<const AssetsProvider> override_asset = nullptr, off64_t offset = 0,
-      off64_t length = kUnknownLength);
+  // Creates an ApkAssets from an open file descriptor.
+  static std::unique_ptr<ApkAssets> LoadFromFd(base::unique_fd fd,
+                                               const std::string& debug_name,
+                                               package_property_t flags = 0U,
+                                               off64_t offset = 0,
+                                               off64_t len = AssetsProvider::kUnknownLength);
 
-  // Creates an ApkAssets from the given path which points to a resources.arsc.
-  static std::unique_ptr<const ApkAssets> LoadTable(
-      const std::string& path, package_property_t flags = 0U,
-      std::unique_ptr<const AssetsProvider> override_asset = nullptr);
+  // Creates an ApkAssets from an AssetProvider.
+  // The ApkAssets will take care of destroying the AssetsProvider when it is destroyed.
+  static std::unique_ptr<ApkAssets> Load(std::unique_ptr<AssetsProvider> assets,
+                                         package_property_t flags = 0U);
 
-  // Creates an ApkAssets from the given file descriptor which points to an resources.arsc, and
-  // takes ownership of the file descriptor.
-  // If `length` equals kUnknownLength, offset must equal 0; otherwise, the .arsc data will be read
-  // using the `offset` into the file descriptor and will be `length` bytes long.
-  static std::unique_ptr<const ApkAssets> LoadTableFromFd(
-      base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U,
-      std::unique_ptr<const AssetsProvider> override_asset = nullptr, off64_t offset = 0,
-      off64_t length = kUnknownLength);
+  // Creates an ApkAssets from the given asset file representing a resources.arsc.
+  static std::unique_ptr<ApkAssets> LoadTable(std::unique_ptr<Asset> resources_asset,
+                                              std::unique_ptr<AssetsProvider> assets,
+                                              package_property_t flags = 0U);
 
   // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
   // data.
-  static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path,
-                                                      package_property_t flags = 0U);
+  static std::unique_ptr<ApkAssets> LoadOverlay(const std::string& idmap_path,
+                                                package_property_t flags = 0U);
 
-  // Creates an ApkAssets from the directory path. File-based resources are read within the
-  // directory as if the directory is an APK.
-  static std::unique_ptr<const ApkAssets> LoadFromDir(
-      const std::string& path, package_property_t flags = 0U,
-      std::unique_ptr<const AssetsProvider> override_asset = nullptr);
-
-  // Creates a totally empty ApkAssets with no resources table and no file entries.
-  static std::unique_ptr<const ApkAssets> LoadEmpty(
-      package_property_t flags = 0U,
-      std::unique_ptr<const AssetsProvider> override_asset = nullptr);
-
-  const std::string& GetPath() const {
-    return path_;
-  }
+  // TODO(177101983): Remove all uses of GetPath for checking whether two ApkAssets are the same.
+  //  With the introduction of ResourcesProviders, not all ApkAssets have paths. This could cause
+  //  bugs when path is used for comparison because multiple ApkAssets could have the same "firendly
+  //  name". Use pointer equality instead. ResourceManager caches and reuses ApkAssets so the
+  //  same asset should have the same pointer.
+  const std::string& GetPath() const;
 
   const AssetsProvider* GetAssetsProvider() const {
     return assets_provider_.get();
@@ -146,53 +91,40 @@
 
   // Returns whether the resources.arsc is allocated in RAM (not mmapped).
   bool IsTableAllocated() const {
-    return resources_asset_ && resources_asset_->isAllocated();
+    return resources_asset_ != nullptr && resources_asset_->isAllocated();
   }
 
   bool IsUpToDate() const;
 
-  // Creates an Asset from a file on disk.
-  static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
-
-  // Creates an Asset from a file descriptor.
-  //
-  // The asset takes ownership of the file descriptor. If `length` equals kUnknownLength, offset
-  // must equal 0; otherwise, the asset data will be read using the `offset` into the file
-  // descriptor and will be `length` bytes long.
-  static std::unique_ptr<Asset> CreateAssetFromFd(base::unique_fd fd,
-                                                  const char* path,
-                                                  off64_t offset = 0,
-                                                  off64_t length = kUnknownLength);
  private:
-  DISALLOW_COPY_AND_ASSIGN(ApkAssets);
+  static std::unique_ptr<ApkAssets> LoadImpl(std::unique_ptr<AssetsProvider> assets,
+                                             package_property_t property_flags,
+                                             std::unique_ptr<Asset> idmap_asset,
+                                             std::unique_ptr<LoadedIdmap> loaded_idmap);
 
-  static std::unique_ptr<const ApkAssets> LoadImpl(
-      std::unique_ptr<const AssetsProvider> assets, const std::string& path,
-      package_property_t property_flags,
-      std::unique_ptr<const AssetsProvider> override_assets = nullptr,
-      std::unique_ptr<Asset> idmap_asset = nullptr,
-      std::unique_ptr<const LoadedIdmap> idmap  = nullptr);
+  static std::unique_ptr<ApkAssets> LoadImpl(std::unique_ptr<Asset> resources_asset,
+                                             std::unique_ptr<AssetsProvider> assets,
+                                             package_property_t property_flags,
+                                             std::unique_ptr<Asset> idmap_asset,
+                                             std::unique_ptr<LoadedIdmap> loaded_idmap);
 
-  static std::unique_ptr<const ApkAssets> LoadTableImpl(
-      std::unique_ptr<Asset> resources_asset, const std::string& path,
-      package_property_t property_flags,
-      std::unique_ptr<const AssetsProvider> override_assets = nullptr);
+  ApkAssets(std::unique_ptr<Asset> resources_asset,
+            std::unique_ptr<LoadedArsc> loaded_arsc,
+            std::unique_ptr<AssetsProvider> assets,
+            package_property_t property_flags,
+            std::unique_ptr<Asset> idmap_asset,
+            std::unique_ptr<LoadedIdmap> loaded_idmap);
 
-  ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
-            std::string path,
-            time_t last_mod_time,
-            package_property_t property_flags);
-
-  std::unique_ptr<const AssetsProvider> assets_provider_;
-  const std::string path_;
-  time_t last_mod_time_;
-  package_property_t property_flags_ = 0U;
   std::unique_ptr<Asset> resources_asset_;
+  std::unique_ptr<LoadedArsc> loaded_arsc_;
+
+  std::unique_ptr<AssetsProvider> assets_provider_;
+  package_property_t property_flags_ = 0U;
+
   std::unique_ptr<Asset> idmap_asset_;
-  std::unique_ptr<const LoadedArsc> loaded_arsc_;
-  std::unique_ptr<const LoadedIdmap> loaded_idmap_;
+  std::unique_ptr<LoadedIdmap> loaded_idmap_;
 };
 
-}  // namespace android
+} // namespace android
 
-#endif /* APKASSETS_H_ */
+#endif // APKASSETS_H_
\ No newline at end of file
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 80bae20..40c91a6 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -167,8 +167,8 @@
 private:
     /* AssetManager needs access to our "create" functions */
     friend class AssetManager;
-    friend class ApkAssets;
-    friend class ZipAssetsProvider;
+    friend struct ZipAssetsProvider;
+    friend struct AssetsProvider;
 
     /*
      * Create the asset from a named file on disk.
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
new file mode 100644
index 0000000..7b06947
--- /dev/null
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROIDFW_ASSETSPROVIDER_H
+#define ANDROIDFW_ASSETSPROVIDER_H
+
+#include <memory>
+#include <string>
+
+#include "android-base/macros.h"
+#include "android-base/unique_fd.h"
+
+#include "androidfw/Asset.h"
+#include "androidfw/Idmap.h"
+#include "androidfw/LoadedArsc.h"
+#include "androidfw/misc.h"
+
+struct ZipArchive;
+
+namespace android {
+
+// Interface responsible for opening and iterating through asset files.
+struct AssetsProvider {
+  static constexpr off64_t kUnknownLength = -1;
+
+  // Opens a file for reading. If `file_exists` is not null, it will be set to `true` if the file
+  // exists. This is useful for determining if the file exists but was unable to be opened due to
+  // an I/O error.
+  std::unique_ptr<Asset> Open(const std::string& path,
+                              Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM,
+                              bool* file_exists = nullptr) const;
+
+  // Iterate over all files and directories provided by the interface. The order of iteration is
+  // stable.
+  virtual bool ForEachFile(const std::string& path,
+                           const std::function<void(const StringPiece&, FileType)>& f) const = 0;
+
+  // Retrieves a name that represents the interface. This may or may not be the path of the
+  // interface source.
+  WARN_UNUSED virtual const std::string& GetDebugName() const = 0;
+
+  // Returns whether the interface provides the most recent version of its files.
+  WARN_UNUSED virtual bool IsUpToDate() const = 0;
+
+  // Creates an Asset from a file on disk.
+  static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+
+  // Creates an Asset from a file descriptor.
+  //
+  // The asset takes ownership of the file descriptor. If `length` equals kUnknownLength, offset
+  // must equal 0; otherwise, the asset data will be read using the `offset` into the file
+  // descriptor and will be `length` bytes long.
+  static std::unique_ptr<Asset> CreateAssetFromFd(base::unique_fd fd,
+                                                  const char* path,
+                                                  off64_t offset = 0,
+                                                  off64_t length = AssetsProvider::kUnknownLength);
+
+  virtual ~AssetsProvider() = default;
+ protected:
+  virtual std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
+                                              bool* file_exists) const = 0;
+};
+
+// Supplies assets from a zip archive.
+struct ZipAssetsProvider : public AssetsProvider {
+  static std::unique_ptr<ZipAssetsProvider> Create(std::string path);
+  static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd,
+                                                   std::string friendly_name,
+                                                   off64_t offset = 0,
+                                                   off64_t len = kUnknownLength);
+
+  bool ForEachFile(const std::string& root_path,
+                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+
+  WARN_UNUSED const std::string& GetDebugName() const override;
+  WARN_UNUSED bool IsUpToDate() const override;
+
+  ~ZipAssetsProvider() override = default;
+ protected:
+  std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
+                                      bool* file_exists) const override;
+
+ private:
+  struct PathOrDebugName;
+  ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, time_t last_mod_time);
+
+  struct PathOrDebugName {
+    PathOrDebugName(std::string&& value, bool is_path);
+
+    // Retrieves the path or null if this class represents a debug name.
+    WARN_UNUSED const std::string* GetPath() const;
+
+    // Retrieves a name that represents the interface. This may or may not represent a path.
+    WARN_UNUSED const std::string& GetDebugName() const;
+
+   private:
+    std::string value_;
+    bool is_path_;
+  };
+
+  std::unique_ptr<ZipArchive, void (*)(ZipArchive*)> zip_handle_;
+  PathOrDebugName name_;
+  time_t last_mod_time_;
+};
+
+// Supplies assets from a root directory.
+struct DirectoryAssetsProvider : public AssetsProvider {
+  static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir);
+
+  bool ForEachFile(const std::string& path,
+                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+
+  WARN_UNUSED const std::string& GetDebugName() const override;
+  WARN_UNUSED bool IsUpToDate() const override;
+
+  ~DirectoryAssetsProvider() override = default;
+ protected:
+  std::unique_ptr<Asset> OpenInternal(const std::string& path,
+                                      Asset::AccessMode mode,
+                                      bool* file_exists) const override;
+
+ private:
+  explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time);
+  std::string dir_;
+  time_t last_mod_time_;
+};
+
+// Supplies assets from a `primary` asset provider and falls back to supplying assets from the
+// `secondary` asset provider if the asset cannot be found in the `primary`.
+struct MultiAssetsProvider : public AssetsProvider {
+  static std::unique_ptr<AssetsProvider> Create(std::unique_ptr<AssetsProvider>&& primary,
+                                                std::unique_ptr<AssetsProvider>&& secondary);
+
+  bool ForEachFile(const std::string& root_path,
+                   const std::function<void(const StringPiece&, FileType)>& f) const override;
+
+  WARN_UNUSED const std::string& GetDebugName() const override;
+  WARN_UNUSED bool IsUpToDate() const override;
+
+  ~MultiAssetsProvider() override = default;
+ protected:
+  std::unique_ptr<Asset> OpenInternal(
+      const std::string& path, Asset::AccessMode mode, bool* file_exists) const override;
+
+ private:
+  MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
+                      std::unique_ptr<AssetsProvider>&& secondary);
+
+  std::unique_ptr<AssetsProvider> primary_;
+  std::unique_ptr<AssetsProvider> secondary_;
+  std::string debug_name_;
+};
+
+// Does not provide any assets.
+struct EmptyAssetsProvider : public AssetsProvider {
+  static std::unique_ptr<AssetsProvider> Create();
+
+  bool ForEachFile(const std::string& path,
+                  const std::function<void(const StringPiece&, FileType)>& f) const override;
+
+  WARN_UNUSED const std::string& GetDebugName() const override;
+  WARN_UNUSED bool IsUpToDate() const override;
+
+  ~EmptyAssetsProvider() override = default;
+ protected:
+  std::unique_ptr<Asset> OpenInternal(const std::string& path, Asset::AccessMode mode,
+                                      bool* file_exists) const override;
+};
+
+}  // namespace android
+
+#endif /* ANDROIDFW_ASSETSPROVIDER_H */
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index fd9a8d13..0ded793 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -149,8 +149,8 @@
 class LoadedIdmap {
  public:
   // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
-  static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_path,
-                                                 const StringPiece& idmap_data);
+  static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path,
+                                           const StringPiece& idmap_data);
 
   // Returns the path to the IDMAP.
   std::string_view IdmapPath() const {
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 891fb90..d9225cd 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -300,17 +300,14 @@
  public:
   // Load a resource table from memory pointed to by `data` of size `len`.
   // The lifetime of `data` must out-live the LoadedArsc returned from this method.
-  // If `system` is set to true, the LoadedArsc is considered as a system provided resource.
-  // If `load_as_shared_library` is set to true, the application package (0x7f) is treated
-  // as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an
-  // ID.
-  static std::unique_ptr<const LoadedArsc> Load(incfs::map_ptr<void> data,
-                                                size_t length,
-                                                const LoadedIdmap* loaded_idmap = nullptr,
-                                                package_property_t property_flags = 0U);
+
+  static std::unique_ptr<LoadedArsc> Load(incfs::map_ptr<void> data,
+                                          size_t length,
+                                          const LoadedIdmap* loaded_idmap = nullptr,
+                                          package_property_t property_flags = 0U);
 
   // Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
-  static std::unique_ptr<const LoadedArsc> CreateEmpty();
+  static std::unique_ptr<LoadedArsc> CreateEmpty();
 
   // Returns the string pool where all string resource values
   // (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 3f0c7cb..b434915 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -27,6 +27,8 @@
 #include "data/overlayable/R.h"
 #include "data/system/R.h"
 
+using ::testing::NotNull;
+
 namespace overlay = com::android::overlay;
 namespace overlayable = com::android::overlayable;
 
@@ -195,7 +197,11 @@
 }
 
 TEST_F(IdmapTest, OverlayLoaderInterop) {
-  auto loader_assets = ApkAssets::LoadTable("loader/resources.arsc", PROPERTY_LOADER);
+  auto asset = AssetsProvider::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+  ASSERT_THAT(asset, NotNull());
+
+  auto loader_assets = ApkAssets::LoadTable(std::move(asset), EmptyAssetsProvider::Create(),
+      PROPERTY_LOADER);
   AssetManager2 asset_manager;
   asset_manager.SetApkAssets({overlayable_assets_.get(), loader_assets.get(),
                               overlay_assets_.get()});
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 9aa3634..f356c8130 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -339,10 +339,8 @@
 }
 
 TEST(LoadedArscTest, LoadCustomLoader) {
-  std::string contents;
-
-  std::unique_ptr<Asset>
-      asset = ApkAssets::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+  auto asset = AssetsProvider::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+  ASSERT_THAT(asset, NotNull());
 
   const StringPiece data(
       reinterpret_cast<const char*>(asset->getBuffer(true /*wordAligned*/)),
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 615bf4d..ce1d96c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -435,6 +435,7 @@
         "canvas/CanvasFrontend.cpp",
         "canvas/CanvasOpBuffer.cpp",
         "canvas/CanvasOpRasterizer.cpp",
+        "effects/StretchEffect.cpp",
         "pipeline/skia/SkiaDisplayList.cpp",
         "pipeline/skia/SkiaRecordingCanvas.cpp",
         "pipeline/skia/RenderNodeDrawable.cpp",
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index ab9b8b5..859a555 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -362,13 +362,8 @@
         return source;
     } else {
         SkBitmap bitmap;
-        const SkImageInfo& info = source.info();
-        bitmap.allocPixels(info.makeColorType(kN32_SkColorType));
-
-        SkCanvas canvas(bitmap);
-        canvas.drawColor(0);
-        canvas.drawBitmap(source, 0.0f, 0.0f, nullptr);
-
+        bitmap.allocPixels(source.info().makeColorType(kN32_SkColorType));
+        bitmap.writePixels(source.pixmap());
         return bitmap;
     }
 }
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index f4c633f..ca2ada9 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -127,10 +127,11 @@
         const SkMatrix& totalMatrix = canvas->getTotalMatrix();
 
         SkRect imageRect = SkRect::MakeIWH(layerImage->width(), layerImage->height());
+        SkSamplingOptions sampling;
         if (getForceFilter() || shouldFilterRect(totalMatrix, imageRect, imageRect)) {
-            paint.setFilterQuality(kLow_SkFilterQuality);
+            sampling = SkSamplingOptions(SkFilterMode::kLinear);
         }
-        canvas->drawImage(layerImage.get(), 0, 0, &paint);
+        canvas->drawImage(layerImage.get(), 0, 0, sampling, &paint);
         // restore the original matrix
         if (nonIdentityMatrix) {
             canvas->restore();
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 65f4e8c..e798f2a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -129,8 +129,9 @@
 
     runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false);
 
-    defaultRenderAhead = std::max(-1, std::min(2, base::GetIntProperty(PROPERTY_RENDERAHEAD,
-            render_ahead().value_or(0))));
+    defaultRenderAhead = std::max(
+            -1,
+            std::min(2, base::GetIntProperty(PROPERTY_RENDERAHEAD, render_ahead().value_or(-1))));
 
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 11a9086..64b8b71 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -487,7 +487,9 @@
         tree->getPaintFor(&paint, tree->stagingProperties());
     }
 
-    void draw(SkCanvas* canvas, const SkMatrix&) const { mRoot->draw(canvas, mBounds, paint); }
+    void draw(SkCanvas* canvas, const SkMatrix&) const {
+        mRoot->draw(canvas, mBounds, paint);
+    }
 
     sp<VectorDrawableRoot> mRoot;
     SkRect mBounds;
@@ -559,7 +561,7 @@
                 return;
             }
             c->concat(invertedMatrix);
-            mLayerSurface->draw(c, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+            mLayerSurface->draw(c, deviceBounds.fLeft, deviceBounds.fTop);
         } else {
             c->drawDrawable(drawable.get());
         }
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index aeb60e6..609706e 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -23,6 +23,7 @@
 #include "Outline.h"
 #include "Rect.h"
 #include "RevealClip.h"
+#include "effects/StretchEffect.h"
 #include "utils/MathUtils.h"
 #include "utils/PaintUtils.h"
 
@@ -98,6 +99,10 @@
 
     SkImageFilter* getImageFilter() const { return mImageFilter.get(); }
 
+    const StretchEffect& getStretchEffect() const { return mStretchEffect; }
+
+    StretchEffect& mutableStretchEffect() { return mStretchEffect; }
+
     // Sets alpha, xfermode, and colorfilter from an SkPaint
     // paint may be NULL, in which case defaults will be set
     bool setFromPaint(const SkPaint* paint);
@@ -124,6 +129,7 @@
     SkBlendMode mMode;
     sk_sp<SkColorFilter> mColorFilter;
     sk_sp<SkImageFilter> mImageFilter;
+    StretchEffect mStretchEffect;
 };
 
 /*
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 1ebc489..8fddf71 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -565,7 +565,8 @@
 void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
     auto image = bitmap.makeImage();
     apply_looper(paint, [&](const SkPaint& p) {
-        mCanvas->drawImage(image, left, top, &p);
+        auto sampling = SkSamplingOptions(p.getFilterQuality());
+        mCanvas->drawImage(image, left, top, sampling, &p);
     });
 }
 
@@ -574,7 +575,8 @@
     SkAutoCanvasRestore acr(mCanvas, true);
     mCanvas->concat(matrix);
     apply_looper(paint, [&](const SkPaint& p) {
-        mCanvas->drawImage(image, 0, 0, &p);
+        auto sampling = SkSamplingOptions(p.getFilterQuality());
+        mCanvas->drawImage(image, 0, 0, sampling, &p);
     });
 }
 
@@ -586,10 +588,17 @@
     SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
 
     apply_looper(paint, [&](const SkPaint& p) {
-        mCanvas->drawImageRect(image, srcRect, dstRect, &p, SkCanvas::kFast_SrcRectConstraint);
+        auto sampling = SkSamplingOptions(p.getFilterQuality());
+        mCanvas->drawImageRect(image, srcRect, dstRect, sampling, &p,
+                               SkCanvas::kFast_SrcRectConstraint);
     });
 }
 
+static SkFilterMode paintToFilter(const Paint* paint) {
+    return paint && paint->isFilterBitmap() ? SkFilterMode::kLinear
+                                            : SkFilterMode::kNearest;
+}
+
 void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
                                 const float* vertices, const int* colors, const Paint* paint) {
     const int ptCount = (meshWidth + 1) * (meshHeight + 1);
@@ -664,18 +673,25 @@
     }
 #endif
 
+    auto image = bitmap.makeImage();
+
     // cons-up a shader for the bitmap
     Paint pnt;
     if (paint) {
         pnt = *paint;
     }
-    SkSamplingOptions sampling(pnt.isFilterBitmap() ? SkFilterMode::kLinear
-                                                    : SkFilterMode::kNearest,
-                               SkMipmapMode::kNone);
-    pnt.setShader(bitmap.makeImage()->makeShader(sampling));
+    SkSamplingOptions sampling(paintToFilter(&pnt));
+    pnt.setShader(image->makeShader(sampling));
+
     auto v = builder.detach();
     apply_looper(&pnt, [&](const SkPaint& p) {
-        mCanvas->drawVertices(v, SkBlendMode::kModulate, p);
+        SkPaint copy(p);
+        auto s = SkSamplingOptions(p.getFilterQuality());
+        if (s != sampling) {
+            // apply_looper changed the quality?
+            copy.setShader(image->makeShader(s));
+        }
+        mCanvas->drawVertices(v, SkBlendMode::kModulate, copy);
     });
 }
 
@@ -704,7 +720,8 @@
     SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
     auto image = bitmap.makeImage();
     apply_looper(paint, [&](const SkPaint& p) {
-        mCanvas->drawImageLattice(image.get(), lattice, dst, &p);
+        auto filter = SkSamplingOptions(p.getFilterQuality()).filter;
+        mCanvas->drawImageLattice(image.get(), lattice, dst, filter, &p);
     });
 }
 
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 6030c36..4a21ad6 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -507,10 +507,12 @@
 
     sk_sp<SkImage> cachedBitmap = getBitmapUpdateIfDirty().makeImage();
 
+    // HWUI always draws VD with bilinear filtering.
+    auto sampling = SkSamplingOptions(SkFilterMode::kLinear);
     int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
     int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
     canvas->drawImageRect(cachedBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
-                           &paint, SkCanvas::kFast_SrcRectConstraint);
+                          sampling, &paint, SkCanvas::kFast_SrcRectConstraint);
 }
 
 void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {
diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h
index fa0c45b..86b1ac7 100644
--- a/libs/hwui/canvas/CanvasOps.h
+++ b/libs/hwui/canvas/CanvasOps.h
@@ -351,21 +351,24 @@
         const sk_sp<Bitmap>& bitmap,
         float left,
         float top,
+        SkFilterMode filter,
         SkPaint paint
     ) : left(left),
         top(top),
+        filter(filter),
         paint(std::move(paint)),
         bitmap(bitmap),
         image(bitmap->makeImage()) { }
 
     float left;
     float top;
+    SkFilterMode filter;
     SkPaint paint;
     sk_sp<Bitmap> bitmap;
     sk_sp<SkImage> image;
 
     void draw(SkCanvas* canvas) const {
-        canvas->drawImage(image, left, top, &paint);
+        canvas->drawImage(image, left, top, SkSamplingOptions(filter), &paint);
     }
     ASSERT_DRAWABLE()
 };
@@ -377,15 +380,18 @@
         const sk_sp<Bitmap>& bitmap,
         SkRect src,
         SkRect dst,
+        SkFilterMode filter,
         SkPaint paint
     ) : src(src),
         dst(dst),
+        filter(filter),
         paint(std::move(paint)),
         bitmap(bitmap),
         image(bitmap->makeImage()) { }
 
     SkRect src;
     SkRect dst;
+    SkFilterMode filter;
     SkPaint paint;
     sk_sp<Bitmap> bitmap;
     sk_sp<SkImage> image;
@@ -394,6 +400,7 @@
         canvas->drawImageRect(image,
                 src,
                 dst,
+                SkSamplingOptions(filter),
                 &paint,
                 SkCanvas::kFast_SrcRectConstraint
         );
@@ -408,21 +415,24 @@
         const sk_sp<Bitmap>& bitmap,
         SkRect dst,
         SkCanvas::Lattice lattice,
+        SkFilterMode filter,
         SkPaint  paint
     ):  dst(dst),
         lattice(lattice),
+        filter(filter),
         bitmap(bitmap),
         image(bitmap->makeImage()),
         paint(std::move(paint)) {}
 
     SkRect dst;
     SkCanvas::Lattice lattice;
+    SkFilterMode filter;
     const sk_sp<Bitmap> bitmap;
     const sk_sp<SkImage> image;
 
     SkPaint paint;
     void draw(SkCanvas* canvas) const {
-        canvas->drawImageLattice(image.get(), lattice, dst, &paint);
+        canvas->drawImageLattice(image.get(), lattice, dst, filter, &paint);
     }
     ASSERT_DRAWABLE()
 };
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/libs/hwui/effects/StretchEffect.cpp
similarity index 69%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to libs/hwui/effects/StretchEffect.cpp
index 8b5b251..51cbc75 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/libs/hwui/effects/StretchEffect.cpp
@@ -14,9 +14,14 @@
  * limitations under the License.
  */
 
-/**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
- */
-@android.annotation.Hide
-package com.android.role;
+#include "StretchEffect.h"
+
+namespace android::uirenderer {
+
+sk_sp<SkImageFilter> StretchEffect::getImageFilter() const {
+    // TODO: Implement & Cache
+    // Probably need to use mutable to achieve caching
+    return nullptr;
+}
+
+} // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/effects/StretchEffect.h b/libs/hwui/effects/StretchEffect.h
new file mode 100644
index 0000000..7dfd639
--- /dev/null
+++ b/libs/hwui/effects/StretchEffect.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#pragma once
+
+#include "utils/MathUtils.h"
+
+#include <SkPoint.h>
+#include <SkRect.h>
+#include <SkImageFilter.h>
+
+namespace android::uirenderer {
+
+// TODO: Inherit from base RenderEffect type?
+class StretchEffect {
+public:
+    enum class StretchInterpolator {
+        SmoothStep,
+    };
+
+    bool isEmpty() const {
+        return MathUtils::isZero(stretchDirection.x())
+                && MathUtils::isZero(stretchDirection.y());
+    }
+
+    void setEmpty() {
+        *this = StretchEffect{};
+    }
+
+    void mergeWith(const StretchEffect& other) {
+        if (other.isEmpty()) {
+            return;
+        }
+        if (isEmpty()) {
+            *this = other;
+            return;
+        }
+        stretchDirection += other.stretchDirection;
+        if (isEmpty()) {
+            return setEmpty();
+        }
+        stretchArea.join(other.stretchArea);
+        maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
+    }
+
+    sk_sp<SkImageFilter> getImageFilter() const;
+
+    SkRect stretchArea {0, 0, 0, 0};
+    SkVector stretchDirection {0, 0};
+    float maxStretchAmount = 0;
+};
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 638de85..0d3d3e3 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -20,6 +20,7 @@
 #endif
 
 #include "utils/TraceUtils.h"
+#include "pipeline/skia/SkiaUtils.h"
 
 #include <SkPicture.h>
 #include <SkRefCnt.h>
@@ -31,6 +32,7 @@
 AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed)
         : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) {
     mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration());
+    setStagingBounds(mSkAnimatedImage->getBounds());
 }
 
 void AnimatedImageDrawable::syncProperties() {
@@ -127,21 +129,38 @@
     return snap;
 }
 
+// Update the matrix to map from the intrinsic bounds of the SkAnimatedImage to
+// the bounds specified by Drawable#setBounds.
+static void handleBounds(SkMatrix* matrix, const SkRect& intrinsicBounds, const SkRect& bounds) {
+    matrix->preTranslate(bounds.left(), bounds.top());
+    matrix->preScale(bounds.width()  / intrinsicBounds.width(),
+                     bounds.height() / intrinsicBounds.height());
+}
+
 // Only called on the RenderThread.
 void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
+    // Store the matrix used to handle bounds and mirroring separate from the
+    // canvas. We may need to invert the matrix to determine the proper bounds
+    // to pass to saveLayer, and this matrix (as opposed to, potentially, the
+    // canvas' matrix) only uses scale and translate, so it must be invertible.
+    SkMatrix matrix;
+    SkAutoCanvasRestore acr(canvas, true);
+    handleBounds(&matrix, mSkAnimatedImage->getBounds(), mProperties.mBounds);
+
+    if (mProperties.mMirrored) {
+        matrix.preTranslate(mSkAnimatedImage->getBounds().width(), 0);
+        matrix.preScale(-1, 1);
+    }
+
     std::optional<SkPaint> lazyPaint;
-    SkAutoCanvasRestore acr(canvas, false);
     if (mProperties.mAlpha != SK_AlphaOPAQUE || mProperties.mColorFilter.get()) {
         lazyPaint.emplace();
         lazyPaint->setAlpha(mProperties.mAlpha);
         lazyPaint->setColorFilter(mProperties.mColorFilter);
         lazyPaint->setFilterQuality(kLow_SkFilterQuality);
     }
-    if (mProperties.mMirrored) {
-        canvas->save();
-        canvas->translate(mSkAnimatedImage->getBounds().width(), 0);
-        canvas->scale(-1, 1);
-    }
+
+    canvas->concat(matrix);
 
     const bool starting = mStarting;
     mStarting = false;
@@ -151,7 +170,11 @@
         // The image is not animating, and never was. Draw directly from
         // mSkAnimatedImage.
         if (lazyPaint) {
-            canvas->saveLayer(mSkAnimatedImage->getBounds(), &*lazyPaint);
+            SkMatrix inverse;
+            (void) matrix.invert(&inverse);
+            SkRect r = mProperties.mBounds;
+            inverse.mapRect(&r);
+            canvas->saveLayer(r, &*lazyPaint);
         }
 
         std::unique_lock lock{mImageLock};
@@ -211,17 +234,31 @@
 }
 
 int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
-    SkAutoCanvasRestore acr(canvas, false);
+    // Store the matrix used to handle bounds and mirroring separate from the
+    // canvas. We may need to invert the matrix to determine the proper bounds
+    // to pass to saveLayer, and this matrix (as opposed to, potentially, the
+    // canvas' matrix) only uses scale and translate, so it must be invertible.
+    SkMatrix matrix;
+    SkAutoCanvasRestore acr(canvas, true);
+    handleBounds(&matrix, mSkAnimatedImage->getBounds(), mStagingProperties.mBounds);
+
+    if (mStagingProperties.mMirrored) {
+        matrix.preTranslate(mSkAnimatedImage->getBounds().width(), 0);
+        matrix.preScale(-1, 1);
+    }
+
+    canvas->concat(matrix);
+
     if (mStagingProperties.mAlpha != SK_AlphaOPAQUE || mStagingProperties.mColorFilter.get()) {
         SkPaint paint;
         paint.setAlpha(mStagingProperties.mAlpha);
         paint.setColorFilter(mStagingProperties.mColorFilter);
-        canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint);
-    }
-    if (mStagingProperties.mMirrored) {
-        canvas->save();
-        canvas->translate(mSkAnimatedImage->getBounds().width(), 0);
-        canvas->scale(-1, 1);
+
+        SkMatrix inverse;
+        (void) matrix.invert(&inverse);
+        SkRect r = mStagingProperties.mBounds;
+        inverse.mapRect(&r);
+        canvas->saveLayer(r, &paint);
     }
 
     if (!mRunning) {
@@ -294,4 +331,10 @@
     return ns2ms(mTimeToShowNextSnapshot - mCurrentTime);
 }
 
+SkRect AnimatedImageDrawable::onGetBounds() {
+    // This must return a bounds that is valid for all possible states,
+    // including if e.g. the client calls setBounds.
+    return SkRectMakeLargest();
+}
+
 }  // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index f81a5a4..8ca3c7e 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -67,9 +67,10 @@
         mStagingProperties.mColorFilter = filter;
     }
     void setStagingMirrored(bool mirrored) { mStagingProperties.mMirrored = mirrored; }
+    void setStagingBounds(const SkRect& bounds) { mStagingProperties.mBounds = bounds; }
     void syncProperties();
 
-    virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); }
+    SkRect onGetBounds() override;
 
     // Draw to software canvas, and return time to next draw.
     // 0 means the animation is not running.
@@ -109,7 +110,7 @@
     size_t byteSize() const { return sizeof(*this) + mBytesUsed; }
 
 protected:
-    virtual void onDraw(SkCanvas* canvas) override;
+    void onDraw(SkCanvas* canvas) override;
 
 private:
     sk_sp<SkAnimatedImage> mSkAnimatedImage;
@@ -145,6 +146,7 @@
         int mAlpha = SK_AlphaOPAQUE;
         sk_sp<SkColorFilter> mColorFilter;
         bool mMirrored = false;
+        SkRect mBounds;
 
         Properties() = default;
         Properties(Properties&) = default;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index f055c6e..ade63e5 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -437,11 +437,11 @@
             if (outputMatrix.invert(&inverse)) {
                 SkCanvas canvas(tmp, SkCanvas::ColorBehavior::kLegacy);
                 canvas.setMatrix(inverse);
-                SkPaint paint;
-                paint.setFilterQuality(kLow_SkFilterQuality); // bilinear
                 SkBitmap priorFrame;
                 priorFrame.installPixels(outputInfo, pixels, rowBytes);
-                canvas.drawBitmap(priorFrame, 0, 0, &paint);
+                priorFrame.setImmutable(); // Don't want asImage() to force a copy
+                canvas.drawImage(priorFrame.asImage(), 0, 0,
+                                 SkSamplingOptions(SkFilterMode::kLinear));
             } else {
                 ALOGE("Failed to invert matrix!");
             }
@@ -458,11 +458,11 @@
 
         SkPaint paint;
         paint.setBlendMode(SkBlendMode::kSrc);
-        paint.setFilterQuality(kLow_SkFilterQuality);  // bilinear filtering
 
         SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
         canvas.setMatrix(outputMatrix);
-        canvas.drawBitmap(tmp, 0.0f, 0.0f, &paint);
+        tmp.setImmutable(); // Don't want asImage() to force copy
+        canvas.drawImage(tmp.asImage(), 0, 0, SkSamplingOptions(SkFilterMode::kLinear), &paint);
     }
 
     return result;
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index 1ff1565..c9433ec8 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -244,6 +244,14 @@
     drawable->setStagingMirrored(mirrored);
 }
 
+static void AnimatedImageDrawable_nSetBounds(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                             jobject jrect) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    SkRect rect;
+    GraphicsJNI::jrect_to_rect(env, jrect, &rect);
+    drawable->setStagingBounds(rect);
+}
+
 static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
     { "nCreate",             "(JLandroid/graphics/ImageDecoder;IIJZLandroid/graphics/Rect;)J",(void*) AnimatedImageDrawable_nCreate },
     { "nGetNativeFinalizer", "()J",                                                          (void*) AnimatedImageDrawable_nGetNativeFinalizer },
@@ -259,6 +267,7 @@
     { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener },
     { "nNativeByteSize",     "(J)J",                                                         (void*) AnimatedImageDrawable_nNativeByteSize },
     { "nSetMirrored",        "(JZ)V",                                                        (void*) AnimatedImageDrawable_nSetMirrored },
+    { "nSetBounds",          "(JLandroid/graphics/Rect;)V",                                  (void*) AnimatedImageDrawable_nSetBounds },
 };
 
 int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index cf02051..4e9daa4 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -457,11 +457,12 @@
         // outputBitmap.  Otherwise we would blend by default, which is not
         // what we want.
         paint.setBlendMode(SkBlendMode::kSrc);
-        paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
 
         SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
         canvas.scale(scaleX, scaleY);
-        canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
+        decodingBitmap.setImmutable(); // so .asImage() doesn't make a copy
+        canvas.drawImage(decodingBitmap.asImage(), 0.0f, 0.0f,
+                         SkSamplingOptions(SkFilterMode::kLinear), &paint);
     } else {
         outputBitmap.swap(decodingBitmap);
     }
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 10c8077..8f455fe 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -25,12 +25,17 @@
 #include <hwui/Typeface.h>
 #include <minikin/FontCollection.h>
 #include <minikin/FontFamily.h>
+#include <minikin/FontFileParser.h>
 #include <minikin/SystemFonts.h>
 #include <utils/TraceUtils.h>
 
 #include <mutex>
 #include <unordered_map>
 
+#ifdef __ANDROID__
+#include <sys/stat.h>
+#endif
+
 using namespace android;
 using android::uirenderer::TraceUtils;
 
@@ -155,12 +160,43 @@
                                            toTypeface(ptr)->fFontCollection);
 }
 
-static sk_sp<SkData> makeSkDataCached(const std::string& path) {
+#ifdef __ANDROID__
+
+static bool getVerity(const std::string& path) {
+    struct statx out = {};
+    if (statx(AT_FDCWD, path.c_str(), 0 /* flags */, STATX_ALL, &out) != 0) {
+        ALOGE("statx failed for %s, errno = %d", path.c_str(), errno);
+        return false;
+    }
+
+    // Validity check.
+    if ((out.stx_attributes_mask & STATX_ATTR_VERITY) == 0) {
+        // STATX_ATTR_VERITY not supported by kernel.
+        return false;
+    }
+
+    return (out.stx_attributes & STATX_ATTR_VERITY) != 0;
+}
+
+#else
+
+static bool getVerity(const std::string&) {
+    // verity check is not enabled on desktop.
+    return false;
+}
+
+#endif  // __ANDROID__
+
+static sk_sp<SkData> makeSkDataCached(const std::string& path, bool hasVerity) {
     // We don't clear cache as Typeface objects created by Typeface_readTypefaces() will be stored
     // in a static field and will not be garbage collected.
     static std::unordered_map<std::string, sk_sp<SkData>> cache;
     static std::mutex mutex;
     ALOG_ASSERT(!path.empty());
+    if (hasVerity && !getVerity(path)) {
+        LOG_ALWAYS_FATAL("verity bit was removed from %s", path.c_str());
+        return nullptr;
+    }
     std::lock_guard lock{mutex};
     sk_sp<SkData>& entry = cache[path];
     if (entry.get() == nullptr) {
@@ -171,15 +207,34 @@
 
 static std::function<std::shared_ptr<minikin::MinikinFont>()> readMinikinFontSkia(
         minikin::BufferReader* reader) {
-    std::string_view fontPath = reader->readString();
-    int fontIndex = reader->read<int>();
-    const minikin::FontVariation* axesPtr;
-    uint32_t axesCount;
-    std::tie(axesPtr, axesCount) = reader->readArray<minikin::FontVariation>();
-    return [fontPath, fontIndex, axesPtr, axesCount]() -> std::shared_ptr<minikin::MinikinFont> {
+    const void* buffer = reader->data();
+    size_t pos = reader->pos();
+    // Advance reader's position.
+    reader->skipString(); // fontPath
+    reader->skip<int>(); // fontIndex
+    reader->skipArray<minikin::FontVariation>(); // axesPtr, axesCount
+    bool hasVerity = static_cast<bool>(reader->read<int8_t>());
+    if (hasVerity) {
+        reader->skip<uint32_t>(); // expectedFontRevision
+        reader->skipString(); // expectedPostScriptName
+    }
+    return [buffer, pos]() -> std::shared_ptr<minikin::MinikinFont> {
+        minikin::BufferReader fontReader(buffer, pos);
+        std::string_view fontPath = fontReader.readString();
         std::string path(fontPath.data(), fontPath.size());
         ATRACE_FORMAT("Loading font %s", path.c_str());
-        sk_sp<SkData> data = makeSkDataCached(path);
+        int fontIndex = fontReader.read<int>();
+        const minikin::FontVariation* axesPtr;
+        uint32_t axesCount;
+        std::tie(axesPtr, axesCount) = fontReader.readArray<minikin::FontVariation>();
+        bool hasVerity = static_cast<bool>(fontReader.read<int8_t>());
+        uint32_t expectedFontRevision;
+        std::string_view expectedPostScriptName;
+        if (hasVerity) {
+            expectedFontRevision = fontReader.read<uint32_t>();
+            expectedPostScriptName = fontReader.readString();
+        }
+        sk_sp<SkData> data = makeSkDataCached(path, hasVerity);
         if (data.get() == nullptr) {
             // This may happen if:
             // 1. When the process failed to open the file (e.g. invalid path or permission).
@@ -189,6 +244,20 @@
         }
         const void* fontPtr = data->data();
         size_t fontSize = data->size();
+        if (hasVerity) {
+            // Verify font metadata if verity is enabled.
+            minikin::FontFileParser parser(fontPtr, fontSize, fontIndex);
+            std::optional<uint32_t> revision = parser.getFontRevision();
+            if (!revision.has_value() || revision.value() != expectedFontRevision) {
+              LOG_ALWAYS_FATAL("Wrong font revision: %s", path.c_str());
+              return nullptr;
+            }
+            std::optional<std::string> psName = parser.getPostScriptName();
+            if (!psName.has_value() || psName.value() != expectedPostScriptName) {
+              LOG_ALWAYS_FATAL("Wrong PostScript name: %s", path.c_str());
+              return nullptr;
+            }
+        }
         std::vector<minikin::FontVariation> axes(axesPtr, axesPtr + axesCount);
         std::shared_ptr<minikin::MinikinFont> minikinFont =
                 fonts::createMinikinFontSkia(std::move(data), fontPath, fontPtr, fontSize,
@@ -203,10 +272,24 @@
 
 static void writeMinikinFontSkia(minikin::BufferWriter* writer,
         const minikin::MinikinFont* typeface) {
-    writer->writeString(typeface->GetFontPath());
+    const std::string& path = typeface->GetFontPath();
+    writer->writeString(path);
     writer->write<int>(typeface->GetFontIndex());
     const std::vector<minikin::FontVariation>& axes = typeface->GetAxes();
     writer->writeArray<minikin::FontVariation>(axes.data(), axes.size());
+    bool hasVerity = getVerity(path);
+    writer->write<int8_t>(static_cast<int8_t>(hasVerity));
+    if (hasVerity) {
+        // Write font metadata for verification only when verity is enabled.
+        minikin::FontFileParser parser(typeface->GetFontData(), typeface->GetFontSize(),
+                                       typeface->GetFontIndex());
+        std::optional<uint32_t> revision = parser.getFontRevision();
+        LOG_ALWAYS_FATAL_IF(!revision.has_value());
+        writer->write<uint32_t>(revision.value());
+        std::optional<std::string> psName = parser.getPostScriptName();
+        LOG_ALWAYS_FATAL_IF(!psName.has_value());
+        writer->writeString(psName.value());
+    }
 }
 
 static jint Typeface_writeTypefaces(JNIEnv *env, jobject, jobject buffer, jlongArray faceHandles) {
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 8b35d96..8023968 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -166,6 +166,31 @@
     return true;
 }
 
+static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    auto& stretch = renderNode->mutateStagingProperties()
+            .mutateLayerProperties().mutableStretchEffect();
+    if (stretch.isEmpty()) {
+        return false;
+    }
+    stretch.setEmpty();
+    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
+}
+
+static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
+        jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
+            StretchEffect{
+        .stretchArea = SkRect::MakeLTRB(left, top, right, bottom),
+        .stretchDirection = {.fX = vX, .fY = vY},
+        .maxStretchAmount = max
+    });
+    renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
+    return true;
+}
+
 static jboolean android_view_RenderNode_hasShadow(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     return renderNode->stagingProperties().hasShadow();
@@ -678,6 +703,8 @@
     { "nSetOutlinePath",       "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath },
     { "nSetOutlineEmpty",      "(J)Z",   (void*) android_view_RenderNode_setOutlineEmpty },
     { "nSetOutlineNone",       "(J)Z",   (void*) android_view_RenderNode_setOutlineNone },
+    { "nClearStretch",         "(J)Z",   (void*) android_view_RenderNode_clearStretch },
+    { "nStretch",              "(JFFFFFFF)Z",   (void*) android_view_RenderNode_stretch },
     { "nHasShadow",            "(J)Z",   (void*) android_view_RenderNode_hasShadow },
     { "nSetSpotShadowColor",   "(JI)Z",  (void*) android_view_RenderNode_setSpotShadowColor },
     { "nGetSpotShadowColor",   "(J)I",   (void*) android_view_RenderNode_getSpotShadowColor },
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index b944310..b769d40 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -79,7 +79,8 @@
 
 // Regular JNI
 static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jobject buffer,
-        jstring filePath, jint weight, jboolean italic, jint ttcIndex) {
+                                jstring filePath, jstring langTags, jint weight, jboolean italic,
+                                jint ttcIndex) {
     NPE_CHECK_RETURN_ZERO(env, buffer);
     std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
     const void* fontPtr = env->GetDirectBufferAddress(buffer);
@@ -94,6 +95,7 @@
         return 0;
     }
     ScopedUtfChars fontPath(env, filePath);
+    ScopedUtfChars langTagStr(env, langTags);
     jobject fontRef = MakeGlobalRefOrDie(env, buffer);
     sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
             release_global_ref, reinterpret_cast<void*>(fontRef)));
@@ -105,8 +107,13 @@
                           "Failed to create internal object. maybe invalid font data.");
         return 0;
     }
-    std::shared_ptr<minikin::Font> font = minikin::Font::Builder(minikinFont).setWeight(weight)
-                    .setSlant(static_cast<minikin::FontStyle::Slant>(italic)).build();
+    uint32_t localeListId = minikin::registerLocaleList(langTagStr.c_str());
+    std::shared_ptr<minikin::Font> font =
+            minikin::Font::Builder(minikinFont)
+                    .setWeight(weight)
+                    .setSlant(static_cast<minikin::FontStyle::Slant>(italic))
+                    .setLocaleListId(localeListId)
+                    .build();
     return reinterpret_cast<jlong>(new FontWrapper(std::move(font)));
 }
 
@@ -302,11 +309,12 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gFontBuilderMethods[] = {
-    { "nInitBuilder", "()J", (void*) Font_Builder_initBuilder },
-    { "nAddAxis", "(JIF)V", (void*) Font_Builder_addAxis },
-    { "nBuild", "(JLjava/nio/ByteBuffer;Ljava/lang/String;IZI)J", (void*) Font_Builder_build },
-    { "nClone", "(JJIZI)J", (void*) Font_Builder_clone },
-    { "nGetReleaseNativeFont", "()J", (void*) Font_Builder_getReleaseNativeFont },
+        {"nInitBuilder", "()J", (void*)Font_Builder_initBuilder},
+        {"nAddAxis", "(JIF)V", (void*)Font_Builder_addAxis},
+        {"nBuild", "(JLjava/nio/ByteBuffer;Ljava/lang/String;Ljava/lang/String;IZI)J",
+         (void*)Font_Builder_build},
+        {"nClone", "(JJIZI)J", (void*)Font_Builder_clone},
+        {"nGetReleaseNativeFont", "()J", (void*)Font_Builder_getReleaseNativeFont},
 };
 
 static const JNINativeMethod gFontMethods[] = {
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index 37e5276..8fe6da318 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -83,6 +83,23 @@
     return reinterpret_cast<jlong>(releaseFontFamily);
 }
 
+// FastNative
+static jstring FontFamily_getLangTags(JNIEnv* env, jobject, jlong familyPtr) {
+    FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr);
+    uint32_t localeListId = family->family->localeListId();
+    if (localeListId == 0) {
+        return nullptr;
+    }
+    std::string langTags = minikin::getLocaleString(localeListId);
+    return env->NewStringUTF(langTags.c_str());
+}
+
+// CriticalNative
+static jint FontFamily_getVariant(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr) {
+    FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr);
+    return static_cast<jint>(family->family->variant());
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gFontFamilyBuilderMethods[] = {
@@ -93,9 +110,16 @@
     { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc },
 };
 
+static const JNINativeMethod gFontFamilyMethods[] = {
+        {"nGetLangTags", "(J)Ljava/lang/String;", (void*)FontFamily_getLangTags},
+        {"nGetVariant", "(J)I", (void*)FontFamily_getVariant},
+};
+
 int register_android_graphics_fonts_FontFamily(JNIEnv* env) {
     return RegisterMethodsOrDie(env, "android/graphics/fonts/FontFamily$Builder",
-            gFontFamilyBuilderMethods, NELEM(gFontFamilyBuilderMethods));
+                                gFontFamilyBuilderMethods, NELEM(gFontFamilyBuilderMethods)) +
+           RegisterMethodsOrDie(env, "android/graphics/fonts/FontFamily", gFontFamilyMethods,
+                                NELEM(gFontFamilyMethods));
 }
 
 }
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index c6c9e9d..71f533c 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -194,7 +194,7 @@
         canvas->concat(invertedMatrix);
 
         const SkIRect deviceBounds = canvas->getDeviceClipBounds();
-        tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop, nullptr);
+        tmpSurface->draw(canvas, deviceBounds.fLeft, deviceBounds.fTop);
     }
 }
 
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index f95f347..34df5dd 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -141,18 +141,20 @@
             // then use nearest neighbor, otherwise use bilerp sampling.
             // Skia TextureOp has the above logic build-in, but not NonAAFillRectOp. TextureOp works
             // only for SrcOver blending and without color filter (readback uses Src blending).
+            SkSamplingOptions sampling(SkFilterMode::kNearest);
             if (layer->getForceFilter() ||
                 shouldFilterRect(totalMatrix, skiaSrcRect, skiaDestRect)) {
-                paint.setFilterQuality(kLow_SkFilterQuality);
+                sampling = SkSamplingOptions(SkFilterMode::kLinear);
             }
-            canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, &paint,
+            canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
                                   SkCanvas::kFast_SrcRectConstraint);
         } else {
             SkRect imageRect = SkRect::MakeIWH(layerImage->width(), layerImage->height());
+            SkSamplingOptions sampling(SkFilterMode::kNearest);
             if (layer->getForceFilter() || shouldFilterRect(totalMatrix, imageRect, imageRect)) {
-                paint.setFilterQuality(kLow_SkFilterQuality);
+                sampling = SkSamplingOptions(SkFilterMode::kLinear);
             }
-            canvas->drawImage(layerImage.get(), 0, 0, &paint);
+            canvas->drawImage(layerImage.get(), 0, 0, sampling, &paint);
         }
         // restore the original matrix
         if (nonIdentityMatrix) {
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 070a765..c010212 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -20,6 +20,8 @@
 #include "SkiaDisplayList.h"
 #include "utils/TraceUtils.h"
 
+#include <include/effects/SkImageFilters.h>
+
 #include <optional>
 
 namespace android {
@@ -169,14 +171,27 @@
 
 static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultiplier,
                             SkPaint* paint) {
-    paint->setFilterQuality(kLow_SkFilterQuality);
     if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
         properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
-        properties.getImageFilter() != nullptr) {
+        properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) {
         paint->setAlpha(properties.alpha() * alphaMultiplier);
         paint->setBlendMode(properties.xferMode());
         paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
-        paint->setImageFilter(sk_ref_sp(properties.getImageFilter()));
+
+        sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
+        sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter();
+        sk_sp<SkImageFilter> filter;
+        if (imageFilter && stretchFilter) {
+            filter = SkImageFilters::Compose(
+                  std::move(stretchFilter),
+                  std::move(imageFilter)
+            );
+        } else if (stretchFilter) {
+            filter = std::move(stretchFilter);
+        } else {
+            filter = std::move(imageFilter);
+        }
+        paint->setImageFilter(std::move(filter));
         return true;
     }
     return false;
@@ -226,6 +241,7 @@
             SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
             SkPaint paint;
             layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
+            SkSamplingOptions sampling(SkFilterMode::kLinear);
 
             // surfaces for layers are created on LAYER_SIZE boundaries (which are >= layer size) so
             // we need to restrict the portion of the surface drawn to the size of the renderNode.
@@ -239,7 +255,7 @@
                     "SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr);
             }
             canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds,
-                                  bounds, &paint);
+                                  bounds, sampling, &paint, SkCanvas::kStrict_SrcRectConstraint);
 
             if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) {
                 renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 80eddaf..6456e36 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -656,7 +656,7 @@
     SkPaint paint;
     const SkColor* colors = kOverdrawColors[static_cast<int>(Properties::overdrawColorSet)];
     paint.setColorFilter(SkOverdrawColorFilter::MakeWithSkColors(colors));
-    surface->getCanvas()->drawImage(counts.get(), 0.0f, 0.0f, &paint);
+    surface->getCanvas()->drawImage(counts.get(), 0.0f, 0.0f, SkSamplingOptions(), &paint);
 }
 
 } /* namespace skiapipeline */
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index bc8ce42..bae11f7 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -182,7 +182,7 @@
     auto functorImage = SkImage::MakeFromAHardwareBuffer(mFrameBuffer.get(), kPremul_SkAlphaType,
                                                          canvas->imageInfo().refColorSpace(),
                                                          kBottomLeft_GrSurfaceOrigin);
-    canvas->drawImage(functorImage, 0, 0, &paint);
+    canvas->drawImage(functorImage, 0, 0, SkSamplingOptions(), &paint);
     canvas->restore();
 }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index eacabfd..37a6ee7 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -157,12 +157,14 @@
 void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
     ATRACE_CALL();
 
-    if (mRenderAheadDepth == 0 && DeviceInfo::get()->getMaxRefreshRate() > 66.6f) {
-        mFixedRenderAhead = false;
-        mRenderAheadCapacity = 1;
-    } else {
-        mFixedRenderAhead = true;
+    if (mFixedRenderAhead) {
         mRenderAheadCapacity = mRenderAheadDepth;
+    } else {
+        if (DeviceInfo::get()->getMaxRefreshRate() > 66.6f) {
+            mRenderAheadCapacity = 1;
+        } else {
+            mRenderAheadCapacity = 0;
+        }
     }
 
     if (window) {
@@ -490,9 +492,11 @@
 
     if (mNativeSurface) {
         // TODO(b/165985262): measure performance impact
-        if (const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
-                vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
-            native_window_set_frame_timeline_vsync(mNativeSurface->getNativeWindow(), vsyncId);
+        const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+        if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
+            const auto inputEventId = mCurrentFrameInfo->get(FrameInfoIndex::NewestInputEvent);
+            native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), vsyncId,
+                                                  inputEventId);
         }
     }
 
@@ -762,11 +766,16 @@
 }
 
 void CanvasContext::setRenderAheadDepth(int renderAhead) {
-    if (renderAhead > 2 || renderAhead < 0 || mNativeSurface) {
+    if (renderAhead > 2 || renderAhead < -1 || mNativeSurface) {
         return;
     }
-    mFixedRenderAhead = true;
-    mRenderAheadDepth = static_cast<uint32_t>(renderAhead);
+    if (renderAhead == -1) {
+        mFixedRenderAhead = false;
+        mRenderAheadDepth = 0;
+    } else {
+        mFixedRenderAhead = true;
+        mRenderAheadDepth = static_cast<uint32_t>(renderAhead);
+    }
 }
 
 SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) {
diff --git a/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp b/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp
index 1d17a02..716d397 100644
--- a/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp
+++ b/libs/hwui/tests/common/scenes/ReadbackFromHardwareBitmap.cpp
@@ -51,7 +51,7 @@
                                                          hardwareBitmap->height(), &canvasBitmap));
 
         SkCanvas skCanvas(canvasBitmap);
-        skCanvas.drawBitmap(readback, 0, 0);
+        skCanvas.drawImage(readback.asImage(), 0, 0);
         canvas.drawBitmap(*heapBitmap, 0, 0, nullptr);
 
         canvas.drawBitmap(*hardwareBitmap, 0, 500, nullptr);
diff --git a/libs/hwui/tests/microbench/CanvasOpBench.cpp b/libs/hwui/tests/microbench/CanvasOpBench.cpp
index ef5749e..e7ba471 100644
--- a/libs/hwui/tests/microbench/CanvasOpBench.cpp
+++ b/libs/hwui/tests/microbench/CanvasOpBench.cpp
@@ -85,6 +85,7 @@
                     iconBitmap,
                     0,
                     0,
+                    SkFilterMode::kNearest,
                     SkPaint{}
             });
             canvas.restore();
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index a09e742..54970df 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -474,6 +474,7 @@
             bitmap,
             7,
             19,
+            SkFilterMode::kNearest,
             SkPaint{}
         }
     );
@@ -496,7 +497,7 @@
     buffer.push<Op::DrawImageRect> ({
           bitmap, SkRect::MakeWH(100, 100),
           SkRect::MakeLTRB(120, 110, 220, 210),
-          SkPaint{}
+          SkFilterMode::kNearest, SkPaint{}
         }
     );
 
@@ -528,6 +529,7 @@
             bitmap,
             SkRect::MakeWH(5, 1),
             lattice,
+            SkFilterMode::kNearest,
             SkPaint{}
         }
     );
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index 3d188c0..3dcaffb 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -31,6 +31,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.os.Parcel;
@@ -1379,10 +1380,9 @@
      * Gets the Automatic Gain Control level in dB.
      *
      * <p> AGC acts as a variable gain amplifier adjusting the power of the incoming signal. The AGC
-     * level may be used to indicate potential interference. When AGC is at a nominal level, this
-     * value must be set as 0. Higher gain (and/or lower input power) shall be output as a positive
-     * number. Hence in cases of strong jamming, in the band of this signal, this value will go more
-     * negative.
+     * level may be used to indicate potential interference. Higher gain (and/or lower input power)
+     * shall be output as a positive number. Hence in cases of strong jamming, in the band of this
+     * signal, this value will go more negative.
      *
      * <p> Note: Different hardware designs (e.g. antenna, pre-amplification, or other RF HW
      * components) may also affect the typical output of of this value on any given hardware design
@@ -1775,6 +1775,7 @@
      */
     @Nullable
     @SystemApi
+    @SuppressLint("NullableCollection")
     public Collection<CorrelationVector> getCorrelationVectors() {
         return mReadOnlyCorrelationVectors;
     }
@@ -1785,7 +1786,9 @@
      * @hide
      */
     @TestApi
-    public void setCorrelationVectors(@Nullable Collection<CorrelationVector> correlationVectors) {
+    public void setCorrelationVectors(
+            @SuppressLint("NullableCollection")
+            @Nullable Collection<CorrelationVector> correlationVectors) {
         if (correlationVectors == null || correlationVectors.isEmpty()) {
             resetCorrelationVectors();
         } else {
diff --git a/location/java/android/location/ILocationListener.aidl b/location/java/android/location/ILocationListener.aidl
index ce92661..40d8615 100644
--- a/location/java/android/location/ILocationListener.aidl
+++ b/location/java/android/location/ILocationListener.aidl
@@ -16,7 +16,7 @@
 
 package android.location;
 
-import android.location.LocationResult;
+import android.location.Location;
 import android.os.IRemoteCallback;
 
 /**
@@ -24,7 +24,7 @@
  */
 oneway interface ILocationListener
 {
-    void onLocationChanged(in LocationResult locationResult, in @nullable IRemoteCallback onCompleteCallback);
+    void onLocationChanged(in List<Location> locations, in @nullable IRemoteCallback onCompleteCallback);
     void onProviderEnabledChanged(String provider, boolean enabled);
     void onFlushComplete(int requestCode);
 }
diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java
index 523117b..35a4091 100644
--- a/location/java/android/location/LocationListener.java
+++ b/location/java/android/location/LocationListener.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.os.Bundle;
 
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -48,15 +49,18 @@
     /**
      * Called when the location has changed and locations are being delivered in batches. The
      * default implementation calls through to ({@link #onLocationChanged(Location)} with all
-     * locations in the batch, from earliest to latest.
+     * locations in the batch. The list of locations is always guaranteed to be non-empty, and is
+     * always guaranteed to be ordered from earliest location to latest location (so that the
+     * earliest location in the batch is at index 0 in the list, and the latest location in the
+     * batch is at index size-1 in the list).
      *
      * @see LocationRequest#getMaxUpdateDelayMillis()
-     * @param locationResult the location result list
+     * @param locations the location list
      */
-    default void onLocationChanged(@NonNull LocationResult locationResult) {
-        final int size = locationResult.size();
-        for (int i = 0; i < size; ++i) {
-            onLocationChanged(locationResult.get(i));
+    default void onLocationChanged(@NonNull List<Location> locations) {
+        final int size = locations.size();
+        for (int i = 0; i < size; i++) {
+            onLocationChanged(locations.get(i));
         }
     }
 
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 96ec590..d569482 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 import static android.Manifest.permission.LOCATION_HARDWARE;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.location.GpsStatus.GPS_EVENT_STARTED;
 import static android.location.LocationRequest.createFromDeprecatedCriteria;
 import static android.location.LocationRequest.createFromDeprecatedProvider;
 
@@ -43,7 +44,6 @@
 import android.compat.Compatibility;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -60,16 +60,17 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.listeners.ListenerExecutor;
-import com.android.internal.listeners.ListenerTransportMultiplexer;
+import com.android.internal.listeners.ListenerTransport;
+import com.android.internal.listeners.ListenerTransportManager;
 import com.android.internal.util.Preconditions;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -231,19 +232,26 @@
 
     /**
      * Key used for an extra holding a {@link Location} value when a location change is sent using
-     * a PendingIntent.
+     * a PendingIntent. If the location change includes a list of batched locations via
+     * {@link #KEY_LOCATIONS} then this key will still be present, and will hold the last location
+     * in the batch. Use {@link Intent#getParcelableExtra(String)} to retrieve the location.
      *
      * @see #requestLocationUpdates(String, LocationRequest, PendingIntent)
      */
     public static final String KEY_LOCATION_CHANGED = "location";
 
     /**
-     * Key used for an extra holding a {@link LocationResult} value when a location change is sent
-     * using a PendingIntent.
+     * Key used for an extra holding a array of {@link Location}s when a location change is sent
+     * using a PendingIntent. This key will only be present if the location change includes
+     * multiple (ie, batched) locations, otherwise only {@link #KEY_LOCATION_CHANGED} will be
+     * present. Use {@link Intent#getParcelableArrayExtra(String)} to retrieve the locations.
+     *
+     * <p>The array of locations will never be empty, and will ordered from earliest location to
+     * latest location, the same as with {@link LocationListener#onLocationChanged(List)}.
      *
      * @see #requestLocationUpdates(String, LocationRequest, PendingIntent)
      */
-    public static final String KEY_LOCATION_RESULT = "locationResult";
+    public static final String KEY_LOCATIONS = "locations";
 
     /**
      * Key used for an extra holding an integer request code when location flush completion is sent
@@ -398,20 +406,40 @@
 
     private static final long MAX_SINGLE_LOCATION_TIMEOUT_MS = 30 * 1000;
 
+    private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
+            "cache_key.location_enabled";
+
+    private static ILocationManager getService() throws RemoteException {
+        try {
+            return ILocationManager.Stub.asInterface(
+                    ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE));
+        } catch (ServiceManager.ServiceNotFoundException e) {
+            throw new RemoteException(e);
+        }
+    }
+
     @GuardedBy("sLocationListeners")
     private static final WeakHashMap<LocationListener, WeakReference<LocationListenerTransport>>
             sLocationListeners = new WeakHashMap<>();
 
-    final Context mContext;
+    // allows lazy instantiation since most processes do not use GNSS APIs
+    private static class GnssLazyLoader {
+        static final GnssStatusTransportManager sGnssStatusListeners =
+                new GnssStatusTransportManager();
+        static final GnssNmeaTransportManager sGnssNmeaListeners =
+                new GnssNmeaTransportManager();
+        static final GnssMeasurementsTransportManager sGnssMeasurementsListeners =
+                new GnssMeasurementsTransportManager();
+        static final GnssAntennaTransportManager sGnssAntennaInfoListeners =
+                new GnssAntennaTransportManager();
+        static final GnssNavigationTransportManager sGnssNavigationListeners =
+                new GnssNavigationTransportManager();
+    }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link "
-            + "LocationManager}")
-    final ILocationManager mService;
+    private final Context mContext;
+    private final ILocationManager mService;
 
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
+    private volatile PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
             new PropertyInvalidatedCache<Integer, Boolean>(
                     4,
                     CACHE_KEY_LOCATION_ENABLED_PROPERTY) {
@@ -425,68 +453,12 @@
                 }
             };
 
-    @GuardedBy("mLock")
-    @Nullable private GnssStatusTransportMultiplexer mGnssStatusTransportMultiplexer;
-    @GuardedBy("mLock")
-    @Nullable private GnssNmeaTransportMultiplexer mGnssNmeaTransportMultiplexer;
-    @GuardedBy("mLock")
-    @Nullable private GnssMeasurementsTransportMultiplexer mGnssMeasurementsTransportMultiplexer;
-    @GuardedBy("mLock")
-    @Nullable private GnssNavigationTransportMultiplexer mGnssNavigationTransportMultiplexer;
-    @GuardedBy("mLock")
-    @Nullable private GnssAntennaInfoTransportMultiplexer mGnssAntennaInfoTransportMultiplexer;
-
     /**
      * @hide
      */
     public LocationManager(@NonNull Context context, @NonNull ILocationManager service) {
-        mService = service;
-        mContext = context;
-    }
-
-    private GnssStatusTransportMultiplexer getGnssStatusTransportMultiplexer() {
-        synchronized (mLock) {
-            if (mGnssStatusTransportMultiplexer == null) {
-                mGnssStatusTransportMultiplexer = new GnssStatusTransportMultiplexer();
-            }
-            return mGnssStatusTransportMultiplexer;
-        }
-    }
-
-    private GnssNmeaTransportMultiplexer getGnssNmeaTransportMultiplexer() {
-        synchronized (mLock) {
-            if (mGnssNmeaTransportMultiplexer == null) {
-                mGnssNmeaTransportMultiplexer = new GnssNmeaTransportMultiplexer();
-            }
-            return mGnssNmeaTransportMultiplexer;
-        }
-    }
-
-    private GnssMeasurementsTransportMultiplexer getGnssMeasurementsTransportMultiplexer() {
-        synchronized (mLock) {
-            if (mGnssMeasurementsTransportMultiplexer == null) {
-                mGnssMeasurementsTransportMultiplexer = new GnssMeasurementsTransportMultiplexer();
-            }
-            return mGnssMeasurementsTransportMultiplexer;
-        }
-    }
-
-    private GnssNavigationTransportMultiplexer getGnssNavigationTransportMultiplexer() {
-        synchronized (mLock) {
-            if (mGnssNavigationTransportMultiplexer == null) {
-                mGnssNavigationTransportMultiplexer = new GnssNavigationTransportMultiplexer();
-            }
-            return mGnssNavigationTransportMultiplexer;
-        }
-    }
-
-    private GnssAntennaInfoTransportMultiplexer getGnssAntennaInfoTransportMultiplexer() {
-        synchronized (mLock) {
-            if (mGnssAntennaInfoTransportMultiplexer == null) {
-                mGnssAntennaInfoTransportMultiplexer = new GnssAntennaInfoTransportMultiplexer();
-            }
-            return mGnssAntennaInfoTransportMultiplexer;
-        }
+        mContext = Objects.requireNonNull(context);
+        mService = Objects.requireNonNull(service);
     }
 
     /**
@@ -627,10 +599,9 @@
      */
     @SystemApi
     public boolean isLocationEnabledForUser(@NonNull UserHandle userHandle) {
-        synchronized (mLock) {
-            if (mLocationEnabledCache != null) {
-                return mLocationEnabledCache.query(userHandle.getIdentifier());
-            }
+        PropertyInvalidatedCache<Integer, Boolean> cache = mLocationEnabledCache;
+        if (cache != null) {
+            return cache.query(userHandle.getIdentifier());
         }
 
         // fallback if cache is disabled
@@ -1905,6 +1876,7 @@
     @Deprecated
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     @Nullable
+    @SuppressWarnings("NullableCollection")
     public List<String> getProviderPackages(@NonNull String provider) {
         try {
             return mService.getProviderPackages(provider);
@@ -2233,6 +2205,7 @@
      * @see #getGnssCapabilities()
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<GnssAntennaInfo> getGnssAntennaInfos() {
         try {
             return mService.getGnssAntennaInfos();
@@ -2263,9 +2236,8 @@
                     "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
 
-        GnssStatusTransportMultiplexer multiplexer = getGnssStatusTransportMultiplexer();
-        GnssStatus gnssStatus = multiplexer.getGnssStatus();
-        int ttff = multiplexer.getTtff();
+        GnssStatus gnssStatus = GpsStatusTransport.sGnssStatus;
+        int ttff = GpsStatusTransport.sTtff;
         if (gnssStatus != null) {
             if (status == null) {
                 status = GpsStatus.create(gnssStatus, ttff);
@@ -2298,8 +2270,8 @@
                     "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
 
-        getGnssStatusTransportMultiplexer().addListener(listener,
-                new HandlerExecutor(new Handler()));
+        GnssLazyLoader.sGnssStatusListeners.addListener(listener,
+                new GpsStatusTransport(new HandlerExecutor(new Handler()), mContext, listener));
         return true;
     }
 
@@ -2318,7 +2290,7 @@
                     "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
 
-        getGnssStatusTransportMultiplexer().removeListener(listener);
+        GnssLazyLoader.sGnssStatusListeners.removeListener(listener);
     }
 
     /**
@@ -2381,7 +2353,8 @@
     public boolean registerGnssStatusCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull GnssStatus.Callback callback) {
-        getGnssStatusTransportMultiplexer().addListener(callback, executor);
+        GnssLazyLoader.sGnssStatusListeners.addListener(callback,
+                new GnssStatusTransport(executor, mContext, callback));
         return true;
     }
 
@@ -2391,7 +2364,7 @@
      * @param callback GNSS status callback object to remove
      */
     public void unregisterGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
-        getGnssStatusTransportMultiplexer().removeListener(callback);
+        GnssLazyLoader.sGnssStatusListeners.removeListener(callback);
     }
 
     /**
@@ -2471,7 +2444,8 @@
     public boolean addNmeaListener(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull OnNmeaMessageListener listener) {
-        getGnssNmeaTransportMultiplexer().addListener(listener, executor);
+        GnssLazyLoader.sGnssNmeaListeners.addListener(listener,
+                new GnssNmeaTransport(executor, mContext, listener));
         return true;
     }
 
@@ -2481,7 +2455,7 @@
      * @param listener a {@link OnNmeaMessageListener} object to remove
      */
     public void removeNmeaListener(@NonNull OnNmeaMessageListener listener) {
-        getGnssNmeaTransportMultiplexer().removeListener(listener);
+        GnssLazyLoader.sGnssNmeaListeners.removeListener(listener);
     }
 
     /**
@@ -2597,10 +2571,8 @@
             @NonNull GnssRequest request,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull GnssMeasurementsEvent.Callback callback) {
-        Preconditions.checkArgument(request != null, "invalid null request");
-        getGnssMeasurementsTransportMultiplexer().addListener(request.toGnssMeasurementRequest(),
-                callback, executor);
-        return true;
+        return registerGnssMeasurementsCallback(request.toGnssMeasurementRequest(), executor,
+                callback);
     }
 
     /**
@@ -2622,8 +2594,8 @@
             @NonNull GnssMeasurementRequest request,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull GnssMeasurementsEvent.Callback callback) {
-        Preconditions.checkArgument(request != null, "invalid null request");
-        getGnssMeasurementsTransportMultiplexer().addListener(request, callback, executor);
+        GnssLazyLoader.sGnssMeasurementsListeners.addListener(callback,
+                new GnssMeasurementsTransport(executor, mContext, request, callback));
         return true;
     }
 
@@ -2655,7 +2627,7 @@
      */
     public void unregisterGnssMeasurementsCallback(
             @NonNull GnssMeasurementsEvent.Callback callback) {
-        getGnssMeasurementsTransportMultiplexer().removeListener(callback);
+        GnssLazyLoader.sGnssMeasurementsListeners.removeListener(callback);
     }
 
     /**
@@ -2680,7 +2652,8 @@
     public boolean registerAntennaInfoListener(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull GnssAntennaInfo.Listener listener) {
-        getGnssAntennaInfoTransportMultiplexer().addListener(listener, executor);
+        GnssLazyLoader.sGnssAntennaInfoListeners.addListener(listener,
+                new GnssAntennaInfoTransport(executor, mContext, listener));
         return true;
     }
 
@@ -2693,7 +2666,7 @@
      */
     @Deprecated
     public void unregisterAntennaInfoListener(@NonNull GnssAntennaInfo.Listener listener) {
-        getGnssAntennaInfoTransportMultiplexer().removeListener(listener);
+        GnssLazyLoader.sGnssAntennaInfoListeners.removeListener(listener);
     }
 
     /**
@@ -2783,7 +2756,8 @@
     public boolean registerGnssNavigationMessageCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull GnssNavigationMessage.Callback callback) {
-        getGnssNavigationTransportMultiplexer().addListener(callback, executor);
+        GnssLazyLoader.sGnssNavigationListeners.addListener(callback,
+                new GnssNavigationTransport(executor, mContext, callback));
         return true;
     }
 
@@ -2794,7 +2768,7 @@
      */
     public void unregisterGnssNavigationMessageCallback(
             @NonNull GnssNavigationMessage.Callback callback) {
-        getGnssNavigationTransportMultiplexer().removeListener(callback);
+        GnssLazyLoader.sGnssNavigationListeners.removeListener(callback);
     }
 
     /**
@@ -2903,6 +2877,89 @@
         }
     }
 
+    private static class GnssStatusTransportManager extends
+            ListenerTransportManager<GnssStatusTransport> {
+
+        @Override
+        protected void registerTransport(GnssStatusTransport transport)
+                            throws RemoteException {
+            getService().registerGnssStatusCallback(transport, transport.getPackage(),
+                    transport.getAttributionTag());
+        }
+
+        @Override
+        protected void unregisterTransport(GnssStatusTransport transport)
+                            throws RemoteException {
+            getService().unregisterGnssStatusCallback(transport);
+        }
+    }
+
+    private static class GnssNmeaTransportManager extends
+            ListenerTransportManager<GnssNmeaTransport> {
+
+        @Override
+        protected void registerTransport(GnssNmeaTransport transport)
+                            throws RemoteException {
+            getService().registerGnssNmeaCallback(transport, transport.getPackage(),
+                    transport.getAttributionTag());
+        }
+
+        @Override
+        protected void unregisterTransport(GnssNmeaTransport transport)
+                            throws RemoteException {
+            getService().unregisterGnssNmeaCallback(transport);
+        }
+    }
+
+    private static class GnssMeasurementsTransportManager extends
+            ListenerTransportManager<GnssMeasurementsTransport> {
+
+        @Override
+        protected void registerTransport(GnssMeasurementsTransport transport)
+                            throws RemoteException {
+            getService().addGnssMeasurementsListener(transport.getRequest(), transport,
+                    transport.getPackage(), transport.getAttributionTag());
+        }
+
+        @Override
+        protected void unregisterTransport(GnssMeasurementsTransport transport)
+                            throws RemoteException {
+            getService().removeGnssMeasurementsListener(transport);
+        }
+    }
+
+    private static class GnssAntennaTransportManager extends
+            ListenerTransportManager<GnssAntennaInfoTransport> {
+
+        @Override
+        protected void registerTransport(GnssAntennaInfoTransport transport) {
+            transport.getContext().registerReceiver(transport,
+                    new IntentFilter(ACTION_GNSS_ANTENNA_INFOS_CHANGED));
+        }
+
+        @Override
+        protected void unregisterTransport(GnssAntennaInfoTransport transport) {
+            transport.getContext().unregisterReceiver(transport);
+        }
+    }
+
+    private static class GnssNavigationTransportManager extends
+            ListenerTransportManager<GnssNavigationTransport> {
+
+        @Override
+        protected void registerTransport(GnssNavigationTransport transport)
+                            throws RemoteException {
+            getService().addGnssNavigationMessageListener(transport,
+                    transport.getPackage(), transport.getAttributionTag());
+        }
+
+        @Override
+        protected void unregisterTransport(GnssNavigationTransport transport)
+                            throws RemoteException {
+            getService().removeGnssNavigationMessageListener(transport);
+        }
+    }
+
     private static class GetCurrentLocationTransport extends ILocationCallback.Stub implements
             ListenerExecutor, CancellationSignal.OnCancelListener {
 
@@ -2968,12 +3025,12 @@
         }
 
         @Override
-        public void onLocationChanged(LocationResult locationResult,
+        public void onLocationChanged(List<Location> locations,
                 @Nullable IRemoteCallback onCompleteCallback) {
             executeSafely(mExecutor, () -> mListener, new ListenerOperation<LocationListener>() {
                 @Override
                 public void operate(LocationListener listener) {
-                    listener.onLocationChanged(locationResult);
+                    listener.onLocationChanged(locations);
                 }
 
                 @Override
@@ -3017,7 +3074,7 @@
 
         @Override
         public void onStarted() {
-            mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
+            mGpsListener.onGpsStatusChanged(GPS_EVENT_STARTED);
         }
 
         @Override
@@ -3036,273 +3093,269 @@
         }
     }
 
-    private class GnssStatusTransportMultiplexer extends
-            ListenerTransportMultiplexer<Void, GnssStatus.Callback> {
+    private static class GnssStatusTransport extends IGnssStatusListener.Stub implements
+            ListenerTransport<GnssStatus.Callback> {
 
-        private @Nullable IGnssStatusListener mListenerTransport;
+        private final Executor mExecutor;
+        private final String mPackageName;
+        private final String mAttributionTag;
 
-        volatile @Nullable GnssStatus mGnssStatus;
-        volatile int mTtff;
+        private volatile @Nullable GnssStatus.Callback mListener;
 
-        GnssStatusTransportMultiplexer() {}
-
-        public GnssStatus getGnssStatus() {
-            return mGnssStatus;
+        GnssStatusTransport(Executor executor, Context context, GnssStatus.Callback listener) {
+            Preconditions.checkArgument(executor != null, "invalid null executor");
+            Preconditions.checkArgument(listener != null, "invalid null callback");
+            mExecutor = executor;
+            mPackageName = context.getPackageName();
+            mAttributionTag = context.getAttributionTag();
+            mListener = listener;
         }
 
-        public int getTtff() {
-            return mTtff;
+        public String getPackage() {
+            return mPackageName;
         }
 
-        public void addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor) {
-            addListener(listener, null, new GpsAdapter(listener), executor);
+        public String getAttributionTag() {
+            return mAttributionTag;
         }
 
         @Override
-        protected void registerWithServer(Void ignored) throws RemoteException {
-            IGnssStatusListener transport = mListenerTransport;
-            if (transport == null) {
-                transport = new GnssStatusListener();
-            }
-
-            // if a remote exception is thrown the transport should not be set
-            mListenerTransport = null;
-            mService.registerGnssStatusCallback(transport, mContext.getPackageName(),
-                    mContext.getAttributionTag());
-            mListenerTransport = transport;
+        public void unregister() {
+            mListener = null;
         }
 
         @Override
-        protected void unregisterWithServer() throws RemoteException {
-            if (mListenerTransport != null) {
-                IGnssStatusListener transport = mListenerTransport;
-                mListenerTransport = null;
-                mService.unregisterGnssStatusCallback(transport);
-            }
+        public @Nullable GnssStatus.Callback getListener() {
+            return mListener;
         }
 
-        private class GnssStatusListener extends IGnssStatusListener.Stub {
+        @Override
+        public void onGnssStarted() {
+            execute(mExecutor, GnssStatus.Callback::onStarted);
+        }
 
-            GnssStatusListener() {}
+        @Override
+        public void onGnssStopped() {
+            execute(mExecutor, GnssStatus.Callback::onStopped);
+        }
 
-            @Override
-            public void onGnssStarted() {
-                deliverToListeners(GnssStatus.Callback::onStarted);
-            }
+        @Override
+        public void onFirstFix(int ttff) {
+            execute(mExecutor, listener -> listener.onFirstFix(ttff));
 
-            @Override
-            public void onGnssStopped() {
-                deliverToListeners(GnssStatus.Callback::onStopped);
-            }
+        }
 
-            @Override
-            public void onFirstFix(int ttff) {
-                mTtff = ttff;
-                deliverToListeners(callback -> callback.onFirstFix(ttff));
-            }
+        @Override
+        public void onSvStatusChanged(GnssStatus gnssStatus) {
+            execute(mExecutor, listener -> listener.onSatelliteStatusChanged(gnssStatus));
+        }
+    }
 
-            @Override
-            public void onSvStatusChanged(GnssStatus gnssStatus) {
-                mGnssStatus = gnssStatus;
-                deliverToListeners(callback -> callback.onSatelliteStatusChanged(gnssStatus));
+    private static class GpsStatusTransport extends GnssStatusTransport {
+
+        static volatile int sTtff;
+        static volatile GnssStatus sGnssStatus;
+
+        GpsStatusTransport(Executor executor, Context context, GpsStatus.Listener listener) {
+            super(executor, context, new GpsAdapter(listener));
+        }
+
+        @Override
+        public void onFirstFix(int ttff) {
+            sTtff = ttff;
+            super.onFirstFix(ttff);
+        }
+
+        @Override
+        public void onSvStatusChanged(GnssStatus gnssStatus) {
+            sGnssStatus = gnssStatus;
+            super.onSvStatusChanged(gnssStatus);
+        }
+    }
+
+    private static class GnssNmeaTransport extends IGnssNmeaListener.Stub implements
+            ListenerTransport<OnNmeaMessageListener> {
+
+        private final Executor mExecutor;
+        private final String mPackageName;
+        private final String mAttributionTag;
+
+        private volatile @Nullable OnNmeaMessageListener mListener;
+
+        GnssNmeaTransport(Executor executor, Context context, OnNmeaMessageListener listener) {
+            Preconditions.checkArgument(executor != null, "invalid null executor");
+            Preconditions.checkArgument(listener != null, "invalid null listener");
+            mExecutor = executor;
+            mPackageName = context.getPackageName();
+            mAttributionTag = context.getAttributionTag();
+            mListener = listener;
+        }
+
+        public String getPackage() {
+            return mPackageName;
+        }
+
+        public String getAttributionTag() {
+            return mAttributionTag;
+        }
+
+        @Override
+        public void unregister() {
+            mListener = null;
+        }
+
+        @Override
+        public @Nullable OnNmeaMessageListener getListener() {
+            return mListener;
+        }
+
+        @Override
+        public void onNmeaReceived(long timestamp, String nmea) {
+            execute(mExecutor, callback -> callback.onNmeaMessage(nmea, timestamp));
+        }
+    }
+
+    private static class GnssMeasurementsTransport extends IGnssMeasurementsListener.Stub implements
+            ListenerTransport<GnssMeasurementsEvent.Callback> {
+
+        private final Executor mExecutor;
+        private final String mPackageName;
+        private final String mAttributionTag;
+        private final GnssMeasurementRequest mRequest;
+
+        private volatile @Nullable GnssMeasurementsEvent.Callback mListener;
+
+        GnssMeasurementsTransport(Executor executor, Context context,
+                GnssMeasurementRequest request, GnssMeasurementsEvent.Callback listener) {
+            Preconditions.checkArgument(executor != null, "invalid null executor");
+            Preconditions.checkArgument(listener != null, "invalid null callback");
+            Preconditions.checkArgument(request != null, "invalid null request");
+            mExecutor = executor;
+            mPackageName = context.getPackageName();
+            mAttributionTag = context.getAttributionTag();
+            mRequest = request;
+            mListener = listener;
+        }
+
+        public String getPackage() {
+            return mPackageName;
+        }
+
+        public String getAttributionTag() {
+            return mAttributionTag;
+        }
+
+        public GnssMeasurementRequest getRequest() {
+            return mRequest;
+        }
+
+        @Override
+        public void unregister() {
+            mListener = null;
+        }
+
+        @Override
+        public @Nullable GnssMeasurementsEvent.Callback getListener() {
+            return mListener;
+        }
+
+        @Override
+        public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
+            execute(mExecutor, callback -> callback.onGnssMeasurementsReceived(event));
+        }
+
+        @Override
+        public void onStatusChanged(int status) {
+            execute(mExecutor, callback -> callback.onStatusChanged(status));
+        }
+    }
+
+    private static class GnssAntennaInfoTransport extends BroadcastReceiver implements
+            ListenerTransport<GnssAntennaInfo.Listener> {
+
+        private final Executor mExecutor;
+        private final Context mContext;
+
+        private volatile @Nullable GnssAntennaInfo.Listener mListener;
+
+        GnssAntennaInfoTransport(Executor executor, Context context,
+                GnssAntennaInfo.Listener listener) {
+            Preconditions.checkArgument(executor != null, "invalid null executor");
+            Preconditions.checkArgument(listener != null, "invalid null listener");
+            mExecutor = executor;
+            mContext = context;
+            mListener = listener;
+        }
+
+        public Context getContext() {
+            return mContext;
+        }
+
+        @Override
+        public void unregister() {
+            mListener = null;
+        }
+
+        @Override
+        public @Nullable GnssAntennaInfo.Listener getListener() {
+            return mListener;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            ArrayList<GnssAntennaInfo> infos = intent.getParcelableArrayListExtra(
+                    EXTRA_GNSS_ANTENNA_INFOS);
+            if (infos != null) {
+                execute(mExecutor, callback -> callback.onGnssAntennaInfoReceived(infos));
             }
         }
     }
 
-    private class GnssNmeaTransportMultiplexer extends
-            ListenerTransportMultiplexer<Void, OnNmeaMessageListener> {
+    private static class GnssNavigationTransport extends IGnssNavigationMessageListener.Stub
+            implements ListenerTransport<GnssNavigationMessage.Callback> {
 
-        private @Nullable IGnssNmeaListener mListenerTransport;
+        private final Executor mExecutor;
+        private final String mPackageName;
+        private final String mAttributionTag;
 
-        GnssNmeaTransportMultiplexer() {}
+        private volatile @Nullable GnssNavigationMessage.Callback mListener;
 
-        public void addListener(@NonNull OnNmeaMessageListener listener,
-                @NonNull Executor executor) {
-            addListener(listener, null, listener, executor);
+        GnssNavigationTransport(Executor executor, Context context,
+                GnssNavigationMessage.Callback listener) {
+            Preconditions.checkArgument(executor != null, "invalid null executor");
+            Preconditions.checkArgument(listener != null, "invalid null callback");
+            mExecutor = executor;
+            mPackageName = context.getPackageName();
+            mAttributionTag = context.getAttributionTag();
+            mListener = listener;
+        }
+
+        public String getPackage() {
+            return mPackageName;
+        }
+
+        public String getAttributionTag() {
+            return mAttributionTag;
         }
 
         @Override
-        protected void registerWithServer(Void ignored) throws RemoteException {
-            IGnssNmeaListener transport = mListenerTransport;
-            if (transport == null) {
-                transport = new GnssNmeaListener();
-            }
-
-            // if a remote exception is thrown the transport should not be set
-            mListenerTransport = null;
-            mService.registerGnssNmeaCallback(transport, mContext.getPackageName(),
-                    mContext.getAttributionTag());
-            mListenerTransport = transport;
+        public void unregister() {
+            mListener = null;
         }
 
         @Override
-        protected void unregisterWithServer() throws RemoteException {
-            if (mListenerTransport != null) {
-                IGnssNmeaListener transport = mListenerTransport;
-                mListenerTransport = null;
-                mService.unregisterGnssNmeaCallback(transport);
-            }
-        }
-
-        private class GnssNmeaListener extends IGnssNmeaListener.Stub {
-
-            GnssNmeaListener() {}
-
-            @Override
-            public void onNmeaReceived(long timestamp, String nmea) {
-                deliverToListeners(callback -> callback.onNmeaMessage(nmea, timestamp));
-            }
-        }
-    }
-
-    private class GnssMeasurementsTransportMultiplexer extends
-            ListenerTransportMultiplexer<GnssMeasurementRequest, GnssMeasurementsEvent.Callback> {
-
-        private @Nullable IGnssMeasurementsListener mListenerTransport;
-
-        GnssMeasurementsTransportMultiplexer() {}
-
-        @Override
-        protected void registerWithServer(GnssMeasurementRequest request) throws RemoteException {
-            IGnssMeasurementsListener transport = mListenerTransport;
-            if (transport == null) {
-                transport = new GnssMeasurementsListener();
-            }
-
-            // if a remote exception is thrown the transport should not be set
-            mListenerTransport = null;
-            mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(),
-                    mContext.getAttributionTag());
-            mListenerTransport = transport;
+        public @Nullable GnssNavigationMessage.Callback getListener() {
+            return mListener;
         }
 
         @Override
-        protected void unregisterWithServer() throws RemoteException {
-            if (mListenerTransport != null) {
-                IGnssMeasurementsListener transport = mListenerTransport;
-                mListenerTransport = null;
-                mService.removeGnssMeasurementsListener(transport);
-            }
+        public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
+            execute(mExecutor, listener -> listener.onGnssNavigationMessageReceived(event));
         }
 
         @Override
-        protected GnssMeasurementRequest mergeRequests(
-                Collection<GnssMeasurementRequest> requests) {
-            GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
-            for (GnssMeasurementRequest request : requests) {
-                if (request.isFullTracking()) {
-                    builder.setFullTracking(true);
-                }
-                if (request.isCorrelationVectorOutputsEnabled()) {
-                    builder.setCorrelationVectorOutputsEnabled(true);
-                }
-            }
-
-            return builder.build();
-        }
-
-        private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub {
-
-            GnssMeasurementsListener() {}
-
-            @Override
-            public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
-                deliverToListeners(callback -> callback.onGnssMeasurementsReceived(event));
-            }
-
-            @Override
-            public void onStatusChanged(int status) {
-                deliverToListeners(callback -> callback.onStatusChanged(status));
-            }
-        }
-    }
-
-    private class GnssNavigationTransportMultiplexer extends
-            ListenerTransportMultiplexer<Void, GnssNavigationMessage.Callback> {
-
-        @Nullable
-        private IGnssNavigationMessageListener mListenerTransport;
-
-        GnssNavigationTransportMultiplexer() {}
-
-        @Override
-        protected void registerWithServer(Void ignored) throws RemoteException {
-            IGnssNavigationMessageListener transport = mListenerTransport;
-            if (transport == null) {
-                transport = new GnssNavigationMessageListener();
-            }
-
-            // if a remote exception is thrown the transport should not be set
-            mListenerTransport = null;
-            mService.addGnssNavigationMessageListener(transport, mContext.getPackageName(),
-                    mContext.getAttributionTag());
-            mListenerTransport = transport;
-        }
-
-        @Override
-        protected void unregisterWithServer() throws RemoteException {
-            if (mListenerTransport != null) {
-                IGnssNavigationMessageListener transport = mListenerTransport;
-                mListenerTransport = null;
-                mService.removeGnssNavigationMessageListener(transport);
-            }
-        }
-
-        private class GnssNavigationMessageListener extends IGnssNavigationMessageListener.Stub {
-
-            GnssNavigationMessageListener() {}
-
-            @Override
-            public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
-                deliverToListeners(listener -> listener.onGnssNavigationMessageReceived(event));
-            }
-
-            @Override
-            public void onStatusChanged(int status) {
-                deliverToListeners(listener -> listener.onStatusChanged(status));
-            }
-        }
-    }
-
-    private class GnssAntennaInfoTransportMultiplexer extends
-            ListenerTransportMultiplexer<Void, GnssAntennaInfo.Listener> {
-
-        private @Nullable BroadcastReceiver mListenerTransport;
-
-        GnssAntennaInfoTransportMultiplexer() {}
-
-        @Override
-        protected void registerWithServer(Void ignored) {
-            if (mListenerTransport == null) {
-                // if an exception is thrown the transport should not be set
-                BroadcastReceiver transport = new GnssAntennaInfoReceiver();
-                mContext.registerReceiver(transport,
-                        new IntentFilter(ACTION_GNSS_ANTENNA_INFOS_CHANGED));
-                mListenerTransport = transport;
-            }
-        }
-
-        @Override
-        protected void unregisterWithServer() {
-            if (mListenerTransport != null) {
-                BroadcastReceiver transport = mListenerTransport;
-                mListenerTransport = null;
-                mContext.unregisterReceiver(transport);
-            }
-        }
-
-        private class GnssAntennaInfoReceiver extends BroadcastReceiver {
-
-            GnssAntennaInfoReceiver() {}
-
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                ArrayList<GnssAntennaInfo> infos = intent.getParcelableArrayListExtra(
-                        EXTRA_GNSS_ANTENNA_INFOS);
-                if (infos != null) {
-                    deliverToListeners(callback -> callback.onGnssAntennaInfoReceived(infos));
-                }
-            }
+        public void onStatusChanged(int status) {
+            execute(mExecutor, listener -> listener.onStatusChanged(status));
         }
     }
 
@@ -3320,8 +3373,8 @@
         }
 
         @Override
-        public void onLocationChanged(@NonNull LocationResult locationResult) {
-            mCallback.onLocationBatch(locationResult.asList());
+        public void onLocationChanged(@NonNull List<Location> locations) {
+            mCallback.onLocationBatch(locations);
         }
     }
 
@@ -3335,12 +3388,6 @@
     /**
      * @hide
      */
-    private static final String CACHE_KEY_LOCATION_ENABLED_PROPERTY =
-            "cache_key.location_enabled";
-
-    /**
-     * @hide
-     */
     public static void invalidateLocalLocationEnabledCaches() {
         PropertyInvalidatedCache.invalidateCache(CACHE_KEY_LOCATION_ENABLED_PROPERTY);
     }
@@ -3349,8 +3396,6 @@
      * @hide
      */
     public void disableLocalLocationEnabledCaches() {
-        synchronized (mLock) {
-            mLocationEnabledCache = null;
-        }
+        mLocationEnabledCache = null;
     }
 }
diff --git a/location/java/android/location/LocationProvider.java b/location/java/android/location/LocationProvider.java
index 60b251e..26cf018 100644
--- a/location/java/android/location/LocationProvider.java
+++ b/location/java/android/location/LocationProvider.java
@@ -23,7 +23,9 @@
  * Information about the properties of a location provider.
  *
  * @deprecated This class is incapable of representing unknown provider properties and may return
- * incorrect results when the properties are unknown.
+ * incorrect results on the rare occasion when a provider's properties are unknown. Prefer using
+ * {@link LocationManager#getProviderProperties(String)} to retrieve {@link ProviderProperties}
+ * instead.
  */
 @Deprecated
 public class LocationProvider {
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 323e740..cb56ee5 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -605,7 +605,7 @@
      * When available, batching can provide substantial power savings to the device, and clients are
      * encouraged to take advantage where appropriate for the use case.
      *
-     * @see LocationListener#onLocationChanged(LocationResult)
+     * @see LocationListener#onLocationChanged(java.util.List)
      * @return the maximum time by which a location update may be delayed
      */
     public @IntRange(from = 0) long getMaxUpdateDelayMillis() {
diff --git a/location/java/android/location/LocationResult.java b/location/java/android/location/LocationResult.java
index 79a000c..8423000 100644
--- a/location/java/android/location/LocationResult.java
+++ b/location/java/android/location/LocationResult.java
@@ -19,7 +19,6 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -34,21 +33,14 @@
 
 /**
  * A location result representing a list of locations, ordered from earliest to latest.
+ *
+ * @hide
  */
 public final class LocationResult implements Parcelable {
 
     /**
-     * Creates a new LocationResult from the given location.
-     */
-    public static @NonNull LocationResult create(@NonNull Location location) {
-        ArrayList<Location> locations = new ArrayList<>(1);
-        locations.add(new Location(Objects.requireNonNull(location)));
-        return new LocationResult(locations);
-    }
-
-    /**
-     * Creates a new LocationResult from the given locations. Locations must be ordered in the same
-     * order they were derived (earliest to latest).
+     * Creates a new LocationResult from the given locations, making a copy of each location.
+     * Locations must be ordered in the same order they were derived (earliest to latest).
      */
     public static @NonNull LocationResult create(@NonNull List<Location> locations) {
         Preconditions.checkArgument(!locations.isEmpty());
@@ -60,16 +52,40 @@
     }
 
     /**
-     * Creates a new LocationResult that takes ownership of the given location without copying it.
-     * Callers must ensure the given location is never mutated after this method is called.
-     *
-     * @hide
+     * Creates a new LocationResult from the given locations, making a copy of each location.
+     * Locations must be ordered in the same order they were derived (earliest to latest).
      */
-    @SystemApi
-    public static @NonNull LocationResult wrap(@NonNull Location location) {
-        ArrayList<Location> locations = new ArrayList<>(1);
-        locations.add(Objects.requireNonNull(location));
-        return new LocationResult(locations);
+    public static @NonNull LocationResult create(@NonNull Location... locations) {
+        Preconditions.checkArgument(locations.length > 0);
+        ArrayList<Location> locationsCopy = new ArrayList<>(locations.length);
+        for (Location location : locations) {
+            locationsCopy.add(new Location(Objects.requireNonNull(location)));
+        }
+        return new LocationResult(locationsCopy);
+    }
+
+    /**
+     * Creates a new LocationResult that takes ownership of the given locations without copying
+     * them. Callers must ensure the given locations are never mutated after this method is called.
+     * Locations must be ordered in the same order they were derived (earliest to latest).
+     */
+    public static @NonNull LocationResult wrap(@NonNull List<Location> locations) {
+        Preconditions.checkArgument(!locations.isEmpty());
+        return new LocationResult(new ArrayList<>(locations));
+    }
+
+    /**
+     * Creates a new LocationResult that takes ownership of the given locations without copying
+     * them. Callers must ensure the given locations are never mutated after this method is called.
+     * Locations must be ordered in the same order they were derived (earliest to latest).
+     */
+    public static @NonNull LocationResult wrap(@NonNull Location... locations) {
+        Preconditions.checkArgument(locations.length > 0);
+        ArrayList<Location> newLocations = new ArrayList<>(locations.length);
+        for (Location location : locations) {
+            newLocations.add(Objects.requireNonNull(location));
+        }
+        return new LocationResult(newLocations);
     }
 
     private final ArrayList<Location> mLocations;
@@ -112,7 +128,7 @@
     }
 
     /**
-     * Returns the numer of locations in this location result.
+     * Returns the number of locations in this location result.
      */
     public @IntRange(from = 1) int size() {
         return mLocations.size();
@@ -139,9 +155,9 @@
      * @hide
      */
     public @NonNull LocationResult deepCopy() {
-        ArrayList<Location> copy = new ArrayList<>(mLocations.size());
         final int size = mLocations.size();
-        for (int i = 0; i < size; ++i) {
+        ArrayList<Location> copy = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
             copy.add(new Location(mLocations.get(i)));
         }
         return new LocationResult(copy);
@@ -164,7 +180,7 @@
      * Returns a LocationResult with only locations that pass the given predicate. This
      * implementation will avoid allocations when no locations are filtered out. The predicate is
      * guaranteed to be invoked once per location, in order from earliest to latest. If all
-     * locations are filtered out a null value is returned instead of an empty LocationResult.
+     * locations are filtered out a null value is returned.
      *
      * @hide
      */
diff --git a/location/java/android/location/provider/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl
index e3f51d9..50ed046 100644
--- a/location/java/android/location/provider/ILocationProviderManager.aidl
+++ b/location/java/android/location/provider/ILocationProviderManager.aidl
@@ -16,7 +16,7 @@
 
 package android.location.provider;
 
-import android.location.LocationResult;
+import android.location.Location;
 import android.location.provider.ProviderProperties;
 
 /**
@@ -28,6 +28,7 @@
     void onSetAllowed(boolean allowed);
     void onSetProperties(in ProviderProperties properties);
 
-    void onReportLocation(in LocationResult locationResult);
+    void onReportLocation(in Location location);
+    void onReportLocations(in List<Location> locations);
     void onFlushComplete();
 }
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 1306ea2..ae6395d 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -25,12 +25,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.location.Location;
-import android.location.LocationResult;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -200,35 +201,29 @@
      * Reports a new location from this provider.
      */
     public void reportLocation(@NonNull Location location) {
-        reportLocation(LocationResult.create(location));
+        ILocationProviderManager manager = mManager;
+        if (manager != null) {
+            try {
+                manager.onReportLocation(stripExtras(location));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } catch (RuntimeException e) {
+                Log.w(mTag, e);
+            }
+        }
     }
 
     /**
-     * Reports a new location result from this provider.
-     *
-     * <p>May only be used from Android S onwards.
+     * Reports a new batch of locations from this provider. Locations must be ordered in the list
+     * from earliest first to latest last.
      */
-    public void reportLocation(@NonNull LocationResult locationResult) {
+    public void reportLocations(@NonNull List<Location> locations) {
         ILocationProviderManager manager = mManager;
         if (manager != null) {
-            locationResult = locationResult.map(location -> {
-                // remove deprecated extras to save on serialization costs
-                Bundle extras = location.getExtras();
-                if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION)
-                        || extras.containsKey("coarseLocation"))) {
-                    location = new Location(location);
-                    extras = location.getExtras();
-                    extras.remove(EXTRA_NO_GPS_LOCATION);
-                    extras.remove("coarseLocation");
-                    if (extras.isEmpty()) {
-                        location.setExtras(null);
-                    }
-                }
-                return location;
-            });
+
 
             try {
-                manager.onReportLocation(locationResult);
+                manager.onReportLocations(stripExtras(locations));
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             } catch (RuntimeException e) {
@@ -246,16 +241,61 @@
 
     /**
      * Requests a flush of any pending batched locations. The callback must always be invoked once
-     * per invocation, and should be invoked after {@link #reportLocation(LocationResult)} has been
-     * invoked with any flushed locations. The callback may be invoked immediately if no locations
-     * are flushed.
+     * per invocation, and should be invoked after {@link #reportLocation(Location)} or
+     * {@link #reportLocations(List)} has been invoked with any flushed locations. The callback may
+     * be invoked immediately if no locations are flushed.
      */
     public abstract void onFlush(@NonNull OnFlushCompleteCallback callback);
 
     /**
      * Implements optional custom commands.
      */
-    public abstract void onSendExtraCommand(@NonNull String command, @Nullable Bundle extras);
+    public abstract void onSendExtraCommand(@NonNull String command,
+            @SuppressLint("NullableCollection")
+            @Nullable Bundle extras);
+
+    private static Location stripExtras(Location location) {
+        Bundle extras = location.getExtras();
+        if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION)
+                || extras.containsKey("indoorProbability")
+                || extras.containsKey("coarseLocation"))) {
+            location = new Location(location);
+            extras = location.getExtras();
+            extras.remove(EXTRA_NO_GPS_LOCATION);
+            extras.remove("indoorProbability");
+            extras.remove("coarseLocation");
+            if (extras.isEmpty()) {
+                location.setExtras(null);
+            }
+        }
+        return location;
+    }
+
+    private static List<Location> stripExtras(List<Location> locations) {
+        List<Location> mapped = locations;
+        final int size = locations.size();
+        int i = 0;
+        for (Location location : locations) {
+            Location newLocation = stripExtras(location);
+            if (mapped != locations) {
+                mapped.add(newLocation);
+            } else if (newLocation != location) {
+                mapped = new ArrayList<>(size);
+                int j = 0;
+                for (Location copiedLocation : locations) {
+                    if (j >= i) {
+                        break;
+                    }
+                    mapped.add(copiedLocation);
+                    j++;
+                }
+                mapped.add(newLocation);
+            }
+            i++;
+        }
+
+        return mapped;
+    }
 
     private final class Service extends ILocationProvider.Stub {
 
diff --git a/location/lib/api/current.txt b/location/lib/api/current.txt
index 338d7cc..0d81f36 100644
--- a/location/lib/api/current.txt
+++ b/location/lib/api/current.txt
@@ -21,8 +21,8 @@
     method @Deprecated protected void onInit();
     method @Deprecated protected boolean onSendExtraCommand(@Nullable String, @Nullable android.os.Bundle);
     method @Deprecated protected abstract void onSetRequest(com.android.location.provider.ProviderRequestUnbundled, android.os.WorkSource);
-    method @Deprecated public void reportLocation(android.location.Location);
-    method @Deprecated public void reportLocation(android.location.LocationResult);
+    method @Deprecated public void reportLocation(@NonNull android.location.Location);
+    method @Deprecated public void reportLocations(@NonNull java.util.List<android.location.Location>);
     method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setAdditionalProviderPackages(java.util.List<java.lang.String>);
     method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.R) public void setAllowed(boolean);
     method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.Q) public void setEnabled(boolean);
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index aea93ce..7f1cf6d 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -16,13 +16,13 @@
 
 package com.android.location.provider;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.location.ILocationManager;
 import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationProvider;
-import android.location.LocationResult;
 import android.location.provider.ILocationProvider;
 import android.location.provider.ILocationProviderManager;
 import android.location.provider.ProviderProperties;
@@ -39,6 +39,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -94,10 +95,6 @@
      */
     public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER;
 
-    private static final String EXTRA_KEY_COARSE_LOCATION = "coarseLocation";
-    private static final String EXTRA_KEY_NO_GPS_LOCATION = "noGPSLocation";
-    private static final String EXTRA_KEY_INDOOR_PROB = "indoorProbability";
-
     final String mTag;
     @Nullable final String mPackageName;
     @Nullable final String mAttributionTag;
@@ -254,20 +251,11 @@
     /**
      * Reports a new location from this provider.
      */
-    public void reportLocation(Location location) {
-        reportLocation(LocationResult.create(location));
-    }
-
-    /**
-     * Reports a new location from this provider.
-     */
-    public void reportLocation(LocationResult locationResult) {
+    public void reportLocation(@NonNull Location location) {
         ILocationProviderManager manager = mManager;
         if (manager != null) {
-            locationResult = locationResult.map(this::cleanUpExtras);
-
             try {
-                manager.onReportLocation(locationResult);
+                manager.onReportLocation(stripExtras(location));
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             } catch (RuntimeException e) {
@@ -277,30 +265,20 @@
     }
 
     /**
-     * Remove deprecated/unnecessary extras to save on serialization costs.
-     *
-     * {@link #EXTRA_KEY_NO_GPS_LOCATION} and {@link #EXTRA_KEY_COARSE_LOCATION} are deprecated.
-     *
-     * {@link #EXTRA_KEY_INDOOR_PROB} should only be used in the framework.
+     * Reports a new batch of locations from this provider. Locations must be ordered in the list
+     * from earliest first to latest last.
      */
-    private Location cleanUpExtras(Location location) {
-        Bundle extras = location.getExtras();
-        if (extras == null) {
-            return location;
-        }
-        if (extras.containsKey(EXTRA_KEY_NO_GPS_LOCATION)
-                || extras.containsKey(EXTRA_KEY_COARSE_LOCATION)
-                || extras.containsKey(EXTRA_KEY_INDOOR_PROB)) {
-            location = new Location(location);
-            extras = location.getExtras();
-            extras.remove(EXTRA_KEY_NO_GPS_LOCATION);
-            extras.remove(EXTRA_KEY_COARSE_LOCATION);
-            extras.remove(EXTRA_KEY_INDOOR_PROB);
-            if (extras.isEmpty()) {
-                location.setExtras(null);
+    public void reportLocations(@NonNull List<Location> locations) {
+        ILocationProviderManager manager = mManager;
+        if (manager != null) {
+            try {
+                manager.onReportLocations(stripExtras(locations));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            } catch (RuntimeException e) {
+                Log.w(mTag, e);
             }
         }
-        return location;
     }
 
     protected void onInit() {
@@ -336,9 +314,9 @@
 
     /**
      * Requests a flush of any pending batched locations. The callback must always be invoked once
-     * per invocation, and should be invoked after {@link #reportLocation(LocationResult)} has been
-     * invoked with any flushed locations. The callback may be invoked immediately if no locations
-     * are flushed.
+     * per invocation, and should be invoked after {@link #reportLocation(Location)} or
+     * {@link #reportLocations(List)} has been invoked with any flushed locations. The callback may
+     * be invoked immediately if no locations are flushed.
      */
     protected void onFlush(OnFlushCompleteCallback callback) {
         callback.onFlushComplete();
@@ -433,4 +411,47 @@
             onSendExtraCommand(command, extras);
         }
     }
+
+    private static Location stripExtras(Location location) {
+        Bundle extras = location.getExtras();
+        if (extras != null && (extras.containsKey(EXTRA_NO_GPS_LOCATION)
+                || extras.containsKey("indoorProbability")
+                || extras.containsKey("coarseLocation"))) {
+            location = new Location(location);
+            extras = location.getExtras();
+            extras.remove(EXTRA_NO_GPS_LOCATION);
+            extras.remove("indoorProbability");
+            extras.remove("coarseLocation");
+            if (extras.isEmpty()) {
+                location.setExtras(null);
+            }
+        }
+        return location;
+    }
+
+    private static List<Location> stripExtras(List<Location> locations) {
+        List<Location> mapped = locations;
+        final int size = locations.size();
+        int i = 0;
+        for (Location location : locations) {
+            Location newLocation = stripExtras(location);
+            if (mapped != locations) {
+                mapped.add(newLocation);
+            } else if (newLocation != location) {
+                mapped = new ArrayList<>(size);
+                int j = 0;
+                for (Location copiedLocation : locations) {
+                    if (j >= i) {
+                        break;
+                    }
+                    mapped.add(copiedLocation);
+                    j++;
+                }
+                mapped.add(newLocation);
+            }
+            i++;
+        }
+
+        return mapped;
+    }
 }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index bcc846b..bb1dbd4 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -739,6 +739,13 @@
             if (mBundle != null) {
                 aa.mBundle = new Bundle(mBundle);
             }
+
+            // Allow the FLAG_HW_HOTWORD only for AudioSource.VOICE_RECOGNITION
+            if (mSource != MediaRecorder.AudioSource.VOICE_RECOGNITION
+                    && (mFlags & FLAG_HW_HOTWORD) == FLAG_HW_HOTWORD) {
+                aa.mFlags &= ~FLAG_HW_HOTWORD;
+            }
+
             return aa;
         }
 
@@ -852,6 +859,23 @@
         }
 
         /**
+         * @hide
+         * Request for capture in hotword mode.
+         *
+         * Requests an audio path optimized for Hotword detection use cases from
+         * the low power audio DSP. This is valid only for capture with
+         * audio source {@link MediaRecorder.AudioSource#VOICE_RECOGNITION}.
+         * There is no guarantee that this mode is available on the device.
+         * @return the same Builder instance.
+         */
+        @SystemApi
+        @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD)
+        public @NonNull Builder setHotwordMode() {
+            mFlags |= FLAG_HW_HOTWORD;
+            return this;
+        }
+
+        /**
          * Specifies whether the audio may or may not be captured by other apps or the system.
          *
          * The default is {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}.
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 255c15c..eedb996 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -316,6 +316,15 @@
      * Not guaranteed to be supported by devices, may be emulated if not supported. */
     public static final int ENCODING_PCM_32BIT = 22;
 
+    /** Audio data format: MPEG-H baseline profile, level 3 */
+    public static final int ENCODING_MPEGH_BL_L3 = 23;
+    /** Audio data format: MPEG-H baseline profile, level 4 */
+    public static final int ENCODING_MPEGH_BL_L4 = 24;
+    /** Audio data format: MPEG-H low complexity profile, level 3 */
+    public static final int ENCODING_MPEGH_LC_L3 = 25;
+    /** Audio data format: MPEG-H low complexity profile, level 4 */
+    public static final int ENCODING_MPEGH_LC_L4 = 26;
+
     /** @hide */
     public static String toLogFriendlyEncoding(int enc) {
         switch(enc) {
@@ -363,6 +372,14 @@
                 return "ENCODING_PCM_24BIT_PACKED";
             case ENCODING_PCM_32BIT:
                 return "ENCODING_PCM_32BIT";
+            case ENCODING_MPEGH_BL_L3:
+                return "ENCODING_MPEGH_BL_L3";
+            case ENCODING_MPEGH_BL_L4:
+                return "ENCODING_MPEGH_BL_L4";
+            case ENCODING_MPEGH_LC_L3:
+                return "ENCODING_MPEGH_LC_L3";
+            case ENCODING_MPEGH_LC_L4:
+                return "ENCODING_MPEGH_LC_L4";
             default :
                 return "invalid encoding " + enc;
         }
@@ -594,6 +611,10 @@
             case ENCODING_OPUS:
             case ENCODING_PCM_24BIT_PACKED:
             case ENCODING_PCM_32BIT:
+            case ENCODING_MPEGH_BL_L3:
+            case ENCODING_MPEGH_BL_L4:
+            case ENCODING_MPEGH_LC_L3:
+            case ENCODING_MPEGH_LC_L4:
                 return true;
             default:
                 return false;
@@ -625,6 +646,10 @@
             case ENCODING_OPUS:
             case ENCODING_PCM_24BIT_PACKED:
             case ENCODING_PCM_32BIT:
+            case ENCODING_MPEGH_BL_L3:
+            case ENCODING_MPEGH_BL_L4:
+            case ENCODING_MPEGH_LC_L3:
+            case ENCODING_MPEGH_LC_L4:
                 return true;
             default:
                 return false;
@@ -659,6 +684,10 @@
             case ENCODING_E_AC3_JOC:
             case ENCODING_DOLBY_MAT:
             case ENCODING_OPUS:
+            case ENCODING_MPEGH_BL_L3:
+            case ENCODING_MPEGH_BL_L4:
+            case ENCODING_MPEGH_LC_L3:
+            case ENCODING_MPEGH_LC_L4:
                 return false;
             case ENCODING_INVALID:
             default:
@@ -693,6 +722,10 @@
             case ENCODING_E_AC3_JOC:
             case ENCODING_DOLBY_MAT:
             case ENCODING_OPUS:
+            case ENCODING_MPEGH_BL_L3:
+            case ENCODING_MPEGH_BL_L4:
+            case ENCODING_MPEGH_LC_L3:
+            case ENCODING_MPEGH_LC_L4:
                 return false;
             case ENCODING_INVALID:
             default:
@@ -975,6 +1008,10 @@
                 case ENCODING_OPUS:
                 case ENCODING_PCM_24BIT_PACKED:
                 case ENCODING_PCM_32BIT:
+                case ENCODING_MPEGH_BL_L3:
+                case ENCODING_MPEGH_BL_L4:
+                case ENCODING_MPEGH_LC_L3:
+                case ENCODING_MPEGH_LC_L4:
                     mEncoding = encoding;
                     break;
                 case ENCODING_INVALID:
@@ -1197,7 +1234,11 @@
         ENCODING_DOLBY_MAT,
         ENCODING_OPUS,
         ENCODING_PCM_24BIT_PACKED,
-        ENCODING_PCM_32BIT }
+        ENCODING_PCM_32BIT,
+        ENCODING_MPEGH_BL_L3,
+        ENCODING_MPEGH_BL_L4,
+        ENCODING_MPEGH_LC_L3,
+        ENCODING_MPEGH_LC_L4 }
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface Encoding {}
@@ -1213,6 +1254,10 @@
             ENCODING_AC4,
             ENCODING_E_AC3_JOC,
             ENCODING_DOLBY_MAT,
+            ENCODING_MPEGH_BL_L3,
+            ENCODING_MPEGH_BL_L4,
+            ENCODING_MPEGH_LC_L3,
+            ENCODING_MPEGH_LC_L4,
     };
 
     /** @hide */
@@ -1225,7 +1270,11 @@
             ENCODING_DOLBY_TRUEHD,
             ENCODING_AC4,
             ENCODING_E_AC3_JOC,
-            ENCODING_DOLBY_MAT }
+            ENCODING_DOLBY_MAT,
+            ENCODING_MPEGH_BL_L3,
+            ENCODING_MPEGH_BL_L4,
+            ENCODING_MPEGH_LC_L3,
+            ENCODING_MPEGH_LC_L4 }
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface SurroundSoundEncoding {}
@@ -1259,6 +1308,14 @@
                 return "Dolby Atmos in Dolby Digital Plus";
             case ENCODING_DOLBY_MAT:
                 return "Dolby MAT";
+            case ENCODING_MPEGH_BL_L3:
+                return "MPEG-H 3D Audio baseline profile level 3";
+            case ENCODING_MPEGH_BL_L4:
+                return "MPEG-H 3D Audio baseline profile level 4";
+            case ENCODING_MPEGH_LC_L3:
+                return "MPEG-H 3D Audio low complexity profile level 3";
+            case ENCODING_MPEGH_LC_L4:
+                return "MPEG-H 3D Audio low complexity profile level 4";
             default:
                 return "Unknown surround sound format";
         }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 08deb15..8d090f8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1,5 +1,4 @@
 /*
-/*
  * Copyright (C) 2007 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index c2ce7d3..b196437 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
@@ -67,7 +68,14 @@
  * fill with the new audio data. The size of this buffer, specified during the construction,
  * determines how long an AudioRecord can record before "over-running" data that has not
  * been read yet. Data should be read from the audio hardware in chunks of sizes inferior to
- * the total recording buffer size.
+ * the total recording buffer size.</p>
+ * <p>
+ * Applications creating an AudioRecord instance need
+ * {@link android.Manifest.permission#RECORD_AUDIO} or the Builder will throw
+ * {@link java.lang.UnsupportedOperationException} on
+ * {@link android.media.AudioRecord.Builder#build build()},
+ * and the constructor will return an instance in state
+ * {@link #STATE_UNINITIALIZED}.</p>
  */
 public class AudioRecord implements AudioRouting, MicrophoneDirection,
         AudioRecordingMonitor, AudioRecordingMonitorClient
@@ -297,6 +305,7 @@
      *   smaller than getMinBufferSize() will result in an initialization failure.
      * @throws java.lang.IllegalArgumentException
      */
+    @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
     public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,
             int bufferSizeInBytes)
     throws IllegalArgumentException {
@@ -334,6 +343,7 @@
      * @throws IllegalArgumentException
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
     public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
             int sessionId) throws IllegalArgumentException {
         mRecordingState = RECORDSTATE_STOPPED;
@@ -718,6 +728,7 @@
          *     were incompatible, or if they are not supported by the device,
          *     or if the device was not available.
          */
+        @RequiresPermission(android.Manifest.permission.RECORD_AUDIO)
         public AudioRecord build() throws UnsupportedOperationException {
             if (mAudioPlaybackCaptureConfiguration != null) {
                 return buildAudioPlaybackCaptureRecord();
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index d248f61..7837d7e 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -45,6 +45,7 @@
             case ImageFormat.YV12:
             case ImageFormat.YUV_420_888:
             case ImageFormat.NV21:
+            case ImageFormat.YCBCR_P010:
                 return 3;
             case ImageFormat.NV16:
                 return 2;
@@ -225,6 +226,7 @@
             case ImageFormat.RAW_SENSOR:
             case ImageFormat.RAW_PRIVATE: // round estimate, real size is unknown
             case ImageFormat.DEPTH16:
+            case ImageFormat.YCBCR_P010:
                 estimatedBytePerPixel = 2.0;
                 break;
             case PixelFormat.RGB_888:
@@ -244,6 +246,7 @@
 
     private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) {
         switch (image.getFormat()) {
+            case ImageFormat.YCBCR_P010:
             case ImageFormat.YV12:
             case ImageFormat.YUV_420_888:
             case ImageFormat.NV21:
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 9657b25e..4d7ed11 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1089,7 +1089,7 @@
 
         private int[] mSampleRates;
         private Range<Integer>[] mSampleRateRanges;
-        private int mMaxInputChannelCount;
+        private Range<Integer>[] mInputChannelRanges;
 
         private static final int MAX_INPUT_CHANNEL_COUNT = 30;
 
@@ -1119,11 +1119,61 @@
         }
 
         /**
-         * Returns the maximum number of input channels supported.  The codec
-         * supports any number of channels between 1 and this maximum value.
+         * Returns the maximum number of input channels supported.
+         *
+         * Through {@link android.os.Build.VERSION_CODES#R}, this method indicated support
+         * for any number of input channels between 1 and this maximum value.
+         *
+         * As of {@link android.os.Build.VERSION_CODES#S},
+         * the implied lower limit of 1 channel is no longer valid.
+         * As of {@link android.os.Build.VERSION_CODES#S}, {@link #getMaxInputChannelCount} is
+         * superseded by {@link #getInputChannelCountRanges},
+         * which returns an array of ranges of channels.
+         * The {@link #getMaxInputChannelCount} method will return the highest value
+         * in the ranges returned by {@link #getInputChannelCountRanges}
+         *
          */
         public int getMaxInputChannelCount() {
-            return mMaxInputChannelCount;
+            int overall_max = 0;
+            for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+                int lmax = mInputChannelRanges[i].getUpper();
+                if (lmax > overall_max) {
+                    overall_max = lmax;
+                }
+            }
+            return overall_max;
+        }
+
+        /**
+         * Returns the minimum number of input channels supported.
+         * This is often 1, but does vary for certain mime types.
+         *
+         * This returns the lowest channel count in the ranges returned by
+         * {@link #getInputChannelCountRanges}.
+         */
+        public int getMinInputChannelCount() {
+            int overall_min = MAX_INPUT_CHANNEL_COUNT;
+            for (int i = mInputChannelRanges.length - 1; i >= 0; i--) {
+                int lmin = mInputChannelRanges[i].getLower();
+                if (lmin < overall_min) {
+                    overall_min = lmin;
+                }
+            }
+            return overall_min;
+        }
+
+        /*
+         * Returns an array of ranges representing the number of input channels supported.
+         * The codec supports any number of input channels within this range.
+         *
+         * This supersedes the {@link #getMaxInputChannelCount} method.
+         *
+         * For many codecs, this will be a single range [1..N], for some N.
+         */
+        @SuppressLint("ArrayReturn")
+        @NonNull
+        public Range<Integer>[] getInputChannelCountRanges() {
+            return Arrays.copyOf(mInputChannelRanges, mInputChannelRanges.length);
         }
 
         /* no public constructor */
@@ -1146,7 +1196,7 @@
 
         private void initWithPlatformLimits() {
             mBitrateRange = Range.create(0, Integer.MAX_VALUE);
-            mMaxInputChannelCount = MAX_INPUT_CHANNEL_COUNT;
+            mInputChannelRanges = new Range[] {Range.create(1, MAX_INPUT_CHANNEL_COUNT)};
             // mBitrateRange = Range.create(1, 320000);
             final int minSampleRate = SystemProperties.
                 getInt("ro.mediacodec.min_sample_rate", 7350);
@@ -1158,9 +1208,12 @@
 
         private boolean supports(Integer sampleRate, Integer inputChannels) {
             // channels and sample rates are checked orthogonally
-            if (inputChannels != null &&
-                    (inputChannels < 1 || inputChannels > mMaxInputChannelCount)) {
-                return false;
+            if (inputChannels != null) {
+                int ix = Utils.binarySearchDistinctRanges(
+                        mInputChannelRanges, inputChannels);
+                if (ix < 0) {
+                    return false;
+                }
             }
             if (sampleRate != null) {
                 int ix = Utils.binarySearchDistinctRanges(
@@ -1292,12 +1345,28 @@
             } else if (sampleRateRange != null) {
                 limitSampleRates(new Range[] { sampleRateRange });
             }
-            applyLimits(maxChannels, bitRates);
+
+            Range<Integer> channelRange = Range.create(1, maxChannels);
+
+            applyLimits(new Range[] { channelRange }, bitRates);
         }
 
-        private void applyLimits(int maxInputChannels, Range<Integer> bitRates) {
-            mMaxInputChannelCount = Range.create(1, mMaxInputChannelCount)
-                    .clamp(maxInputChannels);
+        private void applyLimits(Range<Integer>[] inputChannels, Range<Integer> bitRates) {
+
+            // clamp & make a local copy
+            Range<Integer>[] myInputChannels = new Range[inputChannels.length];
+            for (int i = 0; i < inputChannels.length; i++) {
+                int lower = inputChannels[i].clamp(1);
+                int upper = inputChannels[i].clamp(MAX_INPUT_CHANNEL_COUNT);
+                myInputChannels[i] = Range.create(lower, upper);
+            }
+
+            // sort, intersect with existing, & save channel list
+            sortDistinctRanges(myInputChannels);
+            Range<Integer>[] joinedChannelList =
+                            intersectSortedDistinctRanges(myInputChannels, mInputChannelRanges);
+            mInputChannelRanges = joinedChannelList;
+
             if (bitRates != null) {
                 mBitrateRange = mBitrateRange.intersect(bitRates);
             }
@@ -1305,6 +1374,7 @@
 
         private void parseFromInfo(MediaFormat info) {
             int maxInputChannels = MAX_INPUT_CHANNEL_COUNT;
+            Range<Integer>[] channels = new Range[] { Range.create(1, maxInputChannels)};
             Range<Integer> bitRates = POSITIVE_INTEGERS;
 
             if (info.containsKey("sample-rate-ranges")) {
@@ -1315,17 +1385,38 @@
                 }
                 limitSampleRates(rateRanges);
             }
-            if (info.containsKey("max-channel-count")) {
+
+            // we will prefer channel-ranges over max-channel-count
+            if (info.containsKey("channel-ranges")) {
+                String[] channelStrings = info.getString("channel-ranges").split(",");
+                Range<Integer>[] channelRanges = new Range[channelStrings.length];
+                for (int i = 0; i < channelStrings.length; i++) {
+                    channelRanges[i] = Utils.parseIntRange(channelStrings[i], null);
+                }
+                channels = channelRanges;
+            } else if (info.containsKey("channel-range")) {
+                Range<Integer> oneRange = Utils.parseIntRange(info.getString("channel-range"),
+                                                              null);
+                channels = new Range[] { oneRange };
+            } else if (info.containsKey("max-channel-count")) {
                 maxInputChannels = Utils.parseIntSafely(
                         info.getString("max-channel-count"), maxInputChannels);
+                if (maxInputChannels == 0) {
+                    channels = new Range[] {Range.create(0, 0)};
+                } else {
+                    channels = new Range[] {Range.create(1, maxInputChannels)};
+                }
             } else if ((mParent.mError & ERROR_UNSUPPORTED) != 0) {
                 maxInputChannels = 0;
+                channels = new Range[] {Range.create(0, 0)};
             }
+
             if (info.containsKey("bitrate-range")) {
                 bitRates = bitRates.intersect(
                         Utils.parseIntRange(info.getString("bitrate-range"), bitRates));
             }
-            applyLimits(maxInputChannels, bitRates);
+
+            applyLimits(channels, bitRates);
         }
 
         /** @hide */
@@ -1334,7 +1425,7 @@
             if (mBitrateRange.getLower().equals(mBitrateRange.getUpper())) {
                 format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrateRange.getLower());
             }
-            if (mMaxInputChannelCount == 1) {
+            if (getMaxInputChannelCount() == 1) {
                 // mono-only format
                 format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
             }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 68237de..5e732f9 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -879,38 +879,37 @@
     /**
      * Interface for receiving events about media routing changes.
      */
-    public static class Callback {
-
+    public interface Callback {
         /**
          * Called when routes are added.
          * @param routes the list of routes that have been added. It's never empty.
          */
-        public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
+        default void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
          * Called when routes are removed.
          * @param routes the list of routes that have been removed. It's never empty.
          */
-        public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
+        default void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
          * Called when routes are changed.
          * @param routes the list of routes that have been changed. It's never empty.
          */
-        public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+        default void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
          * Called when a session is changed.
          * @param session the updated session
          */
-        public void onSessionUpdated(@NonNull RoutingSessionInfo session) {}
+        default void onSessionUpdated(@NonNull RoutingSessionInfo session) {}
 
         /**
          * Called when a session is released.
          * @param session the released session.
          * @see #releaseSession(RoutingSessionInfo)
          */
-        public void onSessionReleased(@NonNull RoutingSessionInfo session) {}
+        default void onSessionReleased(@NonNull RoutingSessionInfo session) {}
 
         /**
          * Called when media is transferred.
@@ -918,13 +917,13 @@
          * @param oldSession the previous session
          * @param newSession the new session or {@code null} if the session is released.
          */
-        public void onTransferred(@NonNull RoutingSessionInfo oldSession,
+        default void onTransferred(@NonNull RoutingSessionInfo oldSession,
                 @Nullable RoutingSessionInfo newSession) { }
 
         /**
          * Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
          */
-        public void onTransferFailed(@NonNull RoutingSessionInfo session,
+        default void onTransferFailed(@NonNull RoutingSessionInfo session,
                 @NonNull MediaRoute2Info route) { }
 
         /**
@@ -933,7 +932,7 @@
          * @param packageName the package name of the application
          * @param preferredFeatures the list of preferred route features set by an application.
          */
-        public void onPreferredFeaturesChanged(@NonNull String packageName,
+        default void onPreferredFeaturesChanged(@NonNull String packageName,
                 @NonNull List<String> preferredFeatures) {}
 
         /**
@@ -946,7 +945,7 @@
          *               {@link MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
          *               {@link MediaRoute2ProviderService#REASON_INVALID_COMMAND},
          */
-        public void onRequestFailed(int reason) {}
+        default void onRequestFailed(int reason) {}
     }
 
     final class CallbackRecord {
diff --git a/media/java/android/media/MediaServiceManager.java b/media/java/android/media/MediaServiceManager.java
index 96bff4f..b899559 100644
--- a/media/java/android/media/MediaServiceManager.java
+++ b/media/java/android/media/MediaServiceManager.java
@@ -33,6 +33,7 @@
 public class MediaServiceManager {
     private static final String MEDIA_SESSION_SERVICE = "media_session";
     private static final String MEDIA_TRANSCODING_SERVICE = "media.transcoding";
+    private static final String MEDIA_COMMUNICATION_SERVICE = "media_communication";
 
     /**
      * @hide
@@ -79,4 +80,12 @@
     public ServiceRegisterer getMediaTranscodingServiceRegisterer() {
         return new ServiceRegisterer(MEDIA_TRANSCODING_SERVICE);
     }
+
+    /**
+     * Returns {@link ServiceRegisterer} for MEDIA_COMMUNICATION_SERVICE.
+     */
+    @NonNull
+    public ServiceRegisterer getMediaCommunicationServiceRegisterer() {
+        return new ServiceRegisterer(MEDIA_COMMUNICATION_SERVICE);
+    }
 }
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionManager.java b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
index 6bbcfd3..b183eaf 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionManager.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionManager.java
@@ -23,6 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
@@ -90,7 +91,9 @@
          *               supplied bundle
          */
         void onRecognitionSucceeded(@NonNull RecognitionRequest recognitionRequest,
-                @NonNull MediaMetadata result, @Nullable Bundle extras);
+                @NonNull MediaMetadata result,
+                @SuppressLint("NullableCollection")
+                @Nullable Bundle extras);
 
         /**
          * Invoked when the search is not successful (possibly but not necessarily due to error).
diff --git a/media/java/android/media/musicrecognition/MusicRecognitionService.java b/media/java/android/media/musicrecognition/MusicRecognitionService.java
index e2071b8..04b4c39b 100644
--- a/media/java/android/media/musicrecognition/MusicRecognitionService.java
+++ b/media/java/android/media/musicrecognition/MusicRecognitionService.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
@@ -53,7 +54,9 @@
          * @param extras extra data to be supplied back to the caller. Note that all executable
          *               parameters and file descriptors would be removed from the supplied bundle
          */
-        void onRecognitionSucceeded(@NonNull MediaMetadata result, @Nullable Bundle extras);
+        void onRecognitionSucceeded(@NonNull MediaMetadata result,
+                @SuppressLint("NullableCollection")
+                @Nullable Bundle extras);
 
         /**
          * Call this method if the search does not find a result on an error occurred.
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 77f7b54..31fb8d0 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -16,6 +16,7 @@
 package android.media.session;
 
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
@@ -35,6 +36,7 @@
     void setFlags(int flags);
     void setActive(boolean active);
     void setMediaButtonReceiver(in PendingIntent mbr);
+    void setMediaButtonBroadcastReceiver(in ComponentName broadcastReceiver);
     void setLaunchPendingIntent(in PendingIntent pi);
     void destroySession();
 
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 14b2368..24118b0 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -23,6 +23,7 @@
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioAttributes;
@@ -131,6 +132,7 @@
     public @interface SessionFlags { }
 
     private final Object mLock = new Object();
+    private Context mContext;
     private final int mMaxBitmapSize;
 
     private final Token mSessionToken;
@@ -194,6 +196,7 @@
                     + "parcelables");
         }
 
+        mContext = context;
         mMaxBitmapSize = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
         mCbStub = new CallbackStub(this);
@@ -277,7 +280,10 @@
      *
      * @param mbr The {@link PendingIntent} to send the media button event to.
      * @see PendingIntent#getActivity
+     *
+     * @deprecated Use {@link #setMediaButtonBroadcastReceiver(ComponentName)} instead.
      */
+    @Deprecated
     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
         try {
             mBinder.setMediaButtonReceiver(mbr);
@@ -287,6 +293,32 @@
     }
 
     /**
+     * Set the component name of the manifest-declared {@link android.content.BroadcastReceiver}
+     * class that should receive media buttons. This allows restarting playback after the session
+     * has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON}
+     * intent will be sent to the broadcast receiver.
+     * <p>
+     * Note: The given {@link android.content.BroadcastReceiver} should belong to the same package
+     * as the context that was given when creating {@link MediaSession}.
+     *
+     * @param broadcastReceiver the component name of the BroadcastReceiver class
+     */
+    public void setMediaButtonBroadcastReceiver(@Nullable ComponentName broadcastReceiver) {
+        try {
+            if (broadcastReceiver != null) {
+                if (!TextUtils.equals(broadcastReceiver.getPackageName(),
+                        mContext.getPackageName())) {
+                    throw new IllegalArgumentException("broadcastReceiver should belong to the same"
+                            + " package as the context given when creating MediaSession.");
+                }
+            }
+            mBinder.setMediaButtonBroadcastReceiver(broadcastReceiver);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Failure in setMediaButtonBroadcastReceiver.", e);
+        }
+    }
+
+    /**
      * Set any flags for the session.
      *
      * @param flags The flags to set for this session.
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 4d835b2..ee0be01 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.hardware.tv.tuner.V1_0.Constants;
@@ -64,6 +65,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -244,6 +246,8 @@
     private static final int MSG_ON_FILTER_STATUS = 3;
     private static final int MSG_ON_LNB_EVENT = 4;
 
+    private static final int FILTER_CLEANUP_THRESHOLD = 256;
+
     /** @hide */
     @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
     @Retention(RetentionPolicy.SOURCE)
@@ -1017,6 +1021,7 @@
      * failed.
      */
     @Nullable
+    @SuppressLint("NullableCollection")
     public List<FrontendInfo> getAvailableFrontendInfos() {
         FrontendInfo[] feInfoList = getFrontendInfoListInternal();
         if (feInfoList == null) {
@@ -1206,6 +1211,15 @@
             synchronized (mFilters) {
                 WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter);
                 mFilters.add(weakFilter);
+                if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
+                    Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
+                    while (iterator.hasNext()) {
+                        WeakReference<Filter> wFilter = iterator.next();
+                        if (wFilter.get() == null) {
+                            iterator.remove();
+                        }
+                    }
+                }
             }
         }
         return filter;
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 96371e5..51b685a 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -209,7 +209,7 @@
             prefix = "MONITOR_EVENT_",
             value = {MONITOR_EVENT_SCRAMBLING_STATUS, MONITOR_EVENT_IP_CID_CHANGE})
     @Retention(RetentionPolicy.SOURCE)
-    public @interface MonitorEventTypeMask {}
+    public @interface MonitorEventMask {}
 
     /**
      * Monitor scrambling status change.
@@ -239,7 +239,7 @@
             int type, int subType, FilterConfiguration settings);
     private native int nativeGetId();
     private native long nativeGetId64Bit();
-    private native int nativeConfigureMonitorEvent(int monitorEventTypesMask);
+    private native int nativeConfigureMonitorEvent(int monitorEventMask);
     private native int nativeSetDataSource(Filter source);
     private native int nativeStartFilter();
     private native int nativeStopFilter();
@@ -284,6 +284,12 @@
     /**
      * Configures the filter.
      *
+     * <p>Recofiguring must happen after stopping the filter.
+     *
+     * <p>When stopping, reconfiguring and restarting the filter, the client should discard all
+     * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
+     * using the events from the previous configuration.
+     *
      * @param config the configuration of the filter.
      * @return result status of the operation.
      */
@@ -344,19 +350,19 @@
      * will cause no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the version
      * information.
      *
-     * @param monitorEventTypesMask Types of event to be monitored. Set corresponding bit to
-     *                              monitor it. Reset to stop monitoring.
+     * @param monitorEventMask Types of event to be monitored. Set corresponding bit to
+     *                         monitor it. Reset to stop monitoring.
      * @return result status of the operation.
      */
     @Result
-    public int configureMonitorEvent(@MonitorEventTypeMask int monitorEventTypesMask) {
+    public int setMonitorEventMask(@MonitorEventMask int monitorEventMask) {
         synchronized (mLock) {
             TunerUtils.checkResourceState(TAG, mIsClosed);
             if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
-                    TunerVersionChecker.TUNER_VERSION_1_1, "configureMonitorEvent")) {
+                    TunerVersionChecker.TUNER_VERSION_1_1, "setMonitorEventMask")) {
                 return Tuner.RESULT_UNAVAILABLE;
             }
-            return nativeConfigureMonitorEvent(monitorEventTypesMask);
+            return nativeConfigureMonitorEvent(monitorEventMask);
         }
     }
 
@@ -393,6 +399,10 @@
      *
      * <p>Does nothing if the filter is already started.
      *
+     * <p>When stopping, reconfiguring and restarting the filter, the client should discard all
+     * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
+     * using the events from the previous configuration.
+     *
      * @return result status of the operation.
      */
     @Result
@@ -409,6 +419,12 @@
      *
      * <p>Does nothing if the filter is stopped or not started.
      *
+     * <p>Filter must be stopped to reconfigure.
+     *
+     * <p>When stopping, reconfiguring and restarting the filter, the client should discard all
+     * coming events until it receives {@link RestartEvent} through {@link FilterCallback} to avoid
+     * using the events from the previous configuration.
+     *
      * @return result status of the operation.
      */
     @Result
diff --git a/media/java/android/media/tv/tuner/filter/RestartEvent.java b/media/java/android/media/tv/tuner/filter/RestartEvent.java
index 0696301..9c5992a 100644
--- a/media/java/android/media/tv/tuner/filter/RestartEvent.java
+++ b/media/java/android/media/tv/tuner/filter/RestartEvent.java
@@ -19,17 +19,29 @@
 import android.annotation.SystemApi;
 
 /**
- * An Event that the client would reveice after stopping, reconfiguring and restarting a filter.
+ * An Event that the client would receive after starting a filter. This event is optional to be
+ * received on the newly opened and started filter. It must be received after stopping,
+ * reconfiguring and restarting a Filter to differentiate the valid reconfigured events from the
+ * previous events.
  *
  * <p>After stopping and restarting the filter, the client has to discard all coming events until
  * it receives {@link RestartEvent} to avoid using the events from the previous configuration.
  *
  * <p>Recofiguring must happen after stopping the filter.
  *
+ * @see Filter#stop()
+ * @see Filter#start()
+ * @see Filter#configure(FilterConfiguration)
+ *
  * @hide
  */
 @SystemApi
 public final class RestartEvent extends FilterEvent {
+    /**
+     * The stard id reserved for the newly opened filter's first start event.
+     */
+    public static final int NEW_FILTER_FIRST_START_ID = 0;
+
     private final int mStartId;
 
     // This constructor is used by JNI code only
@@ -38,13 +50,13 @@
     }
 
     /**
-     * Gets the start id.
+     * Gets the start id sent via the current Restart Event.
      *
-     * <p>An unique ID to mark the start point of receiving the valid filter events after
-     * reconfiguring. It must be sent at least once in the first event after the filter is
-     * restarted.
+     * <p>An unique ID to mark the start point of receiving the valid reconfigured filter events.
+     * The client must receive at least once after the filter is reconfigured and restarted.
      *
-     * <p>0 is reserved for the newly opened filter's first start. It's optional to be received.
+     * <p>{@link #NEW_FILTER_FIRST_START_ID} is reserved for the newly opened filter's first start.
+     * It's optional to be received.
      */
     public int getStartId() {
         return mStartId;
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index decf68f..65b64d7 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -31,8 +31,8 @@
     ],
 
     shared_libs: [
-        "audioclient-types-aidl-unstable-cpp",
-        "av-types-aidl-unstable-cpp",
+        "audioclient-types-aidl-cpp",
+        "av-types-aidl-cpp",
         "libandroid_runtime",
         "libaudioclient",
         "libnativehelper",
@@ -166,6 +166,11 @@
         "tv_tuner_aidl_interface-ndk_platform",
         "tv_tuner_resource_manager_aidl_interface-ndk_platform"
     ],
+
+    static_libs: [
+        "libaidlcommonsupport",
+    ],
+
     defaults: [
         "libcodec2-impl-defaults",
     ],
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index c064de2..08a8d89 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,7 +18,6 @@
 
 #include <binder/Parcel.h>
 #include <jni.h>
-#include <media/IMediaMetricsService.h>
 #include <media/MediaMetricsItem.h>
 #include <nativehelper/JNIHelp.h>
 #include <variant>
@@ -151,12 +150,7 @@
         return (jint)BAD_VALUE;
     }
 
-    sp<IMediaMetricsService> service = mediametrics::BaseItem::getService();
-    if (service == nullptr) {
-        ALOGW("Cannot retrieve mediametrics service");
-        return (jint)NO_INIT;
-    }
-    return (jint)service->submitBuffer((char *)buffer, length);
+    return (jint)mediametrics::BaseItem::submitBuffer((char *)buffer, length);
 }
 
 // Helper function to convert a native PersistableBundle to a Java
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index b6c47fca..ecd9cc1 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -69,6 +69,7 @@
         case HAL_PIXEL_FORMAT_RAW_OPAQUE:
         case HAL_PIXEL_FORMAT_BLOB:
         case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+        case HAL_PIXEL_FORMAT_YCBCR_P010:
             return false;
 
         case HAL_PIXEL_FORMAT_YV12:
@@ -261,6 +262,32 @@
             pStride = 1;
             rStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16);
             break;
+        case HAL_PIXEL_FORMAT_YCBCR_P010:
+            if (buffer->height % 2 != 0) {
+                ALOGE("YCBCR_P010: height (%d) should be a multiple of 2", buffer->height);
+                return BAD_VALUE;
+            }
+
+            if (buffer->width <= 0) {
+                ALOGE("YCBCR_P010: width (%d) should be a > 0", buffer->width);
+                return BAD_VALUE;
+            }
+
+            if (buffer->height <= 0) {
+                ALOGE("YCBCR_P010: height (%d) should be a > 0", buffer->height);
+                return BAD_VALUE;
+            }
+
+            ySize = (buffer->stride * 2) * buffer->height;
+            cSize = ySize / 2;
+            pStride = (idx == 0) ? 2 : 4;
+            cb = buffer->data + ySize;
+            cr = cb + 2;
+
+            pData = (idx == 0) ?  buffer->data : (idx == 1) ?  cb : cr;
+            dataSize = (idx == 0) ? ySize : cSize;
+            rStride = buffer->stride * 2;
+            break;
         case HAL_PIXEL_FORMAT_Y8:
             // Single plane, 8bpp.
             LOG_ALWAYS_FATAL_IF(idx != 0, "Wrong index: %d", idx);
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index eb63d76..eee9f1e 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -1458,7 +1458,7 @@
         ALOGE("frontend is not initialized");
         return (int)Result::INVALID_STATE;
     }
-    return (int) mFeClient->tune(settings,settingsExt1_1);
+    return (int) mFeClient->tune(settings, settingsExt1_1);
 }
 
 int JTuner::stopTune() {
@@ -3786,7 +3786,7 @@
         jniThrowRuntimeException(env, "Failed to GetByteArrayElements");
         return -1;
     }
-    int realReadSize = filterClient->read(reinterpret_cast<uint8_t*>(dst) + offset, size);
+    int realReadSize = filterClient->read(reinterpret_cast<int8_t*>(dst) + offset, size);
     env->ReleaseByteArrayElements(buffer, dst, 0);
     return (jint) realReadSize;
 }
diff --git a/media/jni/tuner/DemuxClient.cpp b/media/jni/tuner/DemuxClient.cpp
index e290c60..748d458 100644
--- a/media/jni/tuner/DemuxClient.cpp
+++ b/media/jni/tuner/DemuxClient.cpp
@@ -88,12 +88,19 @@
 }
 
 sp<TimeFilterClient> DemuxClient::openTimeFilter() {
-    // TODO: pending aidl interface
+    if (mTunerDemux != NULL) {
+        shared_ptr<ITunerTimeFilter> tunerTimeFilter;
+        Status s = mTunerDemux->openTimeFilter(&tunerTimeFilter);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return NULL;
+        }
+        return new TimeFilterClient(tunerTimeFilter);
+    }
 
     if (mDemux != NULL) {
         sp<ITimeFilter> hidlTimeFilter = openHidlTimeFilter();
         if (hidlTimeFilter != NULL) {
-            sp<TimeFilterClient> timeFilterClient = new TimeFilterClient();
+            sp<TimeFilterClient> timeFilterClient = new TimeFilterClient(NULL);
             timeFilterClient->setHidlTimeFilter(hidlTimeFilter);
             return timeFilterClient;
         }
@@ -103,7 +110,14 @@
 }
 
 int DemuxClient::getAvSyncHwId(sp<FilterClient> filterClient) {
-    // pending aidl interface
+    if (mTunerDemux != NULL) {
+        int hwId;
+        Status s = mTunerDemux->getAvSyncHwId(filterClient->getAidlFilter(), &hwId);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return INVALID_AV_SYNC_HW_ID;
+        }
+        return hwId;
+    }
 
     if (mDemux != NULL) {
         uint32_t avSyncHwId;
@@ -119,11 +133,18 @@
         }
     }
 
-    return -1;
+    return INVALID_AV_SYNC_HW_ID;
 }
 
 long DemuxClient::getAvSyncTime(int avSyncHwId) {
-    // pending aidl interface
+    if (mTunerDemux != NULL) {
+        int64_t time;
+        Status s = mTunerDemux->getAvSyncTime(avSyncHwId, &time);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return INVALID_AV_SYNC_TIME;
+        }
+        return time;
+    }
 
     if (mDemux != NULL) {
         uint64_t time;
@@ -138,11 +159,20 @@
         }
     }
 
-    return -1;
+    return INVALID_AV_SYNC_TIME;
 }
 
 sp<DvrClient> DemuxClient::openDvr(DvrType dvbType, int bufferSize, sp<DvrClientCallback> cb) {
-    // TODO: pending aidl interface
+    if (mTunerDemux != NULL) {
+        shared_ptr<ITunerDvr> tunerDvr;
+        shared_ptr<TunerDvrCallback> callback =
+                ::ndk::SharedRefBase::make<TunerDvrCallback>(cb);
+        Status s = mTunerDemux->openDvr((int)dvbType, bufferSize, callback, &tunerDvr);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return NULL;
+        }
+        return new DvrClient(tunerDvr);
+    }
 
     if (mDemux != NULL) {
         sp<HidlDvrCallback> callback = new HidlDvrCallback(cb);
@@ -158,7 +188,10 @@
 }
 
 Result DemuxClient::connectCiCam(int ciCamId) {
-    // pending aidl interface
+    if (mTunerDemux != NULL) {
+        Status s = mTunerDemux->connectCiCam(ciCamId);
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mDemux != NULL) {
         return mDemux->connectCiCam(static_cast<uint32_t>(ciCamId));
@@ -168,7 +201,10 @@
 }
 
 Result DemuxClient::disconnectCiCam() {
-    // pending aidl interface
+    if (mTunerDemux != NULL) {
+        Status s = mTunerDemux->disconnectCiCam();
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mDemux != NULL) {
         return mDemux->disconnectCiCam();
@@ -178,7 +214,10 @@
 }
 
 Result DemuxClient::close() {
-    // TODO: pending aidl interface
+    if (mTunerDemux != NULL) {
+        Status s = mTunerDemux->close();
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mDemux != NULL) {
         Result res = mDemux->close();
diff --git a/media/jni/tuner/DemuxClient.h b/media/jni/tuner/DemuxClient.h
index 463944a..31eb35a 100644
--- a/media/jni/tuner/DemuxClient.h
+++ b/media/jni/tuner/DemuxClient.h
@@ -31,7 +31,9 @@
 
 using Status = ::ndk::ScopedAStatus;
 using ::aidl::android::media::tv::tuner::ITunerDemux;
+using ::aidl::android::media::tv::tuner::ITunerTimeFilter;
 
+using ::android::hardware::tv::tuner::V1_0::IDemux;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
 using ::android::hardware::tv::tuner::V1_0::DvrType;
 using ::android::hardware::tv::tuner::V1_0::IDemux;
@@ -39,6 +41,9 @@
 
 using namespace std;
 
+const int64_t INVALID_AV_SYNC_TIME = -1;
+const int INVALID_AV_SYNC_HW_ID = -1;
+
 namespace android {
 
 struct DemuxClient : public RefBase {
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index be592af..0400485 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -210,14 +210,13 @@
             return res;
         }
 
-        AidlMQDesc* aidlMqDesc = NULL;
-        s = mTunerDvr->getQueueDesc(aidlMqDesc);
+        AidlMQDesc aidlMqDesc;
+        s = mTunerDvr->getQueueDesc(&aidlMqDesc);
         res = ClientHelper::getServiceSpecificErrorCode(s);
         if (res != Result::SUCCESS) {
             return res;
         }
-
-        mDvrMQ = new (nothrow) AidlMQ(*aidlMqDesc);
+        mDvrMQ = new (nothrow) AidlMQ(aidlMqDesc);
         EventFlag::createEventFlag(mDvrMQ->getEventFlagWord(), &mDvrMQEventFlag);
         return res;
     }
diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp
index bcef0a2..8b4ca37 100644
--- a/media/jni/tuner/FilterClient.cpp
+++ b/media/jni/tuner/FilterClient.cpp
@@ -16,15 +16,34 @@
 
 #define LOG_TAG "FilterClient"
 
+#include <aidlcommonsupport/NativeHandle.h>
 #include <android-base/logging.h>
+#include <fmq/ConvertMQDescriptors.h>
 #include <utils/Log.h>
 
 #include "FilterClient.h"
 
-using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
+using ::aidl::android::media::tv::tuner::TunerDemuxIpAddressSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterAlpConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterIpConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterMmtpConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterMonitorEvent;
+using ::aidl::android::media::tv::tuner::TunerFilterScIndexMask;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionBits;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionCondition;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionTableInfo;
+using ::aidl::android::media::tv::tuner::TunerFilterSharedHandleInfo;
+using ::aidl::android::media::tv::tuner::TunerFilterTlvConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterTsConfiguration;
+using ::android::hardware::hidl_vec;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterMainType;
 using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxQueueNotifyBits;
+using ::android::hardware::tv::tuner::V1_0::DemuxStreamId;
+using ::android::hardware::tv::tuner::V1_0::DemuxTpid;
+using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterType;
+using ::android::hardware::tv::tuner::V1_1::ScramblingStatus;
 
 namespace android {
 
@@ -48,41 +67,31 @@
 void FilterClient::setHidlFilter(sp<IFilter> filter) {
     mFilter = filter;
     mFilter_1_1 = ::android::hardware::tv::tuner::V1_1::IFilter::castFrom(mFilter);
-    handleAvShareMemory();
 }
 
-int FilterClient::read(uint8_t* buffer, int size) {
-    // TODO: pending aidl interface
-
-    if (mFilter != NULL) {
-        Result res = getFilterMq();
-        if (res != Result::SUCCESS) {
-            return -1;
-        }
-        return copyData(buffer, size);
+int FilterClient::read(int8_t* buffer, int size) {
+    Result res = getFilterMq();
+    if (res != Result::SUCCESS) {
+        return -1;
     }
-
-    return -1;
+    return copyData(buffer, size);
 }
 
 SharedHandleInfo FilterClient::getAvSharedHandleInfo() {
+    handleAvShareMemory();
     SharedHandleInfo info{
-        .sharedHandle = NULL,
-        .size = 0,
+        .sharedHandle = mAvSharedHandle,
+        .size = mAvSharedMemSize,
     };
 
-    // TODO: pending aidl interface
-
-    if (mFilter_1_1 != NULL) {
-        info.sharedHandle = mAvSharedHandle;
-        info.size = mAvSharedMemSize;
-    }
-
     return info;
 }
 
 Result FilterClient::configure(DemuxFilterSettings configure) {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->configure(getAidlFilterSettings(configure));
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter != NULL) {
         return mFilter->configure(configure);
@@ -92,7 +101,10 @@
 }
 
 Result FilterClient::configureMonitorEvent(int monitorEventType) {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->configureMonitorEvent(monitorEventType);
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter_1_1 != NULL) {
         return mFilter_1_1->configureMonitorEvent(monitorEventType);
@@ -102,7 +114,10 @@
 }
 
 Result FilterClient::configureIpFilterContextId(int cid) {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->configureIpFilterContextId(cid);
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter_1_1 != NULL) {
         return mFilter_1_1->configureIpCid(cid);
@@ -112,7 +127,19 @@
 }
 
 Result FilterClient::configureAvStreamType(AvStreamType avStreamType) {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        int type;
+        switch (avStreamType.getDiscriminator()) {
+            case AvStreamType::hidl_discriminator::audio:
+                type = (int)avStreamType.audio();
+                break;
+            case AvStreamType::hidl_discriminator::video:
+                type = (int)avStreamType.video();
+                break;
+        }
+        Status s = mTunerFilter->configureAvStreamType(type);
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter_1_1 != NULL) {
         return mFilter_1_1->configureAvStreamType(avStreamType);
@@ -122,7 +149,10 @@
 }
 
 Result FilterClient::start() {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->start();
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter != NULL) {
         return mFilter->start();
@@ -132,7 +162,10 @@
 }
 
 Result FilterClient::stop() {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->stop();
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter != NULL) {
         return mFilter->stop();
@@ -142,7 +175,10 @@
 }
 
 Result FilterClient::flush() {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->flush();
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter != NULL) {
         return mFilter->flush();
@@ -192,7 +228,10 @@
 }
 
 Result FilterClient::releaseAvHandle(native_handle_t* handle, uint64_t avDataId) {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->releaseAvHandle(makeToAidl(handle), avDataId);
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter != NULL) {
         return mFilter->releaseAvHandle(hidl_handle(handle), avDataId);
@@ -202,7 +241,10 @@
 }
 
 Result FilterClient::setDataSource(sp<FilterClient> filterClient){
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->setDataSource(filterClient->getAidlFilter());
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter != NULL) {
         sp<IFilter> sourceFilter = filterClient->getHalFilter();
@@ -216,13 +258,18 @@
 }
 
 Result FilterClient::close() {
-    // TODO: pending aidl interface
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->close();
+        closeAvSharedMemory();
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mFilter != NULL) {
         Result res = mFilter->close();
         if (res == Result::SUCCESS) {
             mFilter = NULL;
         }
+        closeAvSharedMemory();
         return res;
     }
 
@@ -269,37 +316,634 @@
     return Status::fromServiceSpecificError(static_cast<int32_t>(Result::INVALID_STATE));
 }
 
-Status TunerFilterCallback::onFilterEvent(vector<TunerFilterEvent>* /*filterEvent*/) {
-    // TODO: complete onFilterEvent
+Status TunerFilterCallback::onFilterEvent(const vector<TunerFilterEvent>& filterEvents) {
+    if (mFilterClientCallback == NULL) {
+        return Status::fromServiceSpecificError(static_cast<int32_t>(Result::INVALID_STATE));
+    }
+
+    if (filterEvents.size() == 0) {
+        return Status::fromServiceSpecificError(static_cast<int32_t>(Result::INVALID_ARGUMENT));
+    }
+
+    DemuxFilterEvent event;
+    DemuxFilterEventExt eventExt;
+    getHidlFilterEvent(filterEvents, event, eventExt);
+    if (eventExt.events.size() > 0) {
+        mFilterClientCallback->onFilterEvent_1_1(event, eventExt);
+    } else {
+        mFilterClientCallback->onFilterEvent(event);
+    }
+
     return Status::ok();
 }
 
 /////////////// FilterClient Helper Methods ///////////////////////
 
-Result FilterClient::getFilterMq() {
-    if (mFilter == NULL) {
-        return Result::INVALID_STATE;
+TunerFilterConfiguration FilterClient::getAidlFilterSettings(DemuxFilterSettings configure) {
+    TunerFilterConfiguration config;
+    switch (configure.getDiscriminator()) {
+        case DemuxFilterSettings::hidl_discriminator::ts:
+            return getAidlTsSettings(configure.ts());
+        case DemuxFilterSettings::hidl_discriminator::mmtp:
+            return getAidlMmtpSettings(configure.mmtp());
+        case DemuxFilterSettings::hidl_discriminator::ip:
+            return getAidlIpSettings(configure.ip());
+        case DemuxFilterSettings::hidl_discriminator::tlv:
+            return getAidlTlvSettings(configure.tlv());
+        case DemuxFilterSettings::hidl_discriminator::alp:
+            return getAidlAlpSettings(configure.alp());
+        default:
+            break;
+    }
+    ALOGE("Wrong DemuxFilterSettings union.");
+    return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlTsSettings(DemuxTsFilterSettings ts) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (ts.filterSettings.getDiscriminator()) {
+        case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::av: {
+            filterSettings.set<TunerFilterSettings::av>(
+                    getAidlAvSettings(ts.filterSettings.av()));
+            break;
+        }
+        case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(ts.filterSettings.section()));
+            break;
+        }
+        case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::pesData: {
+            filterSettings.set<TunerFilterSettings::pesData>(
+                    getAidlPesDataSettings(ts.filterSettings.pesData()));
+            break;
+        }
+        case DemuxTsFilterSettings::FilterSettings::hidl_discriminator::record: {
+            filterSettings.set<TunerFilterSettings::record>(
+                    getAidlRecordSettings(ts.filterSettings.record()));
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
     }
 
+    TunerFilterTsConfiguration aidlTs{
+        .tpid = static_cast<char16_t>(ts.tpid),
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::ts>(aidlTs);
+
+    return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlMmtpSettings(DemuxMmtpFilterSettings mmtp) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (mmtp.filterSettings.getDiscriminator()) {
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::av: {
+            filterSettings.set<TunerFilterSettings::av>(
+                    getAidlAvSettings(mmtp.filterSettings.av()));
+            break;
+        }
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(mmtp.filterSettings.section()));
+            break;
+        }
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::pesData: {
+            filterSettings.set<TunerFilterSettings::pesData>(
+                    getAidlPesDataSettings(mmtp.filterSettings.pesData()));
+            break;
+        }
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::record: {
+            filterSettings.set<TunerFilterSettings::record>(
+                    getAidlRecordSettings(mmtp.filterSettings.record()));
+            break;
+        }
+        case DemuxMmtpFilterSettings::FilterSettings::hidl_discriminator::download: {
+            filterSettings.set<TunerFilterSettings::download>(
+                    getAidlDownloadSettings(mmtp.filterSettings.download()));
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerFilterMmtpConfiguration aidlMmtp{
+        .mmtpPid = static_cast<char16_t>(mmtp.mmtpPid),
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::mmtp>(aidlMmtp);
+
+    return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlIpSettings(DemuxIpFilterSettings ip) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (ip.filterSettings.getDiscriminator()) {
+        case DemuxIpFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(ip.filterSettings.section()));
+            break;
+        }
+        case DemuxIpFilterSettings::FilterSettings::hidl_discriminator::bPassthrough: {
+            filterSettings.set<TunerFilterSettings::isPassthrough>(
+                    ip.filterSettings.bPassthrough());
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerDemuxIpAddressSettings ipAddr{
+        .srcPort = static_cast<char16_t>(ip.ipAddr.srcPort),
+        .dstPort = static_cast<char16_t>(ip.ipAddr.dstPort),
+    };
+    getAidlIpAddress(ip.ipAddr, ipAddr.srcIpAddress, ipAddr.dstIpAddress);
+
+    TunerFilterIpConfiguration aidlIp{
+        .ipAddr = ipAddr,
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::ip>(aidlIp);
+
+    return config;
+}
+
+void FilterClient::getAidlIpAddress(DemuxIpAddress ipAddr,
+        TunerDemuxIpAddress& srcIpAddress, TunerDemuxIpAddress& dstIpAddress) {
+    switch (ipAddr.srcIpAddress.getDiscriminator()) {
+        case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v4: {
+            int size = ipAddr.srcIpAddress.v4().size();
+            srcIpAddress.isIpV6 = false;
+            srcIpAddress.addr.resize(ipAddr.srcIpAddress.v4().size());
+            copy(&ipAddr.srcIpAddress.v4()[0], &ipAddr.srcIpAddress.v4()[size],
+                    srcIpAddress.addr.begin());
+            break;
+        }
+        case DemuxIpAddress::SrcIpAddress::hidl_discriminator::v6: {
+            int size = ipAddr.srcIpAddress.v6().size();
+            srcIpAddress.isIpV6 = true;
+            srcIpAddress.addr.resize(size);
+            copy(&ipAddr.srcIpAddress.v6()[0], &ipAddr.srcIpAddress.v6()[size],
+                    srcIpAddress.addr.begin());
+            break;
+        }
+        default:
+            break;
+    }
+    switch (ipAddr.dstIpAddress.getDiscriminator()) {
+        case DemuxIpAddress::DstIpAddress::hidl_discriminator::v4: {
+            int size = ipAddr.dstIpAddress.v4().size();
+            dstIpAddress.isIpV6 = false;
+            dstIpAddress.addr.resize(size);
+            copy(&ipAddr.dstIpAddress.v4()[0], &ipAddr.dstIpAddress.v4()[size],
+                    dstIpAddress.addr.begin());
+            break;
+        }
+        case DemuxIpAddress::DstIpAddress::hidl_discriminator::v6: {
+            int size = ipAddr.dstIpAddress.v6().size();
+            dstIpAddress.isIpV6 = true;
+            dstIpAddress.addr.resize(size);
+            copy(&ipAddr.dstIpAddress.v6()[0], &ipAddr.dstIpAddress.v6()[size],
+                    dstIpAddress.addr.begin());
+            break;
+        }
+        default:
+            break;
+    }
+}
+
+TunerFilterConfiguration FilterClient::getAidlTlvSettings(DemuxTlvFilterSettings tlv) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (tlv.filterSettings.getDiscriminator()) {
+        case DemuxTlvFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(tlv.filterSettings.section()));
+            break;
+        }
+        case DemuxTlvFilterSettings::FilterSettings::hidl_discriminator::bPassthrough: {
+            filterSettings.set<TunerFilterSettings::isPassthrough>(
+                    tlv.filterSettings.bPassthrough());
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerFilterTlvConfiguration aidlTlv{
+        .packetType = static_cast<int8_t>(tlv.packetType),
+        .isCompressedIpPacket = tlv.isCompressedIpPacket,
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::tlv>(aidlTlv);
+
+    return config;
+}
+
+TunerFilterConfiguration FilterClient::getAidlAlpSettings(DemuxAlpFilterSettings alp) {
+    TunerFilterConfiguration config;
+    TunerFilterSettings filterSettings;
+    switch (alp.filterSettings.getDiscriminator()) {
+        case DemuxAlpFilterSettings::FilterSettings::hidl_discriminator::section: {
+            filterSettings.set<TunerFilterSettings::section>(
+                    getAidlSectionSettings(alp.filterSettings.section()));
+            break;
+        }
+        default:
+            filterSettings.set<TunerFilterSettings::nothing>(true);
+            break;
+    }
+
+    TunerFilterAlpConfiguration aidlAlp{
+        .packetType = static_cast<int8_t>(alp.packetType),
+        .lengthType = static_cast<int8_t>(alp.lengthType),
+        .filterSettings = filterSettings,
+    };
+    config.set<TunerFilterConfiguration::alp>(aidlAlp);
+
+    return config;
+}
+
+TunerFilterAvSettings FilterClient::getAidlAvSettings(DemuxFilterAvSettings hidlAv) {
+    TunerFilterAvSettings aidlAv{
+        .isPassthrough = hidlAv.isPassthrough,
+    };
+    return aidlAv;
+}
+
+TunerFilterSectionSettings FilterClient::getAidlSectionSettings(
+        DemuxFilterSectionSettings hidlSection) {
+    TunerFilterSectionSettings aidlSection;
+
+    switch (hidlSection.condition.getDiscriminator()) {
+        case DemuxFilterSectionSettings::Condition::hidl_discriminator::sectionBits: {
+            TunerFilterSectionBits sectionBits;
+            auto hidlSectionBits = hidlSection.condition.sectionBits();
+            sectionBits.filter.resize(hidlSectionBits.filter.size());
+            sectionBits.mask.resize(hidlSectionBits.mask.size());
+            sectionBits.mode.resize(hidlSectionBits.mode.size());
+            copy(hidlSectionBits.filter.begin(), hidlSectionBits.filter.end(),
+                    hidlSectionBits.filter.begin());
+            copy(hidlSectionBits.mask.begin(), hidlSectionBits.mask.end(),
+                    hidlSectionBits.mask.begin());
+            copy(hidlSectionBits.mode.begin(), hidlSectionBits.mode.end(),
+                    hidlSectionBits.mode.begin());
+            aidlSection.condition.set<TunerFilterSectionCondition::sectionBits>(sectionBits);
+            break;
+        }
+        case DemuxFilterSectionSettings::Condition::hidl_discriminator::tableInfo: {
+            TunerFilterSectionTableInfo tableInfo{
+                .tableId = static_cast<char16_t>(hidlSection.condition.tableInfo().tableId),
+                .version = static_cast<char16_t>(hidlSection.condition.tableInfo().version),
+            };
+            aidlSection.condition.set<TunerFilterSectionCondition::tableInfo>(tableInfo);
+            break;
+        }
+    }
+    aidlSection.isCheckCrc = hidlSection.isCheckCrc;
+    aidlSection.isRepeat = hidlSection.isRepeat;
+    aidlSection.isRaw = hidlSection.isRaw;
+    return aidlSection;
+}
+
+TunerFilterPesDataSettings FilterClient::getAidlPesDataSettings(
+        DemuxFilterPesDataSettings hidlPesData) {
+    TunerFilterPesDataSettings aidlPesData{
+        .streamId = static_cast<char16_t>(hidlPesData.streamId),
+        .isRaw = hidlPesData.isRaw,
+    };
+    return aidlPesData;
+}
+
+TunerFilterRecordSettings FilterClient::getAidlRecordSettings(
+        DemuxFilterRecordSettings hidlRecord) {
+    TunerFilterScIndexMask mask;
+    switch (hidlRecord.scIndexMask.getDiscriminator()) {
+        case DemuxFilterRecordSettings::ScIndexMask::hidl_discriminator::sc: {
+            mask.set<TunerFilterScIndexMask::sc>(hidlRecord.scIndexMask.sc());
+            break;
+        }
+        case DemuxFilterRecordSettings::ScIndexMask::hidl_discriminator::scHevc: {
+            mask.set<TunerFilterScIndexMask::scHevc>(hidlRecord.scIndexMask.scHevc());
+            break;
+        }
+        default:
+            break;
+    }
+    TunerFilterRecordSettings aidlRecord{
+        .tsIndexMask = static_cast<int32_t>(hidlRecord.tsIndexMask),
+        .scIndexType = static_cast<int32_t>(hidlRecord.scIndexType),
+        .scIndexMask = mask,
+    };
+    return aidlRecord;
+}
+
+TunerFilterDownloadSettings FilterClient::getAidlDownloadSettings(
+        DemuxFilterDownloadSettings hidlDownload) {
+    TunerFilterDownloadSettings aidlDownload{
+        .downloadId = static_cast<int32_t>(hidlDownload.downloadId),
+    };
+    return aidlDownload;
+}
+
+void TunerFilterCallback::getHidlFilterEvent(const vector<TunerFilterEvent>& filterEvents,
+        DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) {
+    switch (filterEvents[0].getTag()) {
+        case  TunerFilterEvent::media: {
+            getHidlMediaEvent(filterEvents, event);
+            break;
+        }
+        case  TunerFilterEvent::section: {
+            getHidlSectionEvent(filterEvents, event);
+            break;
+        }
+        case  TunerFilterEvent::pes: {
+            getHidlPesEvent(filterEvents, event);
+            break;
+        }
+        case  TunerFilterEvent::tsRecord: {
+            getHidlTsRecordEvent(filterEvents, event, eventExt);
+            break;
+        }
+        case  TunerFilterEvent::mmtpRecord: {
+            getHidlMmtpRecordEvent(filterEvents, event, eventExt);
+            break;
+        }
+        case  TunerFilterEvent::download: {
+            getHidlDownloadEvent(filterEvents, event);
+            break;
+        }
+        case  TunerFilterEvent::ipPayload: {
+            getHidlIpPayloadEvent(filterEvents, event);
+            break;
+        }
+        case  TunerFilterEvent::temi: {
+            getHidlTemiEvent(filterEvents, event);
+            break;
+        }
+        case  TunerFilterEvent::monitor: {
+            getHidlMonitorEvent(filterEvents, eventExt);
+            break;
+        }
+        case  TunerFilterEvent::startId: {
+            getHidlRestartEvent(filterEvents, eventExt);
+            break;
+        }
+        default:
+            break;
+    }
+}
+
+void TunerFilterCallback::getHidlMediaEvent(
+        const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) {
+    event.events.resize(filterEvents.size());
+    for (int i = 0; i < filterEvents.size(); i++) {
+        hidl_handle handle = hidl_handle(makeFromAidl(filterEvents[i]
+                .get<TunerFilterEvent::media>().avMemory));
+        event.events[i].media({
+            .avMemory = handle,
+            .streamId = static_cast<DemuxStreamId>(filterEvents[i]
+                    .get<TunerFilterEvent::media>().streamId),
+            .isPtsPresent = filterEvents[i]
+                    .get<TunerFilterEvent::media>().isPtsPresent,
+            .pts = static_cast<uint64_t>(filterEvents[i]
+                    .get<TunerFilterEvent::media>().pts),
+            .dataLength = static_cast<uint32_t>(filterEvents[i]
+                    .get<TunerFilterEvent::media>().dataLength),
+            .offset = static_cast<uint32_t>(filterEvents[i]
+                    .get<TunerFilterEvent::media>().offset),
+            .isSecureMemory = filterEvents[i]
+                    .get<TunerFilterEvent::media>().isSecureMemory,
+            .avDataId = static_cast<uint64_t>(filterEvents[i]
+                    .get<TunerFilterEvent::media>().avDataId),
+            .mpuSequenceNumber = static_cast<uint32_t>(filterEvents[i]
+                    .get<TunerFilterEvent::media>().offset),
+            .isPesPrivateData = filterEvents[i]
+                    .get<TunerFilterEvent::media>().isPesPrivateData,
+        });
+
+        if (filterEvents[i].get<TunerFilterEvent::media>().isAudioExtraMetaData) {
+            event.events[i].media().extraMetaData.audio({
+                .adFade = static_cast<uint8_t>(filterEvents[i]
+                        .get<TunerFilterEvent::media>().audio.adFade),
+                .adPan = static_cast<uint8_t>(filterEvents[i]
+                        .get<TunerFilterEvent::media>().audio.adPan),
+                .versionTextTag = static_cast<uint8_t>(filterEvents[i]
+                        .get<TunerFilterEvent::media>().audio.versionTextTag),
+                .adGainCenter = static_cast<uint8_t>(filterEvents[i]
+                        .get<TunerFilterEvent::media>().audio.adGainCenter),
+                .adGainFront = static_cast<uint8_t>(filterEvents[i]
+                        .get<TunerFilterEvent::media>().audio.adGainFront),
+                .adGainSurround = static_cast<uint8_t>(filterEvents[i]
+                        .get<TunerFilterEvent::media>().audio.adGainSurround),
+            });
+        } else {
+            event.events[i].media().extraMetaData.noinit();
+        }
+    }
+}
+
+void TunerFilterCallback::getHidlSectionEvent(
+        const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) {
+    event.events.resize(filterEvents.size());
+    for (int i = 0; i < filterEvents.size(); i++) {
+        auto section = filterEvents[i].get<TunerFilterEvent::section>();
+        event.events[i].section({
+            .tableId = static_cast<uint16_t>(section.tableId),
+            .version = static_cast<uint16_t>(section.version),
+            .sectionNum = static_cast<uint16_t>(section.sectionNum),
+            .dataLength = static_cast<uint16_t>(section.dataLength),
+        });
+    }
+}
+
+void TunerFilterCallback::getHidlPesEvent(
+        const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event) {
+    event.events.resize(filterEvents.size());
+    for (int i = 0; i < filterEvents.size(); i++) {
+        auto pes = filterEvents[i].get<TunerFilterEvent::pes>();
+        event.events[i].pes({
+            .streamId = static_cast<DemuxStreamId>(pes.streamId),
+            .dataLength = static_cast<uint16_t>(pes.dataLength),
+            .mpuSequenceNumber = static_cast<uint32_t>(pes.mpuSequenceNumber),
+        });
+    }
+}
+
+void TunerFilterCallback::getHidlTsRecordEvent(const vector<TunerFilterEvent>& filterEvents,
+        DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) {
+    event.events.resize(filterEvents.size());
+    eventExt.events.resize(filterEvents.size());
+    for (int i = 0; i < filterEvents.size(); i++) {
+        auto ts = filterEvents[i].get<TunerFilterEvent::tsRecord>();
+        event.events[i].tsRecord({
+            .tsIndexMask = static_cast<uint32_t>(ts.tsIndexMask),
+            .byteNumber = static_cast<uint64_t>(ts.byteNumber),
+        });
+        event.events[i].tsRecord().pid.tPid(static_cast<DemuxTpid>(ts.pid));
+
+        switch (ts.scIndexMask.getTag()) {
+            case TunerFilterScIndexMask::sc: {
+                event.events[i].tsRecord().scIndexMask.sc(
+                        ts.scIndexMask.get<TunerFilterScIndexMask::sc>());
+                break;
+            }
+            case TunerFilterScIndexMask::scHevc: {
+                event.events[i].tsRecord().scIndexMask.scHevc(
+                        ts.scIndexMask.get<TunerFilterScIndexMask::scHevc>());
+                break;
+            }
+            default:
+                break;
+        }
+
+        if (ts.isExtended) {
+            eventExt.events[i].tsRecord({
+                .pts = static_cast<uint64_t>(ts.pts),
+                .firstMbInSlice = static_cast<uint32_t>(ts.firstMbInSlice),
+            });
+        } else {
+            eventExt.events[i].noinit();
+        }
+    }
+}
+
+void TunerFilterCallback::getHidlMmtpRecordEvent(const vector<TunerFilterEvent>& filterEvents,
+        DemuxFilterEvent& event, DemuxFilterEventExt& eventExt) {
+    event.events.resize(filterEvents.size());
+    eventExt.events.resize(filterEvents.size());
+    for (int i = 0; i < filterEvents.size(); i++) {
+        auto mmtp = filterEvents[i].get<TunerFilterEvent::mmtpRecord>();
+        event.events[i].mmtpRecord({
+            .scHevcIndexMask = static_cast<uint32_t>(mmtp.scHevcIndexMask),
+            .byteNumber = static_cast<uint64_t>(mmtp.byteNumber),
+        });
+
+        if (mmtp.isExtended) {
+            eventExt.events[i].mmtpRecord({
+                .pts = static_cast<uint64_t>(mmtp.pts),
+                .mpuSequenceNumber = static_cast<uint32_t>(mmtp.mpuSequenceNumber),
+                .firstMbInSlice = static_cast<uint32_t>(mmtp.firstMbInSlice),
+                .tsIndexMask = static_cast<uint32_t>(mmtp.tsIndexMask),
+            });
+        } else {
+            eventExt.events[i].noinit();
+        }
+    }
+}
+
+void TunerFilterCallback::getHidlDownloadEvent(const vector<TunerFilterEvent>& filterEvents,
+        DemuxFilterEvent& event) {
+    event.events.resize(filterEvents.size());
+    for (int i = 0; i < filterEvents.size(); i++) {
+        auto download = filterEvents[i].get<TunerFilterEvent::download>();
+        event.events[i].download({
+            .itemId = static_cast<uint32_t>(download.itemId),
+            .mpuSequenceNumber = static_cast<uint32_t>(download.mpuSequenceNumber),
+            .itemFragmentIndex = static_cast<uint32_t>(download.itemFragmentIndex),
+            .lastItemFragmentIndex = static_cast<uint32_t>(download.lastItemFragmentIndex),
+            .dataLength = static_cast<uint16_t>(download.dataLength),
+        });
+    }
+}
+
+void TunerFilterCallback::getHidlIpPayloadEvent(const vector<TunerFilterEvent>& filterEvents,
+        DemuxFilterEvent& event) {
+    event.events.resize(filterEvents.size());
+    for (int i = 0; i < filterEvents.size(); i++) {
+        auto ip = filterEvents[i].get<TunerFilterEvent::ipPayload>();
+        event.events[i].ipPayload({
+            .dataLength = static_cast<uint16_t>(ip.dataLength),
+        });
+    }
+}
+
+void TunerFilterCallback::getHidlTemiEvent(const vector<TunerFilterEvent>& filterEvents,
+        DemuxFilterEvent& event) {
+    event.events.resize(filterEvents.size());
+    for (int i = 0; i < filterEvents.size(); i++) {
+        auto temi = filterEvents[i].get<TunerFilterEvent::temi>();
+        event.events[i].temi({
+            .pts = static_cast<uint64_t>(temi.pts),
+            .descrTag = static_cast<uint8_t>(temi.descrTag),
+        });
+        hidl_vec<uint8_t> descrData(temi.descrData.begin(), temi.descrData.end());
+        event.events[i].temi().descrData = descrData;
+    }
+}
+
+void TunerFilterCallback::getHidlMonitorEvent(const vector<TunerFilterEvent>& filterEvents,
+        DemuxFilterEventExt& eventExt) {
+    auto monitor = filterEvents[0].get<TunerFilterEvent::monitor>();
+    eventExt.events.resize(1);
+    switch (monitor.getTag()) {
+        case TunerFilterMonitorEvent::scramblingStatus: {
+            eventExt.events[0].monitorEvent().scramblingStatus(
+                    static_cast<ScramblingStatus>(monitor.scramblingStatus));
+            break;
+        }
+        case TunerFilterMonitorEvent::cid: {
+            eventExt.events[0].monitorEvent().cid(static_cast<uint32_t>(monitor.cid));
+            break;
+        }
+        default:
+            eventExt.events[0].noinit();
+            break;
+    }
+}
+
+void TunerFilterCallback::getHidlRestartEvent(const vector<TunerFilterEvent>& filterEvents,
+        DemuxFilterEventExt& eventExt) {
+    uint32_t startId = filterEvents[0].get<TunerFilterEvent::startId>();
+    eventExt.events.resize(1);
+    eventExt.events[0].startId(static_cast<uint32_t>(startId));
+}
+
+Result FilterClient::getFilterMq() {
     if (mFilterMQ != NULL) {
         return Result::SUCCESS;
     }
 
-    Result getQueueDescResult = Result::UNKNOWN_ERROR;
-    MQDescriptorSync<uint8_t> filterMQDesc;
-    mFilter->getQueueDesc(
-            [&](Result r, const MQDescriptorSync<uint8_t>& desc) {
-                filterMQDesc = desc;
-                getQueueDescResult = r;
-            });
-    if (getQueueDescResult == Result::SUCCESS) {
-        mFilterMQ = std::make_unique<MQ>(filterMQDesc, true);
-        EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterMQEventFlag);
+    AidlMQDesc aidlMqDesc;
+    Result res = Result::UNAVAILABLE;
+
+    if (mTunerFilter != NULL) {
+        Status s = mTunerFilter->getQueueDesc(&aidlMqDesc);
+        res = ClientHelper::getServiceSpecificErrorCode(s);
+        if (res == Result::SUCCESS) {
+            mFilterMQ = new (nothrow) AidlMQ(aidlMqDesc);
+            EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterMQEventFlag);
+        }
+        return res;
     }
-    return getQueueDescResult;
+
+    if (mFilter != NULL) {
+        MQDescriptorSync<uint8_t> filterMQDesc;
+        mFilter->getQueueDesc(
+                [&](Result r, const MQDescriptorSync<uint8_t>& desc) {
+                    filterMQDesc = desc;
+                    res = r;
+                });
+        if (res == Result::SUCCESS) {
+            AidlMQDesc aidlMQDesc;
+            unsafeHidlToAidlMQDescriptor<uint8_t, int8_t, SynchronizedReadWrite>(
+                    filterMQDesc,  &aidlMQDesc);
+            mFilterMQ = new (nothrow) AidlMessageQueue(aidlMQDesc);
+            EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterMQEventFlag);
+        }
+    }
+
+    return res;
 }
 
-int FilterClient::copyData(uint8_t* buffer, int size) {
+int FilterClient::copyData(int8_t* buffer, int size) {
     if (mFilter == NULL || mFilterMQ == NULL || mFilterMQEventFlag == NULL) {
         return -1;
     }
@@ -333,6 +977,20 @@
 }
 
 void FilterClient::handleAvShareMemory() {
+    if (mAvSharedHandle != NULL) {
+        return;
+    }
+
+    if (mTunerFilter != NULL && mIsMediaFilter) {
+        TunerFilterSharedHandleInfo aidlHandleInfo;
+        Status s = mTunerFilter->getAvSharedHandleInfo(&aidlHandleInfo);
+        if (ClientHelper::getServiceSpecificErrorCode(s) == Result::SUCCESS) {
+            mAvSharedHandle = native_handle_clone(makeFromAidl(aidlHandleInfo.handle));
+            mAvSharedMemSize = aidlHandleInfo.size;
+        }
+        return;
+    }
+
     if (mFilter_1_1 != NULL && mIsMediaFilter) {
         mFilter_1_1->getAvSharedHandle([&](Result r, hidl_handle avMemory, uint64_t avMemSize) {
             if (r == Result::SUCCESS) {
@@ -342,4 +1000,10 @@
         });
     }
 }
+
+void FilterClient::closeAvSharedMemory() {
+    native_handle_close(mAvSharedHandle);
+    native_handle_delete(mAvSharedHandle);
+    mAvSharedMemSize = 0;
+}
 }  // namespace android
diff --git a/media/jni/tuner/FilterClient.h b/media/jni/tuner/FilterClient.h
index 7c85125..bbabc28 100644
--- a/media/jni/tuner/FilterClient.h
+++ b/media/jni/tuner/FilterClient.h
@@ -20,18 +20,30 @@
 #include <aidl/android/media/tv/tuner/ITunerFilter.h>
 #include <aidl/android/media/tv/tuner/BnTunerFilterCallback.h>
 #include <aidl/android/media/tv/tuner/TunerFilterEvent.h>
+#include <aidl/android/media/tv/tuner/TunerFilterSettings.h>
+#include <aidlcommonsupport/NativeHandle.h>
 #include <android/hardware/tv/tuner/1.1/IFilter.h>
 #include <android/hardware/tv/tuner/1.1/IFilterCallback.h>
 #include <android/hardware/tv/tuner/1.1/types.h>
+#include <fmq/AidlMessageQueue.h>
 #include <fmq/MessageQueue.h>
 
 #include "ClientHelper.h"
 #include "FilterClientCallback.h"
 
 using Status = ::ndk::ScopedAStatus;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
 using ::aidl::android::media::tv::tuner::BnTunerFilterCallback;
 using ::aidl::android::media::tv::tuner::ITunerFilter;
+using ::aidl::android::media::tv::tuner::TunerDemuxIpAddress;
+using ::aidl::android::media::tv::tuner::TunerFilterAvSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterConfiguration;
+using ::aidl::android::media::tv::tuner::TunerFilterDownloadSettings;
 using ::aidl::android::media::tv::tuner::TunerFilterEvent;
+using ::aidl::android::media::tv::tuner::TunerFilterPesDataSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterRecordSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterSectionSettings;
+using ::aidl::android::media::tv::tuner::TunerFilterSettings;
 
 using ::android::hardware::EventFlag;
 using ::android::hardware::MessageQueue;
@@ -39,8 +51,19 @@
 using ::android::hardware::Return;
 using ::android::hardware::Void;
 using ::android::hardware::hidl_handle;
+using ::android::hardware::tv::tuner::V1_0::DemuxAlpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxMmtpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterAvSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterDownloadSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterRecordSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::DemuxFilterType;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpAddress;
+using ::android::hardware::tv::tuner::V1_0::DemuxIpFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTlvFilterSettings;
+using ::android::hardware::tv::tuner::V1_0::DemuxTsFilterSettings;
 using ::android::hardware::tv::tuner::V1_0::IFilter;
 using ::android::hardware::tv::tuner::V1_0::Result;
 using ::android::hardware::tv::tuner::V1_1::AvStreamType;
@@ -48,10 +71,13 @@
 
 using namespace std;
 
-using MQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
-
 namespace android {
 
+using MQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+using MQDesc = MQDescriptorSync<uint8_t>;
+using AidlMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+using AidlMQDesc = MQDescriptor<int8_t, SynchronizedReadWrite>;
+
 struct SharedHandleInfo {
     native_handle_t* sharedHandle;
     uint64_t size;
@@ -61,11 +87,33 @@
 
 public:
     TunerFilterCallback(sp<FilterClientCallback> filterClientCallback);
-    // TODO: complete TunerFilterCallback
     Status onFilterStatus(int status);
-    Status onFilterEvent(vector<TunerFilterEvent>* filterEvent);
+    Status onFilterEvent(const vector<TunerFilterEvent>& filterEvents);
 
 private:
+    void getHidlFilterEvent(const vector<TunerFilterEvent>& filterEvents,
+            DemuxFilterEvent& event, DemuxFilterEventExt& eventExt);
+    void getHidlMediaEvent(
+            const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+    void getHidlSectionEvent(
+            const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+    void getHidlPesEvent(
+            const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+    void getHidlTsRecordEvent(const vector<TunerFilterEvent>& filterEvents,
+            DemuxFilterEvent& event, DemuxFilterEventExt& eventExt);
+    void getHidlMmtpRecordEvent(const vector<TunerFilterEvent>& filterEvents,
+            DemuxFilterEvent& event, DemuxFilterEventExt& eventExt);
+    void getHidlDownloadEvent(
+            const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+    void getHidlIpPayloadEvent(
+            const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+    void getHidlTemiEvent(
+            const vector<TunerFilterEvent>& filterEvents, DemuxFilterEvent& event);
+    void getHidlMonitorEvent(
+            const vector<TunerFilterEvent>& filterEvents, DemuxFilterEventExt& eventExt);
+    void getHidlRestartEvent(
+            const vector<TunerFilterEvent>& filterEvents, DemuxFilterEventExt& eventExt);
+
     sp<FilterClientCallback> mFilterClientCallback;
 };
 
@@ -96,7 +144,7 @@
      *
      * @return the actual reading size. -1 if failed to read.
      */
-    int read(uint8_t* buffer, int size);
+    int read(int8_t* buffer, int size);
 
     /**
      * Get the a/v shared memory handle information
@@ -174,10 +222,27 @@
     Result close();
 
 private:
+    TunerFilterConfiguration getAidlFilterSettings(DemuxFilterSettings configure);
+
+    TunerFilterConfiguration getAidlTsSettings(DemuxTsFilterSettings configure);
+    TunerFilterConfiguration getAidlMmtpSettings(DemuxMmtpFilterSettings mmtp);
+    TunerFilterConfiguration getAidlIpSettings(DemuxIpFilterSettings ip);
+    TunerFilterConfiguration getAidlTlvSettings(DemuxTlvFilterSettings tlv);
+    TunerFilterConfiguration getAidlAlpSettings(DemuxAlpFilterSettings alp);
+
+    TunerFilterAvSettings getAidlAvSettings(DemuxFilterAvSettings hidlAv);
+    TunerFilterSectionSettings getAidlSectionSettings(DemuxFilterSectionSettings hidlSection);
+    TunerFilterPesDataSettings getAidlPesDataSettings(DemuxFilterPesDataSettings hidlPesData);
+    TunerFilterRecordSettings getAidlRecordSettings(DemuxFilterRecordSettings hidlRecord);
+    TunerFilterDownloadSettings getAidlDownloadSettings(DemuxFilterDownloadSettings hidlDownload);
+
+    void getAidlIpAddress(DemuxIpAddress ipAddr,
+            TunerDemuxIpAddress& srcIpAddress, TunerDemuxIpAddress& dstIpAddress);
     Result getFilterMq();
-    int copyData(uint8_t* buffer, int size);
+    int copyData(int8_t* buffer, int size);
     void checkIsMediaFilter(DemuxFilterType type);
     void handleAvShareMemory();
+    void closeAvSharedMemory();
 
     /**
      * An AIDL Tuner Filter Singleton assigned at the first time when the Tuner Client
@@ -199,7 +264,7 @@
      */
     sp<::android::hardware::tv::tuner::V1_1::IFilter> mFilter_1_1;
 
-    unique_ptr<MQ> mFilterMQ;
+    AidlMQ* mFilterMQ;
     EventFlag* mFilterMQEventFlag;
 
     sp<FilterClientCallback> mCallback;
diff --git a/media/jni/tuner/FrontendClient.cpp b/media/jni/tuner/FrontendClient.cpp
index ef8f57f..f454907 100644
--- a/media/jni/tuner/FrontendClient.cpp
+++ b/media/jni/tuner/FrontendClient.cpp
@@ -22,27 +22,48 @@
 #include "FrontendClient.h"
 
 using ::aidl::android::media::tv::tuner::TunerFrontendScanAtsc3PlpInfo;
-using ::aidl::android::media::tv::tuner::TunerFrontendSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendUnionSettings;
 
 using ::android::hardware::tv::tuner::V1_0::FrontendAnalogSifStandard;
 using ::android::hardware::tv::tuner::V1_0::FrontendAnalogType;
 using ::android::hardware::tv::tuner::V1_0::FrontendAtscModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendAtsc3Bandwidth;
 using ::android::hardware::tv::tuner::V1_0::FrontendAtsc3Modulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendAtsc3TimeInterleaveMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendDvbcAnnex;
 using ::android::hardware::tv::tuner::V1_0::FrontendDvbcModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendDvbcSpectralInversion;
 using ::android::hardware::tv::tuner::V1_0::FrontendDvbsModulation;
 using ::android::hardware::tv::tuner::V1_0::FrontendDvbsStandard;
+using ::android::hardware::tv::tuner::V1_0::FrontendDvbsRolloff;
+using ::android::hardware::tv::tuner::V1_0::FrontendDvbtBandwidth;
+using ::android::hardware::tv::tuner::V1_0::FrontendDvbtGuardInterval;
 using ::android::hardware::tv::tuner::V1_0::FrontendDvbtHierarchy;
 using ::android::hardware::tv::tuner::V1_0::FrontendDvbtStandard;
+using ::android::hardware::tv::tuner::V1_0::FrontendInnerFec;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbsModulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendIsdbsRolloff;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbs3Modulation;
+using ::android::hardware::tv::tuner::V1_0::FrontendIsdbs3Rolloff;
+using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtBandwidth;
+using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtGuardInterval;
+using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
-using ::android::hardware::tv::tuner::V1_0::FrontendType;
+using ::android::hardware::tv::tuner::V1_0::LnbVoltage;
 using ::android::hardware::tv::tuner::V1_1::Constant;
+using ::android::hardware::tv::tuner::V1_1::FrontendCableTimeInterleaveMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendDtmbBandwidth;
+using ::android::hardware::tv::tuner::V1_1::FrontendDtmbGuardInterval;
 using ::android::hardware::tv::tuner::V1_1::FrontendDtmbModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendDtmbTimeInterleaveMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendDtmbTransmissionMode;
+using ::android::hardware::tv::tuner::V1_1::FrontendDvbcBandwidth;
 using ::android::hardware::tv::tuner::V1_1::FrontendDvbtConstellation;
+using ::android::hardware::tv::tuner::V1_1::FrontendDvbtTransmissionMode;
 using ::android::hardware::tv::tuner::V1_1::FrontendModulation;
+using ::android::hardware::tv::tuner::V1_1::FrontendSpectralInversion;
+using ::android::hardware::tv::tuner::V1_1::FrontendType;
 
 namespace android {
 
@@ -86,15 +107,13 @@
 Result FrontendClient::tune(const FrontendSettings& settings,
         const FrontendSettingsExt1_1& settingsExt1_1) {
     if (mTunerFrontend != NULL) {
-        // TODO: parse hidl settings to aidl settings
-        // TODO: aidl frontend settings to include Tuner HAL 1.1 settings
-        TunerFrontendSettings settings;
-        Status s = mTunerFrontend->tune(settings);
+        TunerFrontendSettings tunerFeSettings = getAidlFrontendSettings(settings, settingsExt1_1);
+        Status s = mTunerFrontend->tune(tunerFeSettings);
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     Result result;
-    if (mFrontend_1_1 != NULL) {
+    if (mFrontend_1_1 != NULL && validateExtendedSettings(settingsExt1_1)) {
         result = mFrontend_1_1->tune_1_1(settings, settingsExt1_1);
         return result;
     }
@@ -124,15 +143,13 @@
 Result FrontendClient::scan(const FrontendSettings& settings, FrontendScanType type,
         const FrontendSettingsExt1_1& settingsExt1_1) {
     if (mTunerFrontend != NULL) {
-        // TODO: parse hidl settings to aidl settings
-        // TODO: aidl frontend settings to include Tuner HAL 1.1 settings
-        TunerFrontendSettings settings;
-        Status s = mTunerFrontend->scan(settings, (int)type);
+        TunerFrontendSettings tunerFeSettings = getAidlFrontendSettings(settings, settingsExt1_1);
+        Status s = mTunerFrontend->scan(tunerFeSettings, (int)type);
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
 
     Result result;
-    if (mFrontend_1_1 != NULL) {
+    if (mFrontend_1_1 != NULL && validateExtendedSettings(settingsExt1_1)) {
         result = mFrontend_1_1->scan_1_1(settings, type, settingsExt1_1);
         return result;
     }
@@ -163,9 +180,16 @@
     vector<FrontendStatus> status;
 
     if (mTunerFrontend != NULL) {
-        // TODO: handle error message.
-        /*status = mTunerFrontend->getStatus(statusTypes);
-        return status;*/
+        vector<TunerFrontendStatus> aidlStatus;
+        vector<int> types;
+        for (auto t : statusTypes) {
+            types.push_back((int)t);
+        }
+        Status s = mTunerFrontend->getStatus(types, &aidlStatus);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return status;
+        }
+        return getHidlStatus(aidlStatus);
     }
 
     if (mFrontend != NULL && statusTypes.size() > 0) {
@@ -183,14 +207,22 @@
 
     return status;
 }
+
 vector<FrontendStatusExt1_1> FrontendClient::getStatusExtended_1_1(
         vector<FrontendStatusTypeExt1_1> statusTypes) {
     vector<FrontendStatusExt1_1> status;
 
     if (mTunerFrontend != NULL) {
-        // TODO: handle error message.
-        /*status = mTunerFrontend->getStatusExtended_1_1(statusTypes);
-        return status;*/
+        vector<TunerFrontendStatus> aidlStatus;
+        vector<int> types;
+        for (auto t : statusTypes) {
+            types.push_back((int)t);
+        }
+        Status s = mTunerFrontend->getStatusExtended_1_1(types, &aidlStatus);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return status;
+        }
+        return getHidlStatusExt(aidlStatus);
     }
 
     if (mFrontend_1_1 != NULL && statusTypes.size() > 0) {
@@ -279,7 +311,6 @@
 
 Result FrontendClient::close() {
     if (mTunerFrontend != NULL) {
-        // TODO: handle error message.
         Status s = mTunerFrontend->close();
         return ClientHelper::getServiceSpecificErrorCode(s);
     }
@@ -296,6 +327,8 @@
     return Result::INVALID_STATE;
 }
 
+/////////////// TunerFrontend Helper Methods ///////////////////////
+
 shared_ptr<ITunerFrontend> FrontendClient::getAidlFrontend() {
     return mTunerFrontend;
 }
@@ -304,6 +337,648 @@
     return mId;
 }
 
+vector<FrontendStatus> FrontendClient::getHidlStatus(vector<TunerFrontendStatus>& aidlStatus) {
+    vector<FrontendStatus> hidlStatus;
+    for (TunerFrontendStatus s : aidlStatus) {
+        FrontendStatus status;
+        switch (s.getTag()) {
+            case TunerFrontendStatus::isDemodLocked: {
+                status.isDemodLocked(s.get<TunerFrontendStatus::isDemodLocked>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::snr: {
+                status.snr(s.get<TunerFrontendStatus::snr>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::ber: {
+                status.ber((uint32_t)s.get<TunerFrontendStatus::ber>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::per: {
+                status.per((uint32_t)s.get<TunerFrontendStatus::per>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::preBer: {
+                status.preBer((uint32_t)s.get<TunerFrontendStatus::preBer>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::signalQuality: {
+                status.signalQuality((uint32_t)s.get<TunerFrontendStatus::signalQuality>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::signalStrength: {
+                status.signalStrength(s.get<TunerFrontendStatus::signalStrength>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::symbolRate: {
+                status.symbolRate((uint32_t)s.get<TunerFrontendStatus::symbolRate>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::innerFec: {
+                status.innerFec(static_cast<FrontendInnerFec>(
+                        s.get<TunerFrontendStatus::innerFec>()));
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::modulation: {
+                auto aidlMod = s.get<TunerFrontendStatus::modulation>();
+                switch (mType) {
+                    case (int)FrontendType::DVBC:
+                        status.modulation().dvbc(static_cast<FrontendDvbcModulation>(aidlMod));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::DVBS:
+                        status.modulation().dvbs(static_cast<FrontendDvbsModulation>(aidlMod));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::ISDBS:
+                        status.modulation().isdbs(static_cast<FrontendIsdbsModulation>(aidlMod));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::ISDBS3:
+                        status.modulation().isdbs3(static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::ISDBT:
+                        status.modulation().isdbt(static_cast<FrontendIsdbtModulation>(aidlMod));
+                        hidlStatus.push_back(status);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            }
+            case TunerFrontendStatus::inversion: {
+                status.inversion(static_cast<FrontendDvbcSpectralInversion>(
+                        s.get<TunerFrontendStatus::inversion>()));
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::lnbVoltage: {
+                status.lnbVoltage(static_cast<LnbVoltage>(
+                        s.get<TunerFrontendStatus::lnbVoltage>()));
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::plpId: {
+                status.plpId((uint8_t)s.get<TunerFrontendStatus::plpId>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::isEWBS: {
+                status.isEWBS(s.get<TunerFrontendStatus::isEWBS>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::agc: {
+                status.agc((uint8_t)s.get<TunerFrontendStatus::agc>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::isLnaOn: {
+                status.isLnaOn(s.get<TunerFrontendStatus::isLnaOn>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::isLayerError: {
+                auto aidlE = s.get<TunerFrontendStatus::isLayerError>();
+                hidl_vec<bool> e(aidlE.begin(), aidlE.end());
+                status.isLayerError(e);
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::mer: {
+                status.mer(s.get<TunerFrontendStatus::mer>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::freqOffset: {
+                status.freqOffset(s.get<TunerFrontendStatus::freqOffset>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::hierarchy: {
+                status.hierarchy(static_cast<FrontendDvbtHierarchy>(
+                        s.get<TunerFrontendStatus::freqOffset>()));
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::isRfLocked: {
+                status.isRfLocked(s.get<TunerFrontendStatus::isRfLocked>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::plpInfo: {
+                int size = s.get<TunerFrontendStatus::plpInfo>().size();
+                status.plpInfo().resize(size);
+                for (int i = 0; i < size; i++) {
+                    auto aidlInfo = s.get<TunerFrontendStatus::plpInfo>()[i];
+                    status.plpInfo()[i] = {
+                        .plpId = (uint8_t)aidlInfo.plpId,
+                        .isLocked = aidlInfo.isLocked,
+                        .uec = (uint32_t)aidlInfo.uec,
+                    };
+                }
+                hidlStatus.push_back(status);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+    return hidlStatus;
+}
+
+vector<FrontendStatusExt1_1> FrontendClient::getHidlStatusExt(
+        vector<TunerFrontendStatus>& aidlStatus) {
+    vector<FrontendStatusExt1_1> hidlStatus;
+    for (TunerFrontendStatus s : aidlStatus) {
+        FrontendStatusExt1_1 status;
+        switch (s.getTag()) {
+            case TunerFrontendStatus::modulations: {
+                for (auto aidlMod : s.get<TunerFrontendStatus::modulations>()) {
+                    int size = status.modulations().size();
+                    status.modulations().resize(size + 1);
+                    switch (mType) {
+                        case (int)FrontendType::DVBC:
+                            status.modulations()[size].dvbc(
+                                    static_cast<FrontendDvbcModulation>(aidlMod));
+                            break;
+                        case (int)FrontendType::DVBS:
+                            status.modulations()[size].dvbs(
+                                    static_cast<FrontendDvbsModulation>(aidlMod));
+                            break;
+                        case (int)FrontendType::DVBT:
+                            status.modulations()[size].dvbt(
+                                    static_cast<FrontendDvbtConstellation>(aidlMod));
+                            break;
+                        case (int)FrontendType::ISDBS:
+                            status.modulations()[size].isdbs(
+                                    static_cast<FrontendIsdbsModulation>(aidlMod));
+                            break;
+                        case (int)FrontendType::ISDBS3:
+                            status.modulations()[size].isdbs3(
+                                    static_cast<FrontendIsdbs3Modulation>(aidlMod));
+                            break;
+                        case (int)FrontendType::ISDBT:
+                            status.modulations()[size].isdbt(
+                                    static_cast<FrontendIsdbtModulation>(aidlMod));
+                            break;
+                        case (int)FrontendType::ATSC:
+                            status.modulations()[size].atsc(
+                                    static_cast<FrontendAtscModulation>(aidlMod));
+                            break;
+                        case (int)FrontendType::ATSC3:
+                            status.modulations()[size].atsc3(
+                                    static_cast<FrontendAtsc3Modulation>(aidlMod));
+                            break;
+                        case (int)FrontendType::DTMB:
+                            status.modulations()[size].dtmb(
+                                    static_cast<FrontendDtmbModulation>(aidlMod));
+                            break;
+                        default:
+                            status.modulations().resize(size);
+                            break;
+                    }
+                }
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::bers: {
+                auto aidlB = s.get<TunerFrontendStatus::bers>();
+                hidl_vec<uint32_t> b(aidlB.begin(), aidlB.end());
+                status.bers(b);
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::codeRates: {
+                int size = s.get<TunerFrontendStatus::codeRates>().size();
+                status.codeRates().resize(size);
+                for (int i = 0; i < size; i++) {
+                    auto aidlCodeRate = s.get<TunerFrontendStatus::codeRates>()[i];
+                    status.codeRates()[i] =
+                            static_cast<hardware::tv::tuner::V1_1::FrontendInnerFec>(aidlCodeRate);
+                }
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::bandwidth: {
+                auto aidlBand = s.get<TunerFrontendStatus::bandwidth>();
+                switch (mType) {
+                    case (int)FrontendType::ATSC3:
+                        status.bandwidth().atsc3(static_cast<FrontendAtsc3Bandwidth>(aidlBand));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::DVBC:
+                        status.bandwidth().dvbc(static_cast<FrontendDvbcBandwidth>(aidlBand));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::DVBT:
+                        status.bandwidth().dvbt(static_cast<FrontendDvbtBandwidth>(aidlBand));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::ISDBT:
+                        status.bandwidth().isdbt(static_cast<FrontendIsdbtBandwidth>(aidlBand));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::DTMB:
+                        status.bandwidth().dtmb(static_cast<FrontendDtmbBandwidth>(aidlBand));
+                        hidlStatus.push_back(status);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            }
+            case TunerFrontendStatus::interval: {
+                auto aidlInter = s.get<TunerFrontendStatus::interval>();
+                switch (mType) {
+                    case (int)FrontendType::DVBT:
+                        status.interval().dvbt(static_cast<FrontendDvbtGuardInterval>(aidlInter));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::ISDBT:
+                        status.interval().isdbt(static_cast<FrontendIsdbtGuardInterval>(aidlInter));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::DTMB:
+                        status.interval().dtmb(static_cast<FrontendDtmbGuardInterval>(aidlInter));
+                        hidlStatus.push_back(status);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            }
+            case TunerFrontendStatus::transmissionMode: {
+                auto aidlTran = s.get<TunerFrontendStatus::transmissionMode>();
+                switch (mType) {
+                    case (int)FrontendType::DVBT:
+                        status.transmissionMode().dvbt(
+                                static_cast<FrontendDvbtTransmissionMode>(aidlTran));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::ISDBT:
+                        status.transmissionMode().isdbt(static_cast<FrontendIsdbtMode>(aidlTran));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::DTMB:
+                        status.transmissionMode().dtmb(
+                                static_cast<FrontendDtmbTransmissionMode>(aidlTran));
+                        hidlStatus.push_back(status);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            }
+            case TunerFrontendStatus::uec: {
+                status.uec((uint32_t)s.get<TunerFrontendStatus::uec>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::systemId: {
+                status.systemId((uint16_t)s.get<TunerFrontendStatus::systemId>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::interleaving: {
+                for (auto aidlInter : s.get<TunerFrontendStatus::interleaving>()) {
+                    int size = status.interleaving().size();
+                    status.interleaving().resize(size + 1);
+                    switch (mType) {
+                        case (int)FrontendType::DVBC:
+                            status.interleaving()[size].dvbc(
+                                    static_cast<FrontendCableTimeInterleaveMode>(aidlInter));
+                            break;
+                        case (int)FrontendType::ATSC3:
+                            status.interleaving()[size].atsc3(
+                                    static_cast<FrontendAtsc3TimeInterleaveMode>(aidlInter));
+                            break;
+                        case (int)FrontendType::DTMB:
+                            status.interleaving()[size].dtmb(
+                                    static_cast<FrontendDtmbTimeInterleaveMode>(aidlInter));
+                            break;
+                        default:
+                            status.interleaving().resize(size);
+                            break;
+                    }
+                }
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::isdbtSegment: {
+                auto aidlSeg = s.get<TunerFrontendStatus::isdbtSegment>();
+                hidl_vec<uint8_t> s(aidlSeg.begin(), aidlSeg.end());
+                status.isdbtSegment(s);
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::tsDataRate: {
+                auto aidlTs = s.get<TunerFrontendStatus::tsDataRate>();
+                hidl_vec<uint32_t> ts(aidlTs.begin(), aidlTs.end());
+                status.tsDataRate(ts);
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::rollOff: {
+                auto aidlRoll = s.get<TunerFrontendStatus::rollOff>();
+                switch (mType) {
+                    case (int)FrontendType::DVBS:
+                        status.rollOff().dvbs(static_cast<FrontendDvbsRolloff>(aidlRoll));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::ISDBS:
+                        status.rollOff().isdbs(static_cast<FrontendIsdbsRolloff>(aidlRoll));
+                        hidlStatus.push_back(status);
+                        break;
+                    case (int)FrontendType::ISDBS3:
+                        status.rollOff().isdbs3(static_cast<FrontendIsdbs3Rolloff>(aidlRoll));
+                        hidlStatus.push_back(status);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            }
+            case TunerFrontendStatus::isMiso: {
+                status.isMiso(s.get<TunerFrontendStatus::isMiso>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::isLinear: {
+                status.isLinear(s.get<TunerFrontendStatus::isLinear>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            case TunerFrontendStatus::isShortFrames: {
+                status.isShortFrames(s.get<TunerFrontendStatus::isShortFrames>());
+                hidlStatus.push_back(status);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+    return hidlStatus;
+}
+
+TunerFrontendSettings FrontendClient::getAidlFrontendSettings(const FrontendSettings& settings,
+        const FrontendSettingsExt1_1& settingsExt1_1) {
+    bool isExtended = validateExtendedSettings(settingsExt1_1);
+    TunerFrontendSettings s{
+        .isExtended = isExtended,
+        .endFrequency = (int) settingsExt1_1.endFrequency,
+        .inversion = (int) settingsExt1_1.inversion,
+    };
+
+    if (settingsExt1_1.settingExt.getDiscriminator()
+            == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::dtmb) {
+        s.settings.set<TunerFrontendUnionSettings::dtmb>(getAidlDtmbSettings(settingsExt1_1));
+        return s;
+    }
+
+    switch (settings.getDiscriminator()) {
+        case FrontendSettings::hidl_discriminator::analog: {
+            s.settings.set<TunerFrontendUnionSettings::analog>(
+                    getAidlAnalogSettings(settings, settingsExt1_1));
+            break;
+        }
+        case FrontendSettings::hidl_discriminator::atsc: {
+            s.settings.set<TunerFrontendUnionSettings::atsc>(getAidlAtscSettings(settings));
+            break;
+        }
+        case FrontendSettings::hidl_discriminator::atsc3: {
+            s.settings.set<TunerFrontendUnionSettings::atsc3>(getAidlAtsc3Settings(settings));
+            break;
+        }
+        case FrontendSettings::hidl_discriminator::dvbs: {
+            s.settings.set<TunerFrontendUnionSettings::dvbs>(
+                    getAidlDvbsSettings(settings, settingsExt1_1));
+            break;
+        }
+        case FrontendSettings::hidl_discriminator::dvbc: {
+            s.settings.set<TunerFrontendUnionSettings::cable>(
+                    getAidlCableSettings(settings, settingsExt1_1));
+            break;
+        }
+        case FrontendSettings::hidl_discriminator::dvbt: {
+            s.settings.set<TunerFrontendUnionSettings::dvbt>(
+                    getAidlDvbtSettings(settings, settingsExt1_1));
+            break;
+        }
+        case FrontendSettings::hidl_discriminator::isdbs: {
+            s.settings.set<TunerFrontendUnionSettings::isdbs>(getAidlIsdbsSettings(settings));
+            break;
+        }
+        case FrontendSettings::hidl_discriminator::isdbs3: {
+            s.settings.set<TunerFrontendUnionSettings::isdbs3>(getAidlIsdbs3Settings(settings));
+            break;
+        }
+        case FrontendSettings::hidl_discriminator::isdbt: {
+            s.settings.set<TunerFrontendUnionSettings::isdbt>(getAidlIsdbtSettings(settings));
+            break;
+        }
+        default:
+            break;
+    }
+    return s;
+}
+
+TunerFrontendAnalogSettings FrontendClient::getAidlAnalogSettings(const FrontendSettings& settings,
+        const FrontendSettingsExt1_1& settingsExt1_1) {
+    TunerFrontendAnalogSettings analogSettings{
+        .frequency = (int)settings.analog().frequency,
+        .signalType = (int)settings.analog().type,
+        .sifStandard = (int)settings.analog().sifStandard,
+    };
+    if (settingsExt1_1.settingExt.getDiscriminator()
+            == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::analog) {
+        analogSettings.isExtended = true;
+        analogSettings.aftFlag = (int)settingsExt1_1.settingExt.analog().aftFlag;
+    } else {
+        analogSettings.isExtended = false;
+    }
+    return analogSettings;
+}
+
+TunerFrontendDvbsSettings FrontendClient::getAidlDvbsSettings(const FrontendSettings& settings,
+        const FrontendSettingsExt1_1& settingsExt1_1) {
+    TunerFrontendDvbsSettings dvbsSettings{
+        .frequency = (int)settings.dvbs().frequency,
+        .modulation = (int)settings.dvbs().modulation,
+        .codeRate = {
+            .fec = (long)settings.dvbs().coderate.fec,
+            .isLinear = settings.dvbs().coderate.isLinear,
+            .isShortFrames = settings.dvbs().coderate.isShortFrames,
+            .bitsPer1000Symbol = (int)settings.dvbs().coderate.bitsPer1000Symbol,
+        },
+        .symbolRate = (int)settings.dvbs().symbolRate,
+        .rolloff = (int)settings.dvbs().rolloff,
+        .pilot = (int)settings.dvbs().pilot,
+        .inputStreamId = (int)settings.dvbs().inputStreamId,
+        .standard = (int)settings.dvbs().standard,
+        .vcm = (int)settings.dvbs().vcmMode,
+    };
+    if (settingsExt1_1.settingExt.getDiscriminator()
+            == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::dvbs) {
+        dvbsSettings.isExtended = true;
+        dvbsSettings.scanType = (int)settingsExt1_1.settingExt.dvbs().scanType;
+        dvbsSettings.isDiseqcRxMessage = settingsExt1_1.settingExt.dvbs().isDiseqcRxMessage;
+    } else {
+        dvbsSettings.isExtended = false;
+    }
+    return dvbsSettings;
+}
+
+TunerFrontendCableSettings FrontendClient::getAidlCableSettings(const FrontendSettings& settings,
+        const FrontendSettingsExt1_1& settingsExt1_1) {
+    TunerFrontendCableSettings cableSettings{
+        .frequency = (int)settings.dvbc().frequency,
+        .modulation = (int)settings.dvbc().modulation,
+        .innerFec = (long)settings.dvbc().fec,
+        .symbolRate = (int)settings.dvbc().symbolRate,
+        .outerFec = (int)settings.dvbc().outerFec,
+        .annex = (int)settings.dvbc().annex,
+        .spectralInversion = (int)settings.dvbc().spectralInversion,
+    };
+    if (settingsExt1_1.settingExt.getDiscriminator()
+            == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::dvbc) {
+        cableSettings.isExtended = true;
+        cableSettings.interleaveMode = (int)settingsExt1_1.settingExt.dvbc().interleaveMode;
+        cableSettings.bandwidth = (int)settingsExt1_1.settingExt.dvbc().bandwidth;
+    } else {
+        cableSettings.isExtended = false;
+    }
+    return cableSettings;
+}
+
+TunerFrontendDvbtSettings FrontendClient::getAidlDvbtSettings(const FrontendSettings& settings,
+        const FrontendSettingsExt1_1& settingsExt1_1) {
+    TunerFrontendDvbtSettings dvbtSettings{
+        .frequency = (int)settings.dvbt().frequency,
+        .transmissionMode = (int)settings.dvbt().transmissionMode,
+        .bandwidth = (int)settings.dvbt().bandwidth,
+        .constellation = (int)settings.dvbt().constellation,
+        .hierarchy = (int)settings.dvbt().hierarchy,
+        .hpCodeRate = (int)settings.dvbt().hpCoderate,
+        .lpCodeRate = (int)settings.dvbt().lpCoderate,
+        .guardInterval = (int)settings.dvbt().guardInterval,
+        .isHighPriority = settings.dvbt().isHighPriority,
+        .standard = (int)settings.dvbt().standard,
+        .isMiso = settings.dvbt().isMiso,
+        .plpMode = (int)settings.dvbt().plpMode,
+        .plpId = (int)settings.dvbt().plpId,
+        .plpGroupId = (int)settings.dvbt().plpGroupId,
+    };
+    if (settingsExt1_1.settingExt.getDiscriminator()
+            == FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::dvbt) {
+        dvbtSettings.isExtended = true;
+        dvbtSettings.constellation = (int)settingsExt1_1.settingExt.dvbt().constellation;
+        dvbtSettings.transmissionMode =
+                (int)settingsExt1_1.settingExt.dvbt().transmissionMode;
+    } else {
+        dvbtSettings.isExtended = false;
+    }
+    return dvbtSettings;
+}
+
+TunerFrontendDtmbSettings FrontendClient::getAidlDtmbSettings(
+        const FrontendSettingsExt1_1& settingsExt1_1) {
+    TunerFrontendDtmbSettings dtmbSettings{
+        .frequency = (int)settingsExt1_1.settingExt.dtmb().frequency,
+        .transmissionMode = (int)settingsExt1_1.settingExt.dtmb().transmissionMode,
+        .bandwidth = (int)settingsExt1_1.settingExt.dtmb().bandwidth,
+        .modulation = (int)settingsExt1_1.settingExt.dtmb().modulation,
+        .codeRate = (int)settingsExt1_1.settingExt.dtmb().codeRate,
+        .guardInterval = (int)settingsExt1_1.settingExt.dtmb().guardInterval,
+        .interleaveMode = (int)settingsExt1_1.settingExt.dtmb().interleaveMode,
+    };
+    return dtmbSettings;
+}
+
+TunerFrontendAtscSettings FrontendClient::getAidlAtscSettings(const FrontendSettings& settings) {
+    TunerFrontendAtscSettings atscSettings{
+        .frequency = (int)settings.atsc().frequency,
+        .modulation = (int)settings.atsc().modulation,
+    };
+    return atscSettings;
+}
+
+TunerFrontendAtsc3Settings FrontendClient::getAidlAtsc3Settings(const FrontendSettings& settings) {
+    TunerFrontendAtsc3Settings atsc3Settings{
+        .frequency = (int)settings.atsc3().frequency,
+        .bandwidth = (int)settings.atsc3().bandwidth,
+        .demodOutputFormat = (int)settings.atsc3().demodOutputFormat,
+    };
+    atsc3Settings.plpSettings.resize(settings.atsc3().plpSettings.size());
+    for (auto plpSetting : settings.atsc3().plpSettings) {
+        atsc3Settings.plpSettings.push_back({
+            .plpId = (int)plpSetting.plpId,
+            .modulation = (int)plpSetting.modulation,
+            .interleaveMode = (int)plpSetting.interleaveMode,
+            .codeRate = (int)plpSetting.codeRate,
+            .fec = (int)plpSetting.fec,
+        });
+    }
+    return atsc3Settings;
+}
+
+TunerFrontendIsdbsSettings FrontendClient::getAidlIsdbsSettings(const FrontendSettings& settings) {
+    TunerFrontendIsdbsSettings isdbsSettings{
+        .frequency = (int)settings.isdbs().frequency,
+        .streamId = (int)settings.isdbs().streamId,
+        .streamIdType = (int)settings.isdbs().streamIdType,
+        .modulation = (int)settings.isdbs().modulation,
+        .codeRate = (int)settings.isdbs().coderate,
+        .symbolRate = (int)settings.isdbs().symbolRate,
+        .rolloff = (int)settings.isdbs().rolloff,
+    };
+    return isdbsSettings;
+}
+
+TunerFrontendIsdbs3Settings FrontendClient::getAidlIsdbs3Settings(
+        const FrontendSettings& settings) {
+    TunerFrontendIsdbs3Settings isdbs3Settings{
+        .frequency = (int)settings.isdbs3().frequency,
+        .streamId = (int)settings.isdbs3().streamId,
+        .streamIdType = (int)settings.isdbs3().streamIdType,
+        .modulation = (int)settings.isdbs3().modulation,
+        .codeRate = (int)settings.isdbs3().coderate,
+        .symbolRate = (int)settings.isdbs3().symbolRate,
+        .rolloff = (int)settings.isdbs3().rolloff,
+    };
+    return isdbs3Settings;
+}
+
+TunerFrontendIsdbtSettings FrontendClient::getAidlIsdbtSettings(const FrontendSettings& settings) {
+    TunerFrontendIsdbtSettings isdbtSettings{
+        .frequency = (int)settings.isdbt().frequency,
+        .modulation = (int)settings.isdbt().modulation,
+        .bandwidth = (int)settings.isdbt().bandwidth,
+        .mode = (int)settings.isdbt().mode,
+        .codeRate = (int)settings.isdbt().coderate,
+        .guardInterval = (int)settings.isdbt().guardInterval,
+        .serviceAreaId = (int)settings.isdbt().serviceAreaId,
+    };
+    return isdbtSettings;
+}
+
+bool FrontendClient::validateExtendedSettings(const FrontendSettingsExt1_1& settingsExt1_1) {
+    return settingsExt1_1.endFrequency != (uint32_t)Constant::INVALID_FRONTEND_SETTING_FREQUENCY
+            || settingsExt1_1.inversion != FrontendSpectralInversion::UNDEFINED
+            || settingsExt1_1.settingExt.getDiscriminator()
+                    != FrontendSettingsExt1_1::SettingsExt::hidl_discriminator::noinit;
+}
+
 /////////////// TunerFrontendCallback ///////////////////////
 
 TunerFrontendCallback::TunerFrontendCallback(sp<FrontendClientCallback> frontendClientCallback)
@@ -440,14 +1115,15 @@
             vector<TunerFrontendScanAtsc3PlpInfo> plp =
                     message.get<TunerFrontendScanMessage::atsc3PlpInfos>();
             hidl_vec<FrontendScanAtsc3PlpInfo> plpInfo;
-            for (TunerFrontendScanAtsc3PlpInfo info : plp) {
+            int size = plp.size();
+            plpInfo.resize(size);
+            for (int i = 0; i < size; i++) {
+                auto info = message.get<TunerFrontendScanMessage::atsc3PlpInfos>()[i];
                 FrontendScanAtsc3PlpInfo p{
                     .plpId = static_cast<uint8_t>(info.plpId),
                     .bLlsFlag = info.llsFlag,
                 };
-                int size = plpInfo.size();
-                plpInfo.resize(size + 1);
-                plpInfo[size] = p;
+                plpInfo[i] = p;
             }
             scanMessage.atsc3PlpInfos(plpInfo);
             break;
diff --git a/media/jni/tuner/FrontendClient.h b/media/jni/tuner/FrontendClient.h
index 03ebb87..298b397 100644
--- a/media/jni/tuner/FrontendClient.h
+++ b/media/jni/tuner/FrontendClient.h
@@ -31,7 +31,19 @@
 
 using ::aidl::android::media::tv::tuner::BnTunerFrontendCallback;
 using ::aidl::android::media::tv::tuner::ITunerFrontend;
+using ::aidl::android::media::tv::tuner::TunerFrontendAnalogSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendAtscSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendAtsc3Settings;
+using ::aidl::android::media::tv::tuner::TunerFrontendCableSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendDvbsSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendDvbtSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendDtmbSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendIsdbsSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendIsdbs3Settings;
+using ::aidl::android::media::tv::tuner::TunerFrontendIsdbtSettings;
 using ::aidl::android::media::tv::tuner::TunerFrontendScanMessage;
+using ::aidl::android::media::tv::tuner::TunerFrontendSettings;
+using ::aidl::android::media::tv::tuner::TunerFrontendStatus;
 
 using ::android::hardware::Return;
 using ::android::hardware::Void;
@@ -171,6 +183,28 @@
     int getId();
 
 private:
+    vector<FrontendStatus> getHidlStatus(vector<TunerFrontendStatus>& aidlStatus);
+    vector<FrontendStatusExt1_1> getHidlStatusExt(vector<TunerFrontendStatus>& aidlStatus);
+
+    TunerFrontendSettings getAidlFrontendSettings(
+            const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+    TunerFrontendAnalogSettings getAidlAnalogSettings(
+            const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+    TunerFrontendDvbsSettings getAidlDvbsSettings(
+            const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+    TunerFrontendCableSettings getAidlCableSettings(
+            const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+    TunerFrontendDvbtSettings getAidlDvbtSettings(
+            const FrontendSettings& settings, const FrontendSettingsExt1_1& settingsExt1_1);
+    TunerFrontendDtmbSettings getAidlDtmbSettings(const FrontendSettingsExt1_1& settingsExt1_1);
+    TunerFrontendAtscSettings getAidlAtscSettings(const FrontendSettings& settings);
+    TunerFrontendAtsc3Settings getAidlAtsc3Settings(const FrontendSettings& settings);
+    TunerFrontendIsdbsSettings getAidlIsdbsSettings(const FrontendSettings& settings);
+    TunerFrontendIsdbs3Settings getAidlIsdbs3Settings(const FrontendSettings& settings);
+    TunerFrontendIsdbtSettings getAidlIsdbtSettings(const FrontendSettings& settings);
+
+    bool validateExtendedSettings(const FrontendSettingsExt1_1& settingsExt1_1);
+
     /**
      * An AIDL Tuner Frontend Singleton assigned at the first time when the Tuner Client
      * opens a frontend cient. Default null when the service does not exist.
diff --git a/media/jni/tuner/TimeFilterClient.cpp b/media/jni/tuner/TimeFilterClient.cpp
index 27ea6e5..432238d 100644
--- a/media/jni/tuner/TimeFilterClient.cpp
+++ b/media/jni/tuner/TimeFilterClient.cpp
@@ -19,6 +19,7 @@
 #include <android-base/logging.h>
 #include <utils/Log.h>
 
+#include "ClientHelper.h"
 #include "TimeFilterClient.h"
 
 using ::android::hardware::tv::tuner::V1_0::Result;
@@ -28,13 +29,12 @@
 
 /////////////// TimeFilterClient ///////////////////////
 
-// TODO: pending aidl interface
-TimeFilterClient::TimeFilterClient() {
-    //mTunerTimeFilter = tunerTimeFilter;
+TimeFilterClient::TimeFilterClient(shared_ptr<ITunerTimeFilter> tunerTimeFilter) {
+    mTunerTimeFilter = tunerTimeFilter;
 }
 
 TimeFilterClient::~TimeFilterClient() {
-    //mTunerTimeFilter = NULL;
+    mTunerTimeFilter = NULL;
     mTimeFilter = NULL;
 }
 
@@ -44,7 +44,10 @@
 }
 
 Result TimeFilterClient::setTimeStamp(long timeStamp) {
-    // TODO: pending aidl interface
+    if (mTunerTimeFilter != NULL) {
+        Status s = mTunerTimeFilter->setTimeStamp(timeStamp);
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mTimeFilter != NULL) {
         return mTimeFilter->setTimeStamp(timeStamp);
@@ -54,7 +57,10 @@
 }
 
 Result TimeFilterClient::clearTimeStamp() {
-    // TODO: pending aidl interface
+    if (mTunerTimeFilter != NULL) {
+        Status s = mTunerTimeFilter->clearTimeStamp();
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mTimeFilter != NULL) {
         return mTimeFilter->clearTimeStamp();
@@ -64,7 +70,14 @@
 }
 
 long TimeFilterClient::getTimeStamp() {
-    // TODO: pending aidl interface
+    if (mTunerTimeFilter != NULL) {
+        int64_t timeStamp;
+        Status s = mTunerTimeFilter->getTimeStamp(&timeStamp);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return (long)Constant64Bit::INVALID_PRESENTATION_TIME_STAMP;
+        }
+        return timeStamp;
+    }
 
     if (mTimeFilter != NULL) {
         Result res;
@@ -84,27 +97,37 @@
 }
 
 long TimeFilterClient::getSourceTime() {
-    // TODO: pending aidl interface
+    if (mTunerTimeFilter != NULL) {
+        int64_t sourceTime;
+        Status s = mTunerTimeFilter->getTimeStamp(&sourceTime);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return (long)Constant64Bit::INVALID_PRESENTATION_TIME_STAMP;
+        }
+        return sourceTime;
+    }
 
     if (mTimeFilter != NULL) {
         Result res;
-        long timestamp;
+        long sourceTime;
         mTimeFilter->getSourceTime(
                 [&](Result r, uint64_t t) {
                     res = r;
-                    timestamp = t;
+                    sourceTime = t;
                 });
         if (res != Result::SUCCESS) {
             return (long)Constant64Bit::INVALID_PRESENTATION_TIME_STAMP;
         }
-        return timestamp;
+        return sourceTime;
     }
 
     return (long)Constant64Bit::INVALID_PRESENTATION_TIME_STAMP;
 }
 
 Result TimeFilterClient::close() {
-    // TODO: pending aidl interface
+    if (mTunerTimeFilter != NULL) {
+        Status s = mTunerTimeFilter->close();
+        return ClientHelper::getServiceSpecificErrorCode(s);
+    }
 
     if (mTimeFilter != NULL) {
         return mTimeFilter->close();
diff --git a/media/jni/tuner/TimeFilterClient.h b/media/jni/tuner/TimeFilterClient.h
index 9a9d172..56ddd68 100644
--- a/media/jni/tuner/TimeFilterClient.h
+++ b/media/jni/tuner/TimeFilterClient.h
@@ -17,12 +17,13 @@
 #ifndef _ANDROID_MEDIA_TV_TIME_FILTER_CLIENT_H_
 #define _ANDROID_MEDIA_TV_TIME_FILTER_CLIENT_H_
 
-//#include <aidl/android/media/tv/tuner/ITunerTimeFilter.h>
+#include <aidl/android/media/tv/tuner/ITunerTimeFilter.h>
 #include <android/hardware/tv/tuner/1.0/ITimeFilter.h>
 #include <android/hardware/tv/tuner/1.1/types.h>
 
-//using ::aidl::android::media::tv::tuner::ITunerTimeFilter;
+using ::aidl::android::media::tv::tuner::ITunerTimeFilter;
 
+using Status = ::ndk::ScopedAStatus;
 using ::android::hardware::Return;
 using ::android::hardware::Void;
 using ::android::hardware::hidl_vec;
@@ -36,8 +37,7 @@
 struct TimeFilterClient : public RefBase {
 
 public:
-    // TODO: add TunerTimeFilter as parameter.
-    TimeFilterClient();
+    TimeFilterClient(shared_ptr<ITunerTimeFilter> tunerTimeFilter);
     ~TimeFilterClient();
 
     // TODO: remove after migration to Tuner Service is done.
@@ -73,8 +73,7 @@
      * An AIDL Tuner TimeFilter Singleton assigned at the first time the Tuner Client
      * opens an TimeFilter. Default null when time filter is not opened.
      */
-    // TODO: pending on aidl interface
-    //shared_ptr<ITunerTimeFilter> mTunerTimeFilter;
+    shared_ptr<ITunerTimeFilter> mTunerTimeFilter;
 
     /**
      * A TimeFilter HAL interface that is ready before migrating to the TunerTimeFilter.
diff --git a/media/jni/tuner/TunerClient.cpp b/media/jni/tuner/TunerClient.cpp
index 4498f54..a604490d 100644
--- a/media/jni/tuner/TunerClient.cpp
+++ b/media/jni/tuner/TunerClient.cpp
@@ -200,7 +200,14 @@
 }
 
 shared_ptr<DemuxCapabilities> TunerClient::getDemuxCaps() {
-    // pending aidl interface
+    if (mTunerService != NULL) {
+        TunerDemuxCapabilities aidlCaps;
+        Status s = mTunerService->getDemuxCaps(&aidlCaps);
+        if (ClientHelper::getServiceSpecificErrorCode(s) != Result::SUCCESS) {
+            return NULL;
+        }
+        return make_shared<DemuxCapabilities>(getHidlDemuxCaps(aidlCaps));
+    }
 
     if (mTuner != NULL) {
         Result res;
@@ -235,7 +242,8 @@
         }
     }
 
-    return NULL;}
+    return NULL;
+}
 
 sp<LnbClient> TunerClient::openLnb(int lnbHandle) {
     if (mTunerService != NULL) {
@@ -458,6 +466,26 @@
     return descrambler;
 }
 
+DemuxCapabilities TunerClient::getHidlDemuxCaps(TunerDemuxCapabilities& aidlCaps) {
+    DemuxCapabilities caps{
+        .numDemux = (uint32_t)aidlCaps.numDemux,
+        .numRecord = (uint32_t)aidlCaps.numRecord,
+        .numPlayback = (uint32_t)aidlCaps.numPlayback,
+        .numTsFilter = (uint32_t)aidlCaps.numTsFilter,
+        .numSectionFilter = (uint32_t)aidlCaps.numSectionFilter,
+        .numAudioFilter = (uint32_t)aidlCaps.numAudioFilter,
+        .numVideoFilter = (uint32_t)aidlCaps.numVideoFilter,
+        .numPesFilter = (uint32_t)aidlCaps.numPesFilter,
+        .numPcrFilter = (uint32_t)aidlCaps.numPcrFilter,
+        .numBytesInSectionFilter = (uint32_t)aidlCaps.numBytesInSectionFilter,
+        .filterCaps = (uint32_t)aidlCaps.filterCaps,
+        .bTimeFilter = aidlCaps.bTimeFilter,
+    };
+    caps.linkCaps.resize(aidlCaps.linkCaps.size());
+    copy(aidlCaps.linkCaps.begin(), aidlCaps.linkCaps.end(), caps.linkCaps.begin());
+    return caps;
+}
+
 FrontendInfo TunerClient::FrontendInfoAidlToHidl(TunerFrontendInfo aidlFrontendInfo) {
     FrontendInfo hidlFrontendInfo {
         .type = static_cast<FrontendType>(aidlFrontendInfo.type),
diff --git a/media/jni/tuner/TunerClient.h b/media/jni/tuner/TunerClient.h
index 6ce6661..acd018e 100644
--- a/media/jni/tuner/TunerClient.h
+++ b/media/jni/tuner/TunerClient.h
@@ -32,6 +32,7 @@
 
 using Status = ::ndk::ScopedAStatus;
 
+using ::aidl::android::media::tv::tuner::TunerDemuxCapabilities;
 using ::aidl::android::media::tv::tuner::ITunerService;
 using ::aidl::android::media::tv::tuner::TunerFrontendInfo;
 using ::aidl::android::media::tv::tunerresourcemanager::ITunerResourceManager;
@@ -145,6 +146,7 @@
     sp<ILnb> openHidlLnbByName(string name, LnbId& lnbId);
     sp<IDescrambler> openHidlDescrambler();
     vector<int> getLnbHandles();
+    DemuxCapabilities getHidlDemuxCaps(TunerDemuxCapabilities& aidlCaps);
     FrontendInfo FrontendInfoAidlToHidl(TunerFrontendInfo aidlFrontendInfo);
     void updateTunerResources();
     void updateFrontendResources();
diff --git a/media/native/midi/include/amidi/AMidi.h b/media/native/midi/include/amidi/AMidi.h
index 0f930b5..742db34 100644
--- a/media/native/midi/include/amidi/AMidi.h
+++ b/media/native/midi/include/amidi/AMidi.h
@@ -61,8 +61,6 @@
     AMIDI_DEVICE_TYPE_BLUETOOTH = 3 /* A MIDI device connected via BlueTooth */
 };
 
-#if __ANDROID_API__ >= 29
-
 /*
  * Device API
  */
@@ -249,8 +247,6 @@
  */
 void AMIDI_API AMidiInputPort_close(const AMidiInputPort *inputPort) __INTRODUCED_IN(29);
 
-#endif /* __ANDROID_API__ >= 29 */
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 1286fc1..4b0062b 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -256,7 +256,7 @@
 
         CountDownLatch latch = new CountDownLatch(1);
 
-        addManagerCallback(new MediaRouter2Manager.Callback());
+        addManagerCallback(new MediaRouter2Manager.Callback() {});
         addRouterCallback(new MediaRouter2.RouteCallback() {});
         addTransferCallback(new MediaRouter2.TransferCallback() {
             @Override
@@ -530,7 +530,7 @@
     @Test
     public void testSetSystemRouteVolume() throws Exception {
         // ensure client
-        addManagerCallback(new MediaRouter2Manager.Callback());
+        addManagerCallback(new MediaRouter2Manager.Callback() {});
         String selectedSystemRouteId =
                 MediaRouter2Utils.getOriginalId(
                 mManager.getActiveSessions().get(0).getSelectedRoutes().get(0));
@@ -902,7 +902,7 @@
 
     private void releaseAllSessions() {
         // ensure ManagerRecord in MediaRouter2ServiceImpl
-        addManagerCallback(new MediaRouter2Manager.Callback());
+        addManagerCallback(new MediaRouter2Manager.Callback() {});
 
         for (RoutingSessionInfo session : mManager.getActiveSessions()) {
             mManager.releaseSession(session);
diff --git a/native/android/OWNERS b/native/android/OWNERS
index ac5a895..d414ed4 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -1,3 +1,4 @@
 per-file libandroid_net.map.txt, net.c = set noparent
 per-file libandroid_net.map.txt, net.c = codewiz@google.com, jchalard@google.com, junyulai@google.com
 per-file libandroid_net.map.txt, net.c = lorenzo@google.com, reminv@google.com, satk@google.com
+per-file system_fonts.cpp = file:/graphics/java/android/graphics/fonts/OWNERS
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 45f42f1b5..48d7380 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -264,7 +264,7 @@
                 static_cast<minikin::FamilyVariant>(matcher->mFamilyVariant),
                 1  /* maxRun */);
 
-    const minikin::Font* font = runs[0].fakedFont.font;
+    const std::shared_ptr<minikin::Font>& font = runs[0].fakedFont.font;
     std::unique_ptr<AFont> result = std::make_unique<AFont>();
     const android::MinikinFontSkia* minikinFontSkia =
             reinterpret_cast<android::MinikinFontSkia*>(font->typeface().get());
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index d464587..3d633ea 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -27,14 +27,13 @@
     ],
 
     shared_libs: [
-        "libandroid_runtime",
         "libhwui",
         "liblog",
     ],
 
     header_libs: [
-        "libhwui_internal_headers",
         "jni_headers",
+        "libhwui_internal_headers",
     ],
 
     static_libs: ["libarect"],
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index 620c7ae..72589e3 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -18,6 +18,7 @@
 
 import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
 import static android.text.TextUtils.emptyIfNull;
+import static android.text.TextUtils.isEmpty;
 import static android.text.TextUtils.withoutPrefix;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -67,7 +68,13 @@
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
 
         String deviceProfile = getRequest().getDeviceProfile();
-        String profileName = getDeviceProfileName(deviceProfile);
+        String profilePrivacyDisclaimer = emptyIfNull(getRequest()
+                .getDeviceProfilePrivilegesDescription())
+                .replace("APP_NAME", getCallingAppName());
+        boolean useDeviceProfile = deviceProfile != null && !isEmpty(profilePrivacyDisclaimer);
+        String profileName = useDeviceProfile
+                ? getDeviceProfileName(deviceProfile)
+                : getString(R.string.profile_name_generic);
 
         if (getRequest().isSingleDevice()) {
             setContentView(R.layout.device_confirmation);
@@ -110,15 +117,12 @@
 
         TextView profileSummary = findViewById(R.id.profile_summary);
 
-        if (deviceProfile != null) {
-            String privacyDisclaimer = emptyIfNull(getRequest()
-                    .getDeviceProfilePrivilegesDescription())
-                    .replace("APP_NAME", getCallingAppName());
+        if (useDeviceProfile) {
             profileSummary.setVisibility(View.VISIBLE);
             profileSummary.setText(getString(R.string.profile_summary,
                     getCallingAppName(),
                     profileName,
-                    privacyDisclaimer));
+                    profilePrivacyDisclaimer));
         } else {
             profileSummary.setVisibility(View.GONE);
         }
@@ -142,7 +146,7 @@
                 return getString(R.string.profile_name_watch);
             }
             default: {
-                Log.wtf(LOG_TAG,
+                Log.w(LOG_TAG,
                         "No localized profile name found for device profile: " + deviceProfile);
                 return withoutPrefix("android.app.role.COMPANION_DEVICE_", deviceProfile)
                         .toLowerCase()
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
new file mode 100644
index 0000000..8db8d76
--- /dev/null
+++ b/packages/Connectivity/framework/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2020 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.
+//
+
+// TODO: use a java_library in the bootclasspath instead
+filegroup {
+    name: "framework-connectivity-sources",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
+    path: "src",
+    visibility: [
+        "//frameworks/base",
+        "//packages/modules/Connectivity:__subpackages__",
+    ],
+}
\ No newline at end of file
diff --git a/core/java/android/net/CaptivePortal.java b/packages/Connectivity/framework/src/android/net/CaptivePortal.java
similarity index 100%
rename from core/java/android/net/CaptivePortal.java
rename to packages/Connectivity/framework/src/android/net/CaptivePortal.java
diff --git a/core/java/android/net/CaptivePortalData.aidl b/packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl
similarity index 100%
rename from core/java/android/net/CaptivePortalData.aidl
rename to packages/Connectivity/framework/src/android/net/CaptivePortalData.aidl
diff --git a/core/java/android/net/CaptivePortalData.java b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java
similarity index 100%
rename from core/java/android/net/CaptivePortalData.java
rename to packages/Connectivity/framework/src/android/net/CaptivePortalData.java
diff --git a/core/java/android/net/ConnectionInfo.aidl b/packages/Connectivity/framework/src/android/net/ConnectionInfo.aidl
similarity index 100%
rename from core/java/android/net/ConnectionInfo.aidl
rename to packages/Connectivity/framework/src/android/net/ConnectionInfo.aidl
diff --git a/core/java/android/net/ConnectionInfo.java b/packages/Connectivity/framework/src/android/net/ConnectionInfo.java
similarity index 100%
rename from core/java/android/net/ConnectionInfo.java
rename to packages/Connectivity/framework/src/android/net/ConnectionInfo.java
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.aidl b/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl
similarity index 100%
rename from core/java/android/net/ConnectivityDiagnosticsManager.aidl
rename to packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.aidl
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.java
similarity index 100%
rename from core/java/android/net/ConnectivityDiagnosticsManager.java
rename to packages/Connectivity/framework/src/android/net/ConnectivityDiagnosticsManager.java
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
new file mode 100644
index 0000000..9afa5d1
--- /dev/null
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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 android.net;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for performing registration for all core connectivity services.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializer {
+    private ConnectivityFrameworkInitializer() {}
+
+    /**
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers all core
+     * connectivity services to {@link Context}, so that {@link Context#getSystemService} can
+     * return them.
+     *
+     * @throws IllegalStateException if this is called anywhere besides
+     * {@link SystemServiceRegistry}.
+     */
+    public static void registerServiceWrappers() {
+        // registerContextAwareService will throw if this is called outside of SystemServiceRegistry
+        // initialization.
+        SystemServiceRegistry.registerContextAwareService(
+                Context.CONNECTIVITY_SERVICE,
+                ConnectivityManager.class,
+                (context, serviceBinder) -> {
+                    IConnectivityManager icm = IConnectivityManager.Stub.asInterface(serviceBinder);
+                    return new ConnectivityManager(context, icm);
+                }
+        );
+
+        // TODO: move outside of the connectivity JAR
+        SystemServiceRegistry.registerContextAwareService(
+                Context.VPN_MANAGEMENT_SERVICE,
+                VpnManager.class,
+                (context) -> {
+                    final ConnectivityManager cm = context.getSystemService(
+                            ConnectivityManager.class);
+                    return cm.createVpnManager();
+                }
+        );
+
+        SystemServiceRegistry.registerContextAwareService(
+                Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
+                ConnectivityDiagnosticsManager.class,
+                (context) -> {
+                    final ConnectivityManager cm = context.getSystemService(
+                            ConnectivityManager.class);
+                    return cm.createDiagnosticsManager();
+                }
+        );
+
+        SystemServiceRegistry.registerContextAwareService(
+                Context.TEST_NETWORK_SERVICE,
+                TestNetworkManager.class,
+                context -> {
+                    final ConnectivityManager cm = context.getSystemService(
+                            ConnectivityManager.class);
+                    return cm.startOrGetTestNetworkManager();
+                }
+        );
+    }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
similarity index 96%
rename from core/java/android/net/ConnectivityManager.java
rename to packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 2e45ed8..3b054e9 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -15,7 +15,9 @@
  */
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
 import static android.net.NetworkRequest.Type.LISTEN;
 import static android.net.NetworkRequest.Type.REQUEST;
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
@@ -28,6 +30,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.PendingIntent;
@@ -3228,32 +3231,6 @@
         }
     }
 
-    /** {@hide} - returns the factory serial number */
-    @UnsupportedAppUsage
-    @RequiresPermission(anyOf = {
-            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
-            android.Manifest.permission.NETWORK_FACTORY})
-    public int registerNetworkFactory(Messenger messenger, String name) {
-        try {
-            return mService.registerNetworkFactory(messenger, name);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /** {@hide} */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    @RequiresPermission(anyOf = {
-            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
-            android.Manifest.permission.NETWORK_FACTORY})
-    public void unregisterNetworkFactory(Messenger messenger) {
-        try {
-            mService.unregisterNetworkFactory(messenger);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     /**
      * Registers the specified {@link NetworkProvider}.
      * Each listener must only be registered once. The listener can be unregistered with
@@ -4606,7 +4583,7 @@
             // Set HTTP proxy system properties to match network.
             // TODO: Deprecate this static method and replace it with a non-static version.
             try {
-                Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
+                Proxy.setHttpProxyConfiguration(getInstance().getDefaultProxy());
             } catch (SecurityException e) {
                 // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy.
                 Log.e(TAG, "Can't set proxy properties", e);
@@ -4820,6 +4797,28 @@
         }
     }
 
+    /** @hide */
+    public TestNetworkManager startOrGetTestNetworkManager() {
+        final IBinder tnBinder;
+        try {
+            tnBinder = mService.startOrGetTestNetworkService();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder));
+    }
+
+    /** @hide */
+    public VpnManager createVpnManager() {
+        return new VpnManager(mContext, mService);
+    }
+
+    /** @hide */
+    public ConnectivityDiagnosticsManager createDiagnosticsManager() {
+        return new ConnectivityDiagnosticsManager(mContext, mService);
+    }
+
     /**
      * Simulates a Data Stall for the specified Network.
      *
@@ -4846,9 +4845,13 @@
         }
     }
 
-    private void setOemNetworkPreference(@NonNull OemNetworkPreferences preference) {
-        Log.d(TAG, "setOemNetworkPreference called with preference: "
-                + preference.toString());
+    private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
+        try {
+            mService.setOemNetworkPreference(preference);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString());
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     @NonNull
@@ -4964,4 +4967,92 @@
         }
         return null;
     }
+
+    /**
+     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, but
+     * does not cause any networks to retain the NET_CAPABILITY_FOREGROUND capability. This can
+     * be used to request that the system provide a network without causing the network to be
+     * in the foreground.
+     *
+     * <p>This method will attempt to find the best network that matches the passed
+     * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the
+     * criteria. The platform will evaluate which network is the best at its own discretion.
+     * Throughput, latency, cost per byte, policy, user preference and other considerations
+     * may be factored in the decision of what is considered the best network.
+     *
+     * <p>As long as this request is outstanding, the platform will try to maintain the best network
+     * matching this request, while always attempting to match the request to a better network if
+     * possible. If a better match is found, the platform will switch this request to the now-best
+     * network and inform the app of the newly best network by invoking
+     * {@link NetworkCallback#onAvailable(Network)} on the provided callback. Note that the platform
+     * will not try to maintain any other network than the best one currently matching the request:
+     * a network not matching any network request may be disconnected at any time.
+     *
+     * <p>For example, an application could use this method to obtain a connected cellular network
+     * even if the device currently has a data connection over Ethernet. This may cause the cellular
+     * radio to consume additional power. Or, an application could inform the system that it wants
+     * a network supporting sending MMSes and have the system let it know about the currently best
+     * MMS-supporting network through the provided {@link NetworkCallback}.
+     *
+     * <p>The status of the request can be followed by listening to the various callbacks described
+     * in {@link NetworkCallback}. The {@link Network} object passed to the callback methods can be
+     * used to direct traffic to the network (although accessing some networks may be subject to
+     * holding specific permissions). Callers will learn about the specific characteristics of the
+     * network through
+     * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} and
+     * {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)}. The methods of the
+     * provided {@link NetworkCallback} will only be invoked due to changes in the best network
+     * matching the request at any given time; therefore when a better network matching the request
+     * becomes available, the {@link NetworkCallback#onAvailable(Network)} method is called
+     * with the new network after which no further updates are given about the previously-best
+     * network, unless it becomes the best again at some later time. All callbacks are invoked
+     * in order on the same thread, which by default is a thread created by the framework running
+     * in the app.
+     *
+     * <p>This{@link NetworkRequest} will live until released via
+     * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits, at
+     * which point the system may let go of the network at any time.
+     *
+     * <p>It is presently unsupported to request a network with mutable
+     * {@link NetworkCapabilities} such as
+     * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+     * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+     * as these {@code NetworkCapabilities} represent states that a particular
+     * network may never attain, and whether a network will attain these states
+     * is unknown prior to bringing up the network so the framework does not
+     * know how to go about satisfying a request with these capabilities.
+     *
+     * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the
+     * number of outstanding requests to 100 per app (identified by their UID), shared with
+     * all variants of this method, of {@link #registerNetworkCallback} as well as
+     * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}.
+     * Requesting a network with this method will count toward this limit. If this limit is
+     * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources,
+     * make sure to unregister the callbacks with
+     * {@link #unregisterNetworkCallback(NetworkCallback)}.
+     *
+     * @param request {@link NetworkRequest} describing this request.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     *                If null, the callback is invoked on the default internal Handler.
+     * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+     *                        the callback must not be shared - it uniquely specifies this request.
+     * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
+     * @throws SecurityException if missing the appropriate permissions.
+     * @throws RuntimeException if the app already has too many callbacks registered.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @SuppressLint("ExecutorRegistration")
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+    })
+    public void requestBackgroundNetwork(@NonNull NetworkRequest request,
+            @Nullable Handler handler, @NonNull NetworkCallback networkCallback) {
+        final NetworkCapabilities nc = request.networkCapabilities;
+        sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST,
+                TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler));
+    }
 }
diff --git a/core/java/android/net/ConnectivityMetricsEvent.aidl b/packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl
similarity index 100%
rename from core/java/android/net/ConnectivityMetricsEvent.aidl
rename to packages/Connectivity/framework/src/android/net/ConnectivityMetricsEvent.aidl
diff --git a/core/java/android/net/ConnectivityThread.java b/packages/Connectivity/framework/src/android/net/ConnectivityThread.java
similarity index 100%
rename from core/java/android/net/ConnectivityThread.java
rename to packages/Connectivity/framework/src/android/net/ConnectivityThread.java
diff --git a/core/java/android/net/DhcpInfo.aidl b/packages/Connectivity/framework/src/android/net/DhcpInfo.aidl
similarity index 100%
rename from core/java/android/net/DhcpInfo.aidl
rename to packages/Connectivity/framework/src/android/net/DhcpInfo.aidl
diff --git a/core/java/android/net/DhcpInfo.java b/packages/Connectivity/framework/src/android/net/DhcpInfo.java
similarity index 100%
rename from core/java/android/net/DhcpInfo.java
rename to packages/Connectivity/framework/src/android/net/DhcpInfo.java
diff --git a/core/java/android/net/DnsResolver.java b/packages/Connectivity/framework/src/android/net/DnsResolver.java
similarity index 100%
rename from core/java/android/net/DnsResolver.java
rename to packages/Connectivity/framework/src/android/net/DnsResolver.java
diff --git a/core/java/android/net/ICaptivePortal.aidl b/packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl
similarity index 100%
rename from core/java/android/net/ICaptivePortal.aidl
rename to packages/Connectivity/framework/src/android/net/ICaptivePortal.aidl
diff --git a/core/java/android/net/IConnectivityDiagnosticsCallback.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityDiagnosticsCallback.aidl
similarity index 100%
rename from core/java/android/net/IConnectivityDiagnosticsCallback.aidl
rename to packages/Connectivity/framework/src/android/net/IConnectivityDiagnosticsCallback.aidl
diff --git a/core/java/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
similarity index 97%
rename from core/java/android/net/IConnectivityManager.aidl
rename to packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
index 6fecee6..e2672c4 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -29,6 +29,7 @@
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
 import android.net.NetworkState;
+import android.net.OemNetworkPreferences;
 import android.net.ProxyInfo;
 import android.net.UidRange;
 import android.net.QosSocketInfo;
@@ -43,7 +44,6 @@
 import com.android.connectivity.aidl.INetworkAgent;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 
 /**
@@ -157,9 +157,6 @@
 
     boolean requestBandwidthUpdate(in Network network);
 
-    int registerNetworkFactory(in Messenger messenger, in String name);
-    void unregisterNetworkFactory(in Messenger messenger);
-
     int registerNetworkProvider(in Messenger messenger, in String name);
     void unregisterNetworkProvider(in Messenger messenger);
 
@@ -244,4 +241,6 @@
 
     void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback);
     void unregisterQosCallback(in IQosCallback callback);
+
+    void setOemNetworkPreference(in OemNetworkPreferences preference);
 }
diff --git a/core/java/android/net/ISocketKeepaliveCallback.aidl b/packages/Connectivity/framework/src/android/net/ISocketKeepaliveCallback.aidl
similarity index 100%
rename from core/java/android/net/ISocketKeepaliveCallback.aidl
rename to packages/Connectivity/framework/src/android/net/ISocketKeepaliveCallback.aidl
diff --git a/core/java/android/net/ITestNetworkManager.aidl b/packages/Connectivity/framework/src/android/net/ITestNetworkManager.aidl
similarity index 100%
rename from core/java/android/net/ITestNetworkManager.aidl
rename to packages/Connectivity/framework/src/android/net/ITestNetworkManager.aidl
diff --git a/core/java/android/net/InetAddresses.java b/packages/Connectivity/framework/src/android/net/InetAddresses.java
similarity index 100%
rename from core/java/android/net/InetAddresses.java
rename to packages/Connectivity/framework/src/android/net/InetAddresses.java
diff --git a/core/java/android/net/InterfaceConfiguration.aidl b/packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl
similarity index 100%
rename from core/java/android/net/InterfaceConfiguration.aidl
rename to packages/Connectivity/framework/src/android/net/InterfaceConfiguration.aidl
diff --git a/core/java/android/net/InvalidPacketException.java b/packages/Connectivity/framework/src/android/net/InvalidPacketException.java
similarity index 100%
rename from core/java/android/net/InvalidPacketException.java
rename to packages/Connectivity/framework/src/android/net/InvalidPacketException.java
diff --git a/core/java/android/net/IpConfiguration.aidl b/packages/Connectivity/framework/src/android/net/IpConfiguration.aidl
similarity index 100%
rename from core/java/android/net/IpConfiguration.aidl
rename to packages/Connectivity/framework/src/android/net/IpConfiguration.aidl
diff --git a/core/java/android/net/IpConfiguration.java b/packages/Connectivity/framework/src/android/net/IpConfiguration.java
similarity index 100%
rename from core/java/android/net/IpConfiguration.java
rename to packages/Connectivity/framework/src/android/net/IpConfiguration.java
diff --git a/core/java/android/net/IpPrefix.aidl b/packages/Connectivity/framework/src/android/net/IpPrefix.aidl
similarity index 100%
rename from core/java/android/net/IpPrefix.aidl
rename to packages/Connectivity/framework/src/android/net/IpPrefix.aidl
diff --git a/core/java/android/net/IpPrefix.java b/packages/Connectivity/framework/src/android/net/IpPrefix.java
similarity index 100%
rename from core/java/android/net/IpPrefix.java
rename to packages/Connectivity/framework/src/android/net/IpPrefix.java
diff --git a/core/java/android/net/KeepalivePacketData.aidl b/packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl
similarity index 100%
rename from core/java/android/net/KeepalivePacketData.aidl
rename to packages/Connectivity/framework/src/android/net/KeepalivePacketData.aidl
diff --git a/core/java/android/net/KeepalivePacketData.java b/packages/Connectivity/framework/src/android/net/KeepalivePacketData.java
similarity index 100%
rename from core/java/android/net/KeepalivePacketData.java
rename to packages/Connectivity/framework/src/android/net/KeepalivePacketData.java
diff --git a/core/java/android/net/LinkAddress.aidl b/packages/Connectivity/framework/src/android/net/LinkAddress.aidl
similarity index 100%
rename from core/java/android/net/LinkAddress.aidl
rename to packages/Connectivity/framework/src/android/net/LinkAddress.aidl
diff --git a/core/java/android/net/LinkAddress.java b/packages/Connectivity/framework/src/android/net/LinkAddress.java
similarity index 100%
rename from core/java/android/net/LinkAddress.java
rename to packages/Connectivity/framework/src/android/net/LinkAddress.java
diff --git a/core/java/android/net/LinkProperties.aidl b/packages/Connectivity/framework/src/android/net/LinkProperties.aidl
similarity index 100%
rename from core/java/android/net/LinkProperties.aidl
rename to packages/Connectivity/framework/src/android/net/LinkProperties.aidl
diff --git a/core/java/android/net/LinkProperties.java b/packages/Connectivity/framework/src/android/net/LinkProperties.java
similarity index 100%
rename from core/java/android/net/LinkProperties.java
rename to packages/Connectivity/framework/src/android/net/LinkProperties.java
diff --git a/core/java/android/net/MacAddress.aidl b/packages/Connectivity/framework/src/android/net/MacAddress.aidl
similarity index 100%
rename from core/java/android/net/MacAddress.aidl
rename to packages/Connectivity/framework/src/android/net/MacAddress.aidl
diff --git a/core/java/android/net/MacAddress.java b/packages/Connectivity/framework/src/android/net/MacAddress.java
similarity index 100%
rename from core/java/android/net/MacAddress.java
rename to packages/Connectivity/framework/src/android/net/MacAddress.java
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/packages/Connectivity/framework/src/android/net/NattKeepalivePacketData.java
similarity index 100%
rename from core/java/android/net/NattKeepalivePacketData.java
rename to packages/Connectivity/framework/src/android/net/NattKeepalivePacketData.java
diff --git a/core/java/android/net/NattSocketKeepalive.java b/packages/Connectivity/framework/src/android/net/NattSocketKeepalive.java
similarity index 100%
rename from core/java/android/net/NattSocketKeepalive.java
rename to packages/Connectivity/framework/src/android/net/NattSocketKeepalive.java
diff --git a/core/java/android/net/Network.aidl b/packages/Connectivity/framework/src/android/net/Network.aidl
similarity index 100%
rename from core/java/android/net/Network.aidl
rename to packages/Connectivity/framework/src/android/net/Network.aidl
diff --git a/core/java/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java
similarity index 94%
rename from core/java/android/net/Network.java
rename to packages/Connectivity/framework/src/android/net/Network.java
index 53996a5..46141e0 100644
--- a/core/java/android/net/Network.java
+++ b/packages/Connectivity/framework/src/android/net/Network.java
@@ -22,6 +22,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -381,7 +382,13 @@
         // Query a property of the underlying socket to ensure that the socket's file descriptor
         // exists, is available to bind to a network and is not closed.
         socket.getReuseAddress();
-        bindSocket(socket.getFileDescriptor$());
+        final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket);
+        bindSocket(pfd.getFileDescriptor());
+        // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the
+        // dup share the underlying socket in the kernel. The socket is never truly closed until the
+        // last fd pointing to the socket being closed. So close the dup one after binding the
+        // socket to control the lifetime of the dup fd.
+        pfd.close();
     }
 
     /**
@@ -393,7 +400,13 @@
         // Query a property of the underlying socket to ensure that the socket's file descriptor
         // exists, is available to bind to a network and is not closed.
         socket.getReuseAddress();
-        bindSocket(socket.getFileDescriptor$());
+        final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket);
+        bindSocket(pfd.getFileDescriptor());
+        // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the
+        // dup share the underlying socket in the kernel. The socket is never truly closed until the
+        // last fd pointing to the socket being closed. So close the dup one after binding the
+        // socket to control the lifetime of the dup fd.
+        pfd.close();
     }
 
     /**
@@ -421,7 +434,7 @@
             throw new SocketException("Only AF_INET/AF_INET6 sockets supported");
         }
 
-        final int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
+        final int err = NetworkUtils.bindSocketToNetwork(fd, netId);
         if (err != 0) {
             // bindSocketToNetwork returns negative errno.
             throw new ErrnoException("Binding socket to network " + netId, -err)
diff --git a/core/java/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
similarity index 99%
rename from core/java/android/net/NetworkAgent.java
rename to packages/Connectivity/framework/src/android/net/NetworkAgent.java
index d22d82d..27aa15d 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -20,6 +20,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -775,7 +776,8 @@
      * @param underlyingNetworks the new list of underlying networks.
      * @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])}
      */
-    public final void setUnderlyingNetworks(@Nullable List<Network> underlyingNetworks) {
+    public final void setUnderlyingNetworks(
+            @SuppressLint("NullableCollection") @Nullable List<Network> underlyingNetworks) {
         final ArrayList<Network> underlyingArray = (underlyingNetworks != null)
                 ? new ArrayList<>(underlyingNetworks) : null;
         queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray));
diff --git a/core/java/android/net/NetworkAgentConfig.aidl b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl
similarity index 100%
rename from core/java/android/net/NetworkAgentConfig.aidl
rename to packages/Connectivity/framework/src/android/net/NetworkAgentConfig.aidl
diff --git a/core/java/android/net/NetworkAgentConfig.java b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
similarity index 98%
rename from core/java/android/net/NetworkAgentConfig.java
rename to packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
index fe1268d..664c265 100644
--- a/core/java/android/net/NetworkAgentConfig.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgentConfig.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -125,6 +127,7 @@
      * @return the subscriber ID, or null if none.
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     @Nullable
     public String getSubscriberId() {
         return subscriberId;
@@ -275,6 +278,7 @@
          * @hide
          */
         @NonNull
+        @SystemApi(client = MODULE_LIBRARIES)
         public Builder setSubscriberId(@Nullable String subscriberId) {
             mConfig.subscriberId = subscriberId;
             return this;
diff --git a/core/java/android/net/NetworkCapabilities.aidl b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl
similarity index 100%
rename from core/java/android/net/NetworkCapabilities.aidl
rename to packages/Connectivity/framework/src/android/net/NetworkCapabilities.aidl
diff --git a/core/java/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
similarity index 96%
rename from core/java/android/net/NetworkCapabilities.java
rename to packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 0a895b9..55b2c3c 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -34,9 +34,9 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.Preconditions;
+import com.android.net.module.util.CollectionUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -204,6 +204,7 @@
             NET_CAPABILITY_TEMPORARILY_NOT_METERED,
             NET_CAPABILITY_OEM_PRIVATE,
             NET_CAPABILITY_VEHICLE_INTERNAL,
+            NET_CAPABILITY_NOT_VCN_MANAGED,
     })
     public @interface NetCapability { }
 
@@ -399,8 +400,23 @@
     @SystemApi
     public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27;
 
+    /**
+     * Indicates that this network is not subsumed by a Virtual Carrier Network (VCN).
+     * <p>
+     * To provide an experience on a VCN similar to a single traditional carrier network, in
+     * some cases the system sets this bit is set by default in application's network requests,
+     * and may choose to remove it at its own discretion when matching the request to a network.
+     * <p>
+     * Applications that want to know about a Virtual Carrier Network's underlying networks,
+     * for example to use them for multipath purposes, should remove this bit from their network
+     * requests ; the system will not add it back once removed.
+     * @hide
+     */
+    @SystemApi
+    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
+
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_VEHICLE_INTERNAL;
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VCN_MANAGED;
 
     /**
      * Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -417,7 +433,8 @@
             | (1 << NET_CAPABILITY_NOT_CONGESTED)
             | (1 << NET_CAPABILITY_NOT_SUSPENDED)
             | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY)
-            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED);
+            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+            | (1 << NET_CAPABILITY_NOT_VCN_MANAGED);
 
     /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -426,16 +443,21 @@
      * can get into a cycle where the NetworkFactory endlessly churns out NetworkAgents that then
      * get immediately torn down because they do not have the requested capability.
      */
+    // Note that as a historical exception, the TRUSTED and NOT_VCN_MANAGED capabilities
+    // are mutable but requestable. Factories are responsible for not getting
+    // in an infinite loop about these.
     private static final long NON_REQUESTABLE_CAPABILITIES =
-            MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_TRUSTED);
+            MUTABLE_CAPABILITIES
+            & ~(1 << NET_CAPABILITY_TRUSTED)
+            & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED);
 
     /**
      * Capabilities that are set by default when the object is constructed.
      */
     private static final long DEFAULT_CAPABILITIES =
-            (1 << NET_CAPABILITY_NOT_RESTRICTED) |
-            (1 << NET_CAPABILITY_TRUSTED) |
-            (1 << NET_CAPABILITY_NOT_VPN);
+            (1 << NET_CAPABILITY_NOT_RESTRICTED)
+            | (1 << NET_CAPABILITY_TRUSTED)
+            | (1 << NET_CAPABILITY_NOT_VPN);
 
     /**
      * Capabilities that suggest that a network is restricted.
@@ -495,7 +517,8 @@
             | (1 << NET_CAPABILITY_NOT_VPN)
             | (1 << NET_CAPABILITY_NOT_ROAMING)
             | (1 << NET_CAPABILITY_NOT_CONGESTED)
-            | (1 << NET_CAPABILITY_NOT_SUSPENDED);
+            | (1 << NET_CAPABILITY_NOT_SUSPENDED)
+            | (1 << NET_CAPABILITY_NOT_VCN_MANAGED);
 
     /**
      * Adds the given capability to this {@code NetworkCapability} instance.
@@ -751,7 +774,7 @@
         if (originalOwnerUid == creatorUid) {
             setOwnerUid(creatorUid);
         }
-        if (ArrayUtils.contains(originalAdministratorUids, creatorUid)) {
+        if (CollectionUtils.contains(originalAdministratorUids, creatorUid)) {
             setAdministratorUids(new int[] {creatorUid});
         }
         // There is no need to clear the UIDs, they have already been cleared by clearAll() above.
@@ -1763,6 +1786,15 @@
         return 0;
     }
 
+    private <T extends Parcelable> void writeParcelableArraySet(Parcel in,
+            @Nullable ArraySet<T> val, int flags) {
+        final int size = (val != null) ? val.size() : -1;
+        in.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            in.writeParcelable(val.valueAt(i), flags);
+        }
+    }
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeLong(mNetworkCapabilities);
@@ -1773,7 +1805,7 @@
         dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
         dest.writeParcelable((Parcelable) mTransportInfo, flags);
         dest.writeInt(mSignalStrength);
-        dest.writeArraySet(mUids);
+        writeParcelableArraySet(dest, mUids, flags);
         dest.writeString(mSSID);
         dest.writeBoolean(mPrivateDnsBroken);
         dest.writeIntArray(getAdministratorUids());
@@ -1796,8 +1828,7 @@
                 netCap.mNetworkSpecifier = in.readParcelable(null);
                 netCap.mTransportInfo = in.readParcelable(null);
                 netCap.mSignalStrength = in.readInt();
-                netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
-                        null /* ClassLoader, null for default */);
+                netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */);
                 netCap.mSSID = in.readString();
                 netCap.mPrivateDnsBroken = in.readBoolean();
                 netCap.setAdministratorUids(in.createIntArray());
@@ -1810,6 +1841,20 @@
             public NetworkCapabilities[] newArray(int size) {
                 return new NetworkCapabilities[size];
             }
+
+            private @Nullable <T extends Parcelable> ArraySet<T> readParcelableArraySet(Parcel in,
+                    @Nullable ClassLoader loader) {
+                final int size = in.readInt();
+                if (size < 0) {
+                    return null;
+                }
+                final ArraySet<T> result = new ArraySet<>(size);
+                for (int i = 0; i < size; i++) {
+                    final T value = in.readParcelable(loader);
+                    result.append(value);
+                }
+                return result;
+            }
         };
 
     @Override
@@ -1857,7 +1902,7 @@
             sb.append(" OwnerUid: ").append(mOwnerUid);
         }
 
-        if (!ArrayUtils.isEmpty(mAdministratorUids)) {
+        if (mAdministratorUids != null && mAdministratorUids.length != 0) {
             sb.append(" AdminUids: ").append(Arrays.toString(mAdministratorUids));
         }
 
@@ -1982,6 +2027,7 @@
             case NET_CAPABILITY_TEMPORARILY_NOT_METERED:    return "TEMPORARILY_NOT_METERED";
             case NET_CAPABILITY_OEM_PRIVATE:          return "OEM_PRIVATE";
             case NET_CAPABILITY_VEHICLE_INTERNAL:     return "NET_CAPABILITY_VEHICLE_INTERNAL";
+            case NET_CAPABILITY_NOT_VCN_MANAGED:      return "NOT_VCN_MANAGED";
             default:                                  return Integer.toString(capability);
         }
     }
@@ -2489,7 +2535,7 @@
         @NonNull
         public NetworkCapabilities build() {
             if (mCaps.getOwnerUid() != Process.INVALID_UID) {
-                if (!ArrayUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) {
+                if (!CollectionUtils.contains(mCaps.getAdministratorUids(), mCaps.getOwnerUid())) {
                     throw new IllegalStateException("The owner UID must be included in "
                             + " administrator UIDs.");
                 }
diff --git a/core/java/android/net/NetworkConfig.java b/packages/Connectivity/framework/src/android/net/NetworkConfig.java
similarity index 100%
rename from core/java/android/net/NetworkConfig.java
rename to packages/Connectivity/framework/src/android/net/NetworkConfig.java
diff --git a/core/java/android/net/NetworkInfo.aidl b/packages/Connectivity/framework/src/android/net/NetworkInfo.aidl
similarity index 100%
rename from core/java/android/net/NetworkInfo.aidl
rename to packages/Connectivity/framework/src/android/net/NetworkInfo.aidl
diff --git a/core/java/android/net/NetworkInfo.java b/packages/Connectivity/framework/src/android/net/NetworkInfo.java
similarity index 100%
rename from core/java/android/net/NetworkInfo.java
rename to packages/Connectivity/framework/src/android/net/NetworkInfo.java
diff --git a/core/java/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
similarity index 100%
rename from core/java/android/net/NetworkProvider.java
rename to packages/Connectivity/framework/src/android/net/NetworkProvider.java
diff --git a/core/java/android/net/NetworkRequest.aidl b/packages/Connectivity/framework/src/android/net/NetworkRequest.aidl
similarity index 100%
rename from core/java/android/net/NetworkRequest.aidl
rename to packages/Connectivity/framework/src/android/net/NetworkRequest.aidl
diff --git a/core/java/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
similarity index 96%
rename from core/java/android/net/NetworkRequest.java
rename to packages/Connectivity/framework/src/android/net/NetworkRequest.java
index 66b99b9..b9ef4c2 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -353,7 +353,9 @@
          *                         NetworkSpecifier.
          */
         public Builder setNetworkSpecifier(NetworkSpecifier networkSpecifier) {
-            MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(networkSpecifier);
+            if (networkSpecifier instanceof MatchAllNetworkSpecifier) {
+                throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted");
+            }
             mNetworkCapabilities.setNetworkSpecifier(networkSpecifier);
             return this;
         }
@@ -433,25 +435,7 @@
      * @hide
      */
     public boolean isRequest() {
-        return isForegroundRequest() || isBackgroundRequest();
-    }
-
-    /**
-     * Returns true iff. the contained NetworkRequest is one that:
-     *
-     *     - should be associated with at most one satisfying network
-     *       at a time;
-     *
-     *     - should cause a network to be kept up and in the foreground if
-     *       it is the best network which can satisfy the NetworkRequest.
-     *
-     * For full detail of how isRequest() is used for pairing Networks with
-     * NetworkRequests read rematchNetworkAndRequests().
-     *
-     * @hide
-     */
-    public boolean isForegroundRequest() {
-        return type == Type.TRACK_DEFAULT || type == Type.REQUEST;
+        return type == Type.REQUEST || type == Type.BACKGROUND_REQUEST;
     }
 
     /**
diff --git a/core/java/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
similarity index 97%
rename from core/java/android/net/NetworkUtils.java
rename to packages/Connectivity/framework/src/android/net/NetworkUtils.java
index b5962c5..8be4af7 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -81,11 +81,11 @@
     public native static boolean bindProcessToNetworkForHostResolution(int netId);
 
     /**
-     * Explicitly binds {@code socketfd} to the network designated by {@code netId}.  This
+     * Explicitly binds {@code fd} to the network designated by {@code netId}.  This
      * overrides any binding via {@link #bindProcessToNetwork}.
      * @return 0 on success or negative errno on failure.
      */
-    public native static int bindSocketToNetwork(int socketfd, int netId);
+    public static native int bindSocketToNetwork(FileDescriptor fd, int netId);
 
     /**
      * Protect {@code fd} from VPN connections.  After protecting, data sent through
@@ -93,9 +93,7 @@
      * forwarded through the VPN.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static boolean protectFromVpn(FileDescriptor fd) {
-        return protectFromVpn(fd.getInt$());
-    }
+    public static native boolean protectFromVpn(FileDescriptor fd);
 
     /**
      * Protect {@code socketfd} from VPN connections.  After protecting, data sent through
diff --git a/core/java/android/net/PacProxySelector.java b/packages/Connectivity/framework/src/android/net/PacProxySelector.java
similarity index 100%
rename from core/java/android/net/PacProxySelector.java
rename to packages/Connectivity/framework/src/android/net/PacProxySelector.java
diff --git a/core/java/android/net/Proxy.java b/packages/Connectivity/framework/src/android/net/Proxy.java
similarity index 77%
rename from core/java/android/net/Proxy.java
rename to packages/Connectivity/framework/src/android/net/Proxy.java
index 03b07e0..77c8a4f 100644
--- a/core/java/android/net/Proxy.java
+++ b/packages/Connectivity/framework/src/android/net/Proxy.java
@@ -16,8 +16,10 @@
 
 package android.net;
 
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -30,8 +32,6 @@
 import java.net.ProxySelector;
 import java.net.URI;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * A convenience class for accessing the user and default proxy
@@ -64,40 +64,9 @@
     @Deprecated
     public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO";
 
-    /** @hide */
-    public static final int PROXY_VALID             = 0;
-    /** @hide */
-    public static final int PROXY_HOSTNAME_EMPTY    = 1;
-    /** @hide */
-    public static final int PROXY_HOSTNAME_INVALID  = 2;
-    /** @hide */
-    public static final int PROXY_PORT_EMPTY        = 3;
-    /** @hide */
-    public static final int PROXY_PORT_INVALID      = 4;
-    /** @hide */
-    public static final int PROXY_EXCLLIST_INVALID  = 5;
-
     private static ConnectivityManager sConnectivityManager = null;
 
-    // Hostname / IP REGEX validation
-    // Matches blank input, ips, and domain names
-    private static final String NAME_IP_REGEX =
-        "[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*";
-
-    private static final String HOSTNAME_REGEXP = "^$|^" + NAME_IP_REGEX + "$";
-
-    private static final Pattern HOSTNAME_PATTERN;
-
-    private static final String EXCL_REGEX =
-        "[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*(\\.[a-zA-Z0-9*]+(\\-[a-zA-Z0-9*]+)*)*";
-
-    private static final String EXCLLIST_REGEXP = "^$|^" + EXCL_REGEX + "(," + EXCL_REGEX + ")*$";
-
-    private static final Pattern EXCLLIST_PATTERN;
-
     static {
-        HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
-        EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP);
         sDefaultProxySelector = ProxySelector.getDefault();
     }
 
@@ -216,36 +185,21 @@
         return false;
     }
 
-    /**
-     * Validate syntax of hostname, port and exclusion list entries
-     * {@hide}
-     */
-    public static int validate(String hostname, String port, String exclList) {
-        Matcher match = HOSTNAME_PATTERN.matcher(hostname);
-        Matcher listMatch = EXCLLIST_PATTERN.matcher(exclList);
-
-        if (!match.matches()) return PROXY_HOSTNAME_INVALID;
-
-        if (!listMatch.matches()) return PROXY_EXCLLIST_INVALID;
-
-        if (hostname.length() > 0 && port.length() == 0) return PROXY_PORT_EMPTY;
-
-        if (port.length() > 0) {
-            if (hostname.length() == 0) return PROXY_HOSTNAME_EMPTY;
-            int portVal = -1;
-            try {
-                portVal = Integer.parseInt(port);
-            } catch (NumberFormatException ex) {
-                return PROXY_PORT_INVALID;
-            }
-            if (portVal <= 0 || portVal > 0xFFFF) return PROXY_PORT_INVALID;
-        }
-        return PROXY_VALID;
-    }
-
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final void setHttpProxySystemProperty(ProxyInfo p) {
+    @Deprecated
+    public static void setHttpProxySystemProperty(ProxyInfo p) {
+        setHttpProxyConfiguration(p);
+    }
+
+    /**
+     * Set HTTP proxy configuration for the process to match the provided ProxyInfo.
+     *
+     * If the provided ProxyInfo is null, the proxy configuration will be cleared.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static void setHttpProxyConfiguration(@Nullable ProxyInfo p) {
         String host = null;
         String port = null;
         String exclList = null;
@@ -256,11 +210,11 @@
             exclList = ProxyUtils.exclusionListAsString(p.getExclusionList());
             pacFileUrl = p.getPacFileUrl();
         }
-        setHttpProxySystemProperty(host, port, exclList, pacFileUrl);
+        setHttpProxyConfiguration(host, port, exclList, pacFileUrl);
     }
 
     /** @hide */
-    public static final void setHttpProxySystemProperty(String host, String port, String exclList,
+    public static void setHttpProxyConfiguration(String host, String port, String exclList,
             Uri pacFileUrl) {
         if (exclList != null) exclList = exclList.replace(",", "|");
         if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList);
diff --git a/core/java/android/net/ProxyInfo.aidl b/packages/Connectivity/framework/src/android/net/ProxyInfo.aidl
similarity index 100%
rename from core/java/android/net/ProxyInfo.aidl
rename to packages/Connectivity/framework/src/android/net/ProxyInfo.aidl
diff --git a/core/java/android/net/ProxyInfo.java b/packages/Connectivity/framework/src/android/net/ProxyInfo.java
similarity index 97%
rename from core/java/android/net/ProxyInfo.java
rename to packages/Connectivity/framework/src/android/net/ProxyInfo.java
index 950d393..229db0d 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/packages/Connectivity/framework/src/android/net/ProxyInfo.java
@@ -23,6 +23,8 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.net.module.util.ProxyUtils;
+
 import java.net.InetSocketAddress;
 import java.net.URLConnection;
 import java.util.List;
@@ -233,7 +235,7 @@
      */
     public boolean isValid() {
         if (!Uri.EMPTY.equals(mPacFileUrl)) return true;
-        return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost,
+        return ProxyUtils.PROXY_VALID == ProxyUtils.validate(mHost == null ? "" : mHost,
                 mPort == 0 ? "" : Integer.toString(mPort),
                 mExclusionList == null ? "" : mExclusionList);
     }
@@ -355,7 +357,7 @@
                     port = in.readInt();
                 }
                 String exclList = in.readString();
-                String[] parsedExclList = in.readStringArray();
+                String[] parsedExclList = in.createStringArray();
                 ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList);
                 return proxyProperties;
             }
diff --git a/core/java/android/net/RouteInfo.aidl b/packages/Connectivity/framework/src/android/net/RouteInfo.aidl
similarity index 100%
rename from core/java/android/net/RouteInfo.aidl
rename to packages/Connectivity/framework/src/android/net/RouteInfo.aidl
diff --git a/core/java/android/net/RouteInfo.java b/packages/Connectivity/framework/src/android/net/RouteInfo.java
similarity index 100%
rename from core/java/android/net/RouteInfo.java
rename to packages/Connectivity/framework/src/android/net/RouteInfo.java
diff --git a/core/java/android/net/SocketKeepalive.java b/packages/Connectivity/framework/src/android/net/SocketKeepalive.java
similarity index 100%
rename from core/java/android/net/SocketKeepalive.java
rename to packages/Connectivity/framework/src/android/net/SocketKeepalive.java
diff --git a/core/java/android/net/StaticIpConfiguration.aidl b/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl
similarity index 100%
rename from core/java/android/net/StaticIpConfiguration.aidl
rename to packages/Connectivity/framework/src/android/net/StaticIpConfiguration.aidl
diff --git a/core/java/android/net/StaticIpConfiguration.java b/packages/Connectivity/framework/src/android/net/StaticIpConfiguration.java
similarity index 100%
rename from core/java/android/net/StaticIpConfiguration.java
rename to packages/Connectivity/framework/src/android/net/StaticIpConfiguration.java
diff --git a/core/java/android/net/TcpKeepalivePacketData.java b/packages/Connectivity/framework/src/android/net/TcpKeepalivePacketData.java
similarity index 100%
rename from core/java/android/net/TcpKeepalivePacketData.java
rename to packages/Connectivity/framework/src/android/net/TcpKeepalivePacketData.java
diff --git a/core/java/android/net/TcpRepairWindow.java b/packages/Connectivity/framework/src/android/net/TcpRepairWindow.java
similarity index 100%
rename from core/java/android/net/TcpRepairWindow.java
rename to packages/Connectivity/framework/src/android/net/TcpRepairWindow.java
diff --git a/core/java/android/net/TcpSocketKeepalive.java b/packages/Connectivity/framework/src/android/net/TcpSocketKeepalive.java
similarity index 100%
rename from core/java/android/net/TcpSocketKeepalive.java
rename to packages/Connectivity/framework/src/android/net/TcpSocketKeepalive.java
diff --git a/core/java/android/net/TestNetworkInterface.aidl b/packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl
similarity index 100%
rename from core/java/android/net/TestNetworkInterface.aidl
rename to packages/Connectivity/framework/src/android/net/TestNetworkInterface.aidl
diff --git a/core/java/android/net/TestNetworkInterface.java b/packages/Connectivity/framework/src/android/net/TestNetworkInterface.java
similarity index 100%
rename from core/java/android/net/TestNetworkInterface.java
rename to packages/Connectivity/framework/src/android/net/TestNetworkInterface.java
diff --git a/core/java/android/net/TestNetworkManager.java b/packages/Connectivity/framework/src/android/net/TestNetworkManager.java
similarity index 100%
rename from core/java/android/net/TestNetworkManager.java
rename to packages/Connectivity/framework/src/android/net/TestNetworkManager.java
diff --git a/core/java/android/net/TransportInfo.java b/packages/Connectivity/framework/src/android/net/TransportInfo.java
similarity index 100%
rename from core/java/android/net/TransportInfo.java
rename to packages/Connectivity/framework/src/android/net/TransportInfo.java
diff --git a/core/java/android/net/UidRange.aidl b/packages/Connectivity/framework/src/android/net/UidRange.aidl
similarity index 100%
rename from core/java/android/net/UidRange.aidl
rename to packages/Connectivity/framework/src/android/net/UidRange.aidl
diff --git a/core/java/android/net/VpnManager.java b/packages/Connectivity/framework/src/android/net/VpnManager.java
similarity index 63%
rename from core/java/android/net/VpnManager.java
rename to packages/Connectivity/framework/src/android/net/VpnManager.java
index c87b827..1812509 100644
--- a/core/java/android/net/VpnManager.java
+++ b/packages/Connectivity/framework/src/android/net/VpnManager.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
@@ -28,6 +29,8 @@
 import android.content.res.Resources;
 import android.os.RemoteException;
 
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 
 import java.io.IOException;
@@ -161,4 +164,104 @@
             throw e.rethrowFromSystemServer();
         }
     }
-}
+
+    /**
+     * Return the VPN configuration for the given user ID.
+     * @hide
+     */
+    @Nullable
+    public VpnConfig getVpnConfig(@UserIdInt int userId) {
+        try {
+            return mService.getVpnConfig(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Prepare for a VPN application.
+     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
+     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+     *
+     * @param oldPackage Package name of the application which currently controls VPN, which will
+     *                   be replaced. If there is no such application, this should should either be
+     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
+     * @param newPackage Package name of the application which should gain control of VPN, or
+     *                   {@code null} to disable.
+     * @param userId User for whom to prepare the new VPN.
+     *
+     * @hide
+     */
+    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
+            int userId) {
+        try {
+            return mService.prepareVpn(oldPackage, newPackage, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set whether the VPN package has the ability to launch VPNs without user intervention. This
+     * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
+     * class. If the caller is not {@code userId}, {@link
+     * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+     *
+     * @param packageName The package for which authorization state should change.
+     * @param userId User for whom {@code packageName} is installed.
+     * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
+     *     permissions should be granted. When unauthorizing an app, {@link
+     *     VpnManager.TYPE_VPN_NONE} should be used.
+     * @hide
+     */
+    public void setVpnPackageAuthorization(
+            String packageName, int userId, @VpnManager.VpnType int vpnType) {
+        try {
+            mService.setVpnPackageAuthorization(packageName, userId, vpnType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the legacy VPN information for the specified user ID.
+     * @hide
+     */
+    public LegacyVpnInfo getLegacyVpnInfo(@UserIdInt int userId) {
+        try {
+            return mService.getLegacyVpnInfo(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Starts a legacy VPN.
+     * @hide
+     */
+    public void startLegacyVpn(VpnProfile profile) {
+        try {
+            mService.startLegacyVpn(profile);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Informs the service that legacy lockdown VPN state should be updated (e.g., if its keystore
+     * entry has been updated). If the LockdownVpn mechanism is enabled, updates the vpn
+     * with a reload of its profile.
+     *
+     * <p>This method can only be called by the system UID
+     * @return a boolean indicating success
+     *
+     * @hide
+     */
+    public boolean updateLockdownVpn() {
+        try {
+            return mService.updateLockdownVpn();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/net/VpnService.java b/packages/Connectivity/framework/src/android/net/VpnService.java
similarity index 100%
rename from core/java/android/net/VpnService.java
rename to packages/Connectivity/framework/src/android/net/VpnService.java
diff --git a/core/java/android/net/apf/ApfCapabilities.aidl b/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl
similarity index 100%
rename from core/java/android/net/apf/ApfCapabilities.aidl
rename to packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.aidl
diff --git a/core/java/android/net/apf/ApfCapabilities.java b/packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.java
similarity index 100%
rename from core/java/android/net/apf/ApfCapabilities.java
rename to packages/Connectivity/framework/src/android/net/apf/ApfCapabilities.java
diff --git a/core/java/android/net/util/DnsUtils.java b/packages/Connectivity/framework/src/android/net/util/DnsUtils.java
similarity index 100%
rename from core/java/android/net/util/DnsUtils.java
rename to packages/Connectivity/framework/src/android/net/util/DnsUtils.java
diff --git a/core/java/android/net/util/KeepaliveUtils.java b/packages/Connectivity/framework/src/android/net/util/KeepaliveUtils.java
similarity index 100%
rename from core/java/android/net/util/KeepaliveUtils.java
rename to packages/Connectivity/framework/src/android/net/util/KeepaliveUtils.java
diff --git a/core/java/android/net/util/MultinetworkPolicyTracker.java b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java
similarity index 100%
rename from core/java/android/net/util/MultinetworkPolicyTracker.java
rename to packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java
diff --git a/core/java/android/net/util/SocketUtils.java b/packages/Connectivity/framework/src/android/net/util/SocketUtils.java
similarity index 100%
rename from core/java/android/net/util/SocketUtils.java
rename to packages/Connectivity/framework/src/android/net/util/SocketUtils.java
diff --git a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl
new file mode 100644
index 0000000..64b5567
--- /dev/null
+++ b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+package com.android.connectivity.aidl;
+
+import android.net.NattKeepalivePacketData;
+import android.net.QosFilterParcelable;
+import android.net.TcpKeepalivePacketData;
+
+import com.android.connectivity.aidl.INetworkAgentRegistry;
+
+/**
+ * Interface to notify NetworkAgent of connectivity events.
+ * @hide
+ */
+oneway interface INetworkAgent {
+    void onRegistered(in INetworkAgentRegistry registry);
+    void onDisconnected();
+    void onBandwidthUpdateRequested();
+    void onValidationStatusChanged(int validationStatus,
+            in @nullable String captivePortalUrl);
+    void onSaveAcceptUnvalidated(boolean acceptUnvalidated);
+    void onStartNattSocketKeepalive(int slot, int intervalDurationMs,
+        in NattKeepalivePacketData packetData);
+    void onStartTcpSocketKeepalive(int slot, int intervalDurationMs,
+        in TcpKeepalivePacketData packetData);
+    void onStopSocketKeepalive(int slot);
+    void onSignalStrengthThresholdsUpdated(in int[] thresholds);
+    void onPreventAutomaticReconnect();
+    void onAddNattKeepalivePacketFilter(int slot,
+        in NattKeepalivePacketData packetData);
+    void onAddTcpKeepalivePacketFilter(int slot,
+        in TcpKeepalivePacketData packetData);
+    void onRemoveKeepalivePacketFilter(int slot);
+    void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel);
+    void onQosCallbackUnregistered(int qosCallbackId);
+}
diff --git a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
new file mode 100644
index 0000000..f0193db
--- /dev/null
+++ b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2020, 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 perNmissions and
+ * limitations under the License.
+ */
+package com.android.connectivity.aidl;
+
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.QosSession;
+import android.telephony.data.EpsBearerQosSessionAttributes;
+
+/**
+ * Interface for NetworkAgents to send network network properties.
+ * @hide
+ */
+oneway interface INetworkAgentRegistry {
+    void sendNetworkCapabilities(in NetworkCapabilities nc);
+    void sendLinkProperties(in LinkProperties lp);
+    // TODO: consider replacing this by "markConnected()" and removing
+    void sendNetworkInfo(in NetworkInfo info);
+    void sendScore(int score);
+    void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial);
+    void sendSocketKeepaliveEvent(int slot, int reason);
+    void sendUnderlyingNetworks(in @nullable List<Network> networks);
+    void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes);
+    void sendQosSessionLost(int qosCallbackId, in QosSession session);
+    void sendQosCallbackError(int qosCallbackId, int exceptionType);
+}
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index c8f3bd3..8fc3181 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -57,6 +57,7 @@
     static_libs: [
         "net-utils-device-common",
         "net-utils-framework-common",
+        "netd-client",
     ],
     apex_available: [
         "//apex_available:platform",
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/BootCompletedReceiver.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/BootCompletedReceiver.java
index 35bc490..fcee98d 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/BootCompletedReceiver.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/BootCompletedReceiver.java
@@ -21,7 +21,6 @@
 import android.content.Intent;
 import android.os.UserHandle;
 import android.os.image.DynamicSystemClient;
-import android.os.image.DynamicSystemManager;
 
 
 /**
@@ -41,15 +40,6 @@
             return;
         }
 
-        DynamicSystemManager dynSystem =
-                (DynamicSystemManager) context.getSystemService(Context.DYNAMIC_SYSTEM_SERVICE);
-
-        boolean isInUse = (dynSystem != null) && dynSystem.isInUse();
-
-        if (!isInUse) {
-            return;
-        }
-
         Intent startServiceIntent = new Intent(
                 context, DynamicSystemInstallationService.class);
 
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index d472311..7cc5994 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -26,7 +26,6 @@
 import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationRequest;
-import android.location.LocationResult;
 import android.location.provider.ILocationProvider;
 import android.location.provider.ILocationProviderManager;
 import android.location.provider.ProviderProperties;
@@ -49,6 +48,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.List;
 import java.util.Random;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -167,10 +167,13 @@
         }
 
         @Override
-        public void onReportLocation(LocationResult locationResult) {
-            for (int i = 0; i < locationResult.size(); i++) {
-                mLocations.add(locationResult.get(i));
-            }
+        public void onReportLocation(Location location) {
+            mLocations.add(location);
+        }
+
+        @Override
+        public void onReportLocations(List<Location> locations) {
+            mLocations.addAll(locations);
         }
 
         @Override
diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml
index 65e75cd..d411831 100644
--- a/packages/PackageInstaller/res/values-eu/strings.xml
+++ b/packages/PackageInstaller/res/values-eu/strings.xml
@@ -83,9 +83,9 @@
     <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak tableta honetan."</string>
     <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telebista honetan."</string>
     <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"Segurtasuna bermatzeko, ezin dira instalatu iturburu honetako aplikazio ezezagunak telefono honetan."</string>
-    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonoak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik telefonoak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
-    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tabletak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik tabletak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
-    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Telebistak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartu egingo duzu zeu zarela hura erabiltzeagatik telebistak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+    <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"Telefonoak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartuko duzu zeu zarela hura erabiltzeagatik telefonoak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+    <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"Tabletak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartuko duzu zeu zarela hura erabiltzeagatik tabletak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
+    <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Telebistak eta datu pertsonalek aplikazio ezezagunen erasoak jaso ditzakete. Aplikazio hau instalatzen baduzu, onartuko duzu zeu zarela hura erabiltzeagatik telebistak jasan ditzakeen kalteen edo datu-galeren erantzulea."</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"Egin aurrera"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"Ezarpenak"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"Wear aplikazioak instalatzea/desinstalatzea"</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index c6c7142..935cb37 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -25,7 +25,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -143,16 +146,21 @@
 
                 File file = new File(mPackageURI.getPath());
                 try {
-                    PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0);
-                    params.setAppPackageName(pkg.packageName);
-                    params.setInstallLocation(pkg.installLocation);
-                    params.setSize(
-                            PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride));
-                } catch (PackageParser.PackageParserException e) {
-                    Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
-                    Log.e(LOG_TAG,
-                            "Cannot calculate installed size " + file + ". Try only apk size.");
-                    params.setSize(file.length());
+                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+                    final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                            input.reset(), file, /* flags */ 0);
+                    if (result.isError()) {
+                        Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
+                        Log.e(LOG_TAG,
+                                "Cannot calculate installed size " + file + ". Try only apk size.");
+                        params.setSize(file.length());
+                    } else {
+                        final PackageLite pkg = result.getResult();
+                        params.setAppPackageName(pkg.getPackageName());
+                        params.setInstallLocation(pkg.getInstallLocation());
+                        params.setSize(
+                                PackageHelper.calculateInstalledSize(pkg, params.abiOverride));
+                    }
                 } catch (IOException e) {
                     Log.e(LOG_TAG,
                             "Cannot calculate installed size " + file + ". Try only apk size.");
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
index 8a73fb5..2be00b9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_off.xml
@@ -15,11 +15,17 @@
   limitations under the License.
   -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item
+      android:top="4dp"
+      android:left="4dp"
+      android:right="4dp"
+      android:bottom="4dp">
 
-  <size android:height="24dp" android:width="24dp" />
-  <solid android:color="@color/thumb_off" />
-  <stroke android:width="4dp" android:color="@android:color/transparent" />
+    <shape android:shape="oval" >
+      <size android:height="20dp" android:width="20dp" />
+      <solid android:color="@color/thumb_off" />
+    </shape>
 
-</shape>
+  </item>
+</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
index 8a0af00..e85eb42 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/thumb_on.xml
@@ -15,11 +15,17 @@
   limitations under the License.
   -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval" >
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item
+      android:top="4dp"
+      android:left="4dp"
+      android:right="4dp"
+      android:bottom="4dp">
 
-  <size android:height="24dp" android:width="24dp" />
-  <solid android:color="?android:attr/colorAccent" />
-  <stroke android:width="4dp" android:color="@android:color/transparent" />
+    <shape android:shape="oval" >
+      <size android:height="20dp" android:width="20dp" />
+      <solid android:color="?android:attr/colorAccent" />
+    </shape>
 
-</shape>
+  </item>
+</layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
index 1be3a8e..b29f459 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off.xml
@@ -20,7 +20,7 @@
   <item
       android:width="13.33dp"
       android:height="1.67dp"
-      android:left="19dp"
+      android:left="21dp"
       android:gravity="center"
       android:drawable="@drawable/track_off_indicator" />
 </layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
index 3cc490f..c838654 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_off_background.xml
@@ -17,17 +17,17 @@
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="24dp"
-    android:viewportWidth="48"
-    android:viewportHeight="24">
+    android:width="52dp"
+    android:height="28dp"
+    android:viewportWidth="52"
+    android:viewportHeight="28">
 
   <group>
     <clip-path
-        android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
+        android:pathData="M14 0H38C45.732 0 52 6.26801 52 14C52 21.732 45.732 28 38 28H14C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0Z" />
 
     <path
-        android:pathData="M0 0V24H48V0"
+        android:pathData="M0 0V28H52V0"
         android:fillColor="@color/track_off" />
   </group>
 
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
index 2553891..cf24112 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on.xml
@@ -21,6 +21,6 @@
       android:width="13.19dp"
       android:height="10.06dp"
       android:gravity="center"
-      android:right="19dp"
+      android:right="21dp"
       android:drawable="@drawable/track_on_indicator" />
 </layer-list>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
index 68ce19b..bb1a7ef 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable/track_on_background.xml
@@ -17,19 +17,20 @@
 
 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="24dp"
-    android:viewportWidth="48"
-    android:viewportHeight="24"
+    android:width="52dp"
+    android:height="28dp"
+    android:viewportWidth="52"
+    android:viewportHeight="28"
     android:tint="@*android:color/switch_track_material">
 
   <group>
     <clip-path
-        android:pathData="M12 0H36C42.6274 0 48 5.37258 48 12C48 18.6274 42.6274 24 36 24H12C5.37258 24 0 18.6274 0 12C0 5.37258 5.37258 0 12 0Z" />
+        android:pathData="M14 0H38C45.732 0 52 6.26801 52 14C52 21.732 45.732 28 38 28H14C6.26801 28 0 21.732 0 14C0 6.26801 6.26801 0 14 0Z" />
 
     <path
-        android:pathData="M0 0V24H48V0"
+        android:pathData="M0 0V28H52V0"
         android:fillColor="@*android:color/white_disabled_material" />
+
   </group>
 
 </vector>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
index 7e3ce9d..52779bc 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/main_switch_bar.xml
@@ -26,7 +26,7 @@
         android:minHeight="@dimen/min_switch_bar_height"
         android:layout_height="wrap_content"
         android:layout_width="match_parent"
-        android:background="?android:attr/colorBackground"
+        android:background="?android:attr/selectableItemBackground"
         android:paddingLeft="@dimen/switchbar_margin_start"
         android:paddingRight="@dimen/switchbar_margin_end">
 
@@ -35,7 +35,7 @@
             android:layout_height="wrap_content"
             android:layout_width="0dp"
             android:layout_weight="1"
-            android:paddingRight="54dp"
+            android:layout_marginRight="16dp"
             android:layout_gravity="center_vertical"
             android:maxLines="2"
             android:ellipsize="end"
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
index 9dc0af3..147db77 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-night/colors.xml
@@ -17,7 +17,6 @@
 
 <resources>
 
-    <color name="title_text_color">@*android:color/primary_text_dark</color>
     <color name="thumb_off">#BFFFFFFF</color>
     <color name="track_off">@*android:color/material_grey_600</color>
 
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
index 8194bdd..147db77 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/colors.xml
@@ -17,7 +17,6 @@
 
 <resources>
 
-    <color name="title_text_color">@*android:color/primary_text_light</color>
     <color name="thumb_off">#BFFFFFFF</color>
     <color name="track_off">@*android:color/material_grey_600</color>
 
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index dd443de..b145c9b 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -18,13 +18,13 @@
 <resources>
 
     <!-- Size of layout margin left -->
-    <dimen name="switchbar_margin_start">26dp</dimen>
+    <dimen name="switchbar_margin_start">22dp</dimen>
 
     <!-- Size of layout margin right -->
-    <dimen name="switchbar_margin_end">24dp</dimen>
+    <dimen name="switchbar_margin_end">16dp</dimen>
 
     <!-- Minimum width of switch -->
-    <dimen name="min_switch_width">48dp</dimen>
+    <dimen name="min_switch_width">52dp</dimen>
 
     <!-- Minimum width of switch bar -->
     <dimen name="min_switch_bar_height">72dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
index fbb896c..59b5899 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
@@ -19,7 +19,6 @@
 
     <style name="MainSwitchText">
         <item name="android:textSize">20sp</item>
-        <item name="android:textColor">@color/title_text_color</item>
     </style>
 
     <style name="Settings.MainSwitch" parent="@android:style/Widget.Material.CompoundButton.Switch">
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 532c996..74b6578 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -136,10 +136,8 @@
      * Show the MainSwitchBar
      */
     public void show() {
-        if (!isShowing()) {
-            setVisibility(View.VISIBLE);
-            mSwitch.setOnCheckedChangeListener(this);
-        }
+        setVisibility(View.VISIBLE);
+        mSwitch.setOnCheckedChangeListener(this);
     }
 
     /**
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index dae0e70..274bf8d 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -17,9 +17,11 @@
 package com.android.settingslib.widget;
 
 import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.widget.Switch;
 
+import androidx.core.content.res.TypedArrayUtils;
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.TwoStatePreference;
 
@@ -38,30 +40,29 @@
     private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
 
     private MainSwitchBar mMainSwitchBar;
-    private Switch mSwitch;
     private String mTitle;
 
     private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin;
 
     public MainSwitchPreference(Context context) {
         super(context);
-        init();
+        init(context, null);
     }
 
     public MainSwitchPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
+        init(context, attrs);
     }
 
     public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init();
+        init(context, attrs);
     }
 
     public MainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init();
+        init(context, attrs);
     }
 
     @Override
@@ -76,8 +77,21 @@
         registerListenerToSwitchBar();
     }
 
-    private void init() {
+    private void init(Context context, AttributeSet attrs) {
         setLayoutResource(R.layout.main_switch_layout);
+
+        if (attrs != null) {
+            TypedArray a = context.obtainStyledAttributes(attrs,
+                    androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/,
+                    0 /*defStyleRes*/);
+            final CharSequence title = TypedArrayUtils.getText(a,
+                    androidx.preference.R.styleable.Preference_title,
+                    androidx.preference.R.styleable.Preference_android_title);
+            if (!TextUtils.isEmpty(title)) {
+                setTitle(title.toString());
+            }
+            a.recycle();
+        }
     }
 
     /**
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml b/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml
index 4b1b255..4a1b089 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml
@@ -19,14 +19,13 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingLeft="24dp"
-    android:paddingStart="24dp"
     android:paddingRight="?android:attr/listPreferredItemPaddingRight"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:background="?android:attr/selectableItemBackground"
     android:baselineAligned="false"
     android:layout_marginTop="16dp"
-    android:gravity="center_vertical">
+    android:gravity="center_vertical"
+    style="@style/PreferenceCategoryStartMargin">
 
     <RelativeLayout
         android:layout_width="0dp"
@@ -57,6 +56,5 @@
             android:textColor="?android:attr/textColorSecondary"
             android:maxLines="10"
             style="@style/PreferenceSummaryTextStyle"/>
-
     </RelativeLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml
new file mode 100644
index 0000000..4f40256
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<resources>
+    <style name="PreferenceCategoryStartMargin">
+        <item name="android:paddingLeft">24dp</item>
+        <item name="android:paddingStart">24dp</item>
+    </style>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
index 6b285d5..a6623b0 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
@@ -22,4 +22,9 @@
         <!-- 0.8 Spacing, 0.8/11 = 0.072727273 -->
         <item name="android:letterSpacing">0.072727273</item>
     </style>
+
+    <style name="PreferenceCategoryStartMargin">
+        <item name="android:paddingLeft">?android:attr/listPreferredItemPaddingLeft</item>
+        <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+    </style>
 </resources>
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index f346a59..3331550 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -6,6 +6,7 @@
 
     static_libs: [
         "androidx.preference_preference",
+        "androidx-constraintlayout_constraintlayout",
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
index 9dbd5fa..f45105d 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
+++ b/packages/SettingsLib/UsageProgressBarPreference/res/layout/preference_usage_progress_bar.xml
@@ -17,6 +17,7 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:gravity="center_vertical"
@@ -26,29 +27,40 @@
     android:paddingTop="32dp"
     android:paddingBottom="32dp">
 
-    <RelativeLayout
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
         <TextView
             android:id="@+id/usage_summary"
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_alignParentStart="true"
-            android:layout_alignBaseline="@id/total_summary"
-            android:singleLine="true"
+            app:layout_constraintWidth_percent="0.45"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintBaseline_toBaselineOf="@id/total_summary"
             android:ellipsize="marquee"
             android:fontFamily="@*android:string/config_headlineFontFamily"
             android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Display1"
-            android:textSize="20sp"/>
+            android:textSize="14sp"
+            android:textAlignment="viewStart"/>
         <TextView
             android:id="@+id/total_summary"
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_alignParentEnd="true"
-            android:singleLine="true"
+            app:layout_constraintWidth_percent="0.45"
+            app:layout_constraintEnd_toStartOf="@id/custom_content"
             android:ellipsize="marquee"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"/>
-    </RelativeLayout>
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
+            android:textSize="14sp"
+            android:textAlignment="viewEnd"/>
+        <FrameLayout
+            android:id="@+id/custom_content"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintBottom_toBottomOf="@id/total_summary"
+            app:layout_constraintWidth_percent="0.1"/>
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <ProgressBar
         android:id="@android:id/progress"
diff --git a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
index 950a8b4..af64a1d 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
+++ b/packages/SettingsLib/UsageProgressBarPreference/src/com/android/settingslib/widget/UsageProgressBarPreference.java
@@ -22,6 +22,9 @@
 import android.text.TextUtils;
 import android.text.style.RelativeSizeSpan;
 import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
@@ -41,6 +44,7 @@
 
     private CharSequence mUsageSummary;
     private CharSequence mTotalSummary;
+    private ImageView mCustomImageView;
     private int mPercent = -1;
 
     /**
@@ -110,6 +114,15 @@
         notifyChanged();
     }
 
+    /** Set custom ImageView to the right side of total summary. */
+    public <T extends ImageView> void setCustomContent(T imageView) {
+        if (imageView == mCustomImageView) {
+            return;
+        }
+        mCustomImageView = imageView;
+        notifyChanged();
+    }
+
     /**
      * Binds the created View to the data for this preference.
      *
@@ -141,6 +154,15 @@
             progressBar.setIndeterminate(false);
             progressBar.setProgress(mPercent);
         }
+
+        final FrameLayout customLayout = (FrameLayout) holder.findViewById(R.id.custom_content);
+        if (mCustomImageView == null) {
+            customLayout.removeAllViews();
+            customLayout.setVisibility(View.GONE);
+        } else {
+            customLayout.addView(mCustomImageView);
+            customLayout.setVisibility(View.VISIBLE);
+        }
     }
 
     private CharSequence enlargeFontOfNumber(CharSequence summary) {
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 6f4f72a..889980a 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Sal nie outomaties koppel nie"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Geen internettoegang nie"</string>
     <string name="saved_network" msgid="7143698034077223645">"Gestoor deur <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Gekoppel aan beperkte netwerk"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Outomaties deur %1$s gekoppel"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Outomaties deur netwerkgraderingverskaffer gekoppel"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Gekoppel via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 3f5df34..c41e4d5 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"በራስ-ሰር አይገናኝም"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ምንም የበይነመረብ መዳረሻ ያለም"</string>
     <string name="saved_network" msgid="7143698034077223645">"የተቀመጠው በ<xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"ከሚለካ አውታረ መረብ ጋር ተገናኝቷል"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"በ%1$s በኩል በራስ-ሰር ተገናኝቷል"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"በአውታረ መረብ ደረጃ ሰጪ አቅራቢ በኩል በራስ-ሰር ተገናኝቷል"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"በ%1$s በኩል መገናኘት"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 0351d94..f8d1d57 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"لن يتم الاتصال تلقائيًا"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"لا يتوفّر اتصال بالإنترنت"</string>
     <string name="saved_network" msgid="7143698034077223645">"تم الحفظ بواسطة <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"تم الاتصال بشبكة تفرض تكلفة استخدام."</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"‏تم الاتصال تلقائيًا عبر %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"تم الاتصال تلقائيًا عبر مقدم خدمة تقييم الشبكة"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"‏تم الاتصال عبر %1$s"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 0f7db8f..c6078f8 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"স্বয়ংক্ৰিয়ভাৱে সংযোগ নহ’ব"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ইণ্টাৰনেট সংযোগ নাই"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g>এ ছেভ কৰিছে"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"নিৰিখ-নিৰ্দিষ্ট নেটৱৰ্কৰ সৈতে সংযোগ কৰা হৈছে"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s মাধ্যমেদি স্বয়ংক্ৰিয়ভাৱে সংযোগ কৰা হৈছে"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"নেটৱৰ্ক ৰেটিং প্ৰদানকাৰীৰ জৰিয়তে স্বয়ং সংয়োগ কৰা হ’ল"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s-ৰ মাধ্যমেদি সংযোগ কৰা হৈছে"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 0855d17e..d3e0a25 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Avtomatik qoşulmayacaq"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"İnternet girişi yoxdur"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> tərəfindən saxlandı"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Ölçülən şəbəkəyə qoşulub"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s üzərindən avtomatik qoşuldu"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Avtomatik olaraq şəbəkə reytinq provayderi ilə qoşuludur"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s vasitəsilə qoşuludur"</string>
@@ -311,7 +312,7 @@
     <string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Adsız Bluetooth cihazları (yalnız MAC ünvanları) göstəriləcək"</string>
     <string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Uzaqdan idarə olunan cihazlarda dözülməz yüksək səs həcmi və ya nəzarət çatışmazlığı kimi səs problemləri olduqda Bluetooth mütləq səs həcmi xüsusiyyətini deaktiv edir."</string>
     <string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Bluetooth Gabeldorsche funksiyasını aktiv edir."</string>
-    <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Təkmilləşdirilmiş Bağlantı funksiyasını aktiv edir."</string>
+    <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Qabaqcıl məlumat mübadiləsini aktiv edir."</string>
     <string name="enable_terminal_title" msgid="3834790541986303654">"Yerli terminal"</string>
     <string name="enable_terminal_summary" msgid="2481074834856064500">"Yerli örtük girişini təklif edən terminal tətbiqi aktiv edin"</string>
     <string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP yoxlanılır"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 3386fe86..8541222 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Automatsko povezivanje nije uspelo"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nema pristupa internetu"</string>
     <string name="saved_network" msgid="7143698034077223645">"Sačuvao/la je <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Povezani ste na mrežu sa ograničenjem"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatski povezano preko %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatski povezano preko dobavljača ocene mreže"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Veza je uspostavljena preko pristupne tačke %1$s"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 6ac2172..aab600f 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Не будзе аўтаматычна падключацца"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Няма доступу да інтэрнэту"</string>
     <string name="saved_network" msgid="7143698034077223645">"Захавана праз: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Падключана да сеткі з падлікам трафіка"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Аўтаматычна падключана праз %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Аўтаматычна падключана праз пастаўшчыка паслугі ацэнкі сеткі"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Падключана праз %1$s"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 19ed5bd..92f2935 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Няма да се свърже автоматично"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Няма достъп до интернет"</string>
     <string name="saved_network" msgid="7143698034077223645">"Запазено от <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Установена е връзка с мрежа с отчитане"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Автоматично е установена връзка чрез %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Автоматично е установена връзка чрез доставчик на услуги за оценяване на мрежите"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Установена е връзка през „%1$s“"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index b0e9342..b40e24e 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"অটোমেটিক কানেক্ট করবে না"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ইন্টারনেট অ্যাক্সেস নেই"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> দ্বারা সেভ করা"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"মিটার্ড নেটওয়ার্কের সঙ্গে কানেক্ট করা"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"স্বয়ংক্রিয়ভাবে %1$s এর মাধ্যমে কানেক্ট হয়েছে"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"নেটওয়ার্কের রেটিং প্রদানকারীর মাধ্যমে অটোমেটিক কানেক্ট"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s মাধ্যমে কানেক্ট হয়েছে"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 9377624..cec2e45 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Neće se automatski povezati"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nema pristupa internetu"</string>
     <string name="saved_network" msgid="7143698034077223645">"Sačuvano: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Povezani ste s mrežom s naplatom"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatski povezano koristeći %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatski povezano putem ocjenjivača mreže"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Povezani preko %1$s"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index a2a7770..6134da8 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"No es connectarà automàticament"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"No hi ha accés a Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Desada per <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Connectat a una xarxa d\'ús mesurat"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Connectada automàticament a través de: %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Connectada automàticament a través d\'un proveïdor de valoració de xarxes"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Connectada mitjançant %1$s"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 4db4d5c..f29a3dd 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Připojení nebude automaticky navázáno"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nebyl zjištěn žádný přístup k internetu"</string>
     <string name="saved_network" msgid="7143698034077223645">"Uloženo uživatelem <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Připojeno k měřené síti"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automaticky připojeno přes poskytovatele %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automaticky připojeno přes poskytovatele hodnocení sítí"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Připojeno prostřednictvím %1$s"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index b76afa5c..d92d41d 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Der oprettes ikke automatisk forbindelse"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Ingen internetadgang"</string>
     <string name="saved_network" msgid="7143698034077223645">"Gemt af <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Der er oprettet forbindelse til det forbrugsbaserede netværk"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatisk tilsluttet via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatisk forbundet via udbyder af netværksvurdering"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Tilsluttet via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index ba760dd..ac05b620 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Kein automatischer Verbindungsaufbau"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Kein Internetzugriff"</string>
     <string name="saved_network" msgid="7143698034077223645">"Gespeichert von <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Mit kostenpflichtigem Netzwerk verbunden"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatisch über %1$s verbunden"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatisch über Anbieter von Netzwerkbewertungen verbunden"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Über %1$s verbunden"</string>
@@ -285,7 +286,7 @@
     <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Verringert den Akkuverbrauch und verbessert die Netzwerkleistung"</string>
     <string name="wifi_enhanced_mac_randomization_summary" msgid="1210663439867489931">"Wenn dieser Modus aktiviert ist, kann sich die MAC-Adresse dieses Geräts bei jeder Verbindung mit einem Netzwerk ändern, bei dem die MAC-Adressen randomisiert werden."</string>
     <string name="wifi_metered_label" msgid="8737187690304098638">"Kostenpflichtig"</string>
-    <string name="wifi_unmetered_label" msgid="6174142840934095093">"Kostenlos"</string>
+    <string name="wifi_unmetered_label" msgid="6174142840934095093">"Ohne Datenlimit"</string>
     <string name="select_logd_size_title" msgid="1604578195914595173">"Logger-Puffergrößen"</string>
     <string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Größe pro Protokollpuffer wählen"</string>
     <string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Speicher der dauerhaften Protokollierung löschen?"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index c608a62..0b11d69 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Δεν θα συνδεθεί αυτόματα"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Δεν υπάρχει πρόσβαση στο διαδίκτυο"</string>
     <string name="saved_network" msgid="7143698034077223645">"Αποθηκεύτηκε από <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Σύνδεση σε δίκτυο με ογκοχρέωση"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Συνδέθηκε αυτόματα μέσω %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Συνδέθηκε αυτόματα μέσω παρόχου αξιολόγησης δικτύου"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Συνδέθηκε μέσω %1$s"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index e67c3d1..b98c4b8 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string>
     <string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Connected to metered network"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatically connected via network rating provider"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Connected via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index b0830fc..aa0d3f1 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string>
     <string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Connected to metered network"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatically connected via network rating provider"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Connected via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index e67c3d1..b98c4b8 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string>
     <string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Connected to metered network"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatically connected via network rating provider"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Connected via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index e67c3d1..b98c4b8 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string>
     <string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Connected to metered network"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatically connected via network rating provider"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Connected via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 4c0af75..c01f3a0 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎Won\'t automatically connect‎‏‎‎‏‎"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‎‏‏‎‏‏‎No internet access‎‏‎‎‏‎"</string>
     <string name="saved_network" msgid="7143698034077223645">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‏‏‏‎‏‎Saved by ‎‏‎‎‏‏‎<xliff:g id="NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎Connected to metered network‎‏‎‎‏‎"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‎Automatically connected via %1$s‎‏‎‎‏‎"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‎‏‎Automatically connected via network rating provider‎‏‎‎‏‎"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎Connected via %1$s‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 9ae7781..1da8e37 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"No se conectará automáticamente"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"No hay acceso a Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Guardada por <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Conectado a la red de uso medido"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Conexión automática mediante %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Conectado automáticamente mediante proveedor de calificación de red"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Conexión a través de %1$s"</string>
@@ -116,8 +117,8 @@
     <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SINCRONIZAR"</string>
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Cancelar"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"La sincronización te permite acceder a los contactos y al historial de llamadas cuando el dispositivo está conectado."</string>
-    <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"No se pudo sincronizar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
-    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"No se pudo sincronizar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g> debido a que el PIN o la clave de acceso son incorrectos."</string>
+    <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"No se pudo vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
+    <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"No se pudo vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g> debido a que el PIN o la clave de acceso son incorrectos."</string>
     <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"No se puede establecer la comunicación con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
     <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Vínculo rechazado por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Computadora"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 203726a..54226ce 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"No se establecerá conexión automáticamente"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"No se ha detectado ningún acceso a Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Guardada por <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Conectado a una red de uso medido"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Conectada automáticamente a través de %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Conectado automáticamente a través de un proveedor de valoración de redes"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Conectado a través de %1$s"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 379ed6c..4c747e3 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Automaatselt ei ühendata"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Juurdepääs Internetile puudub"</string>
     <string name="saved_network" msgid="7143698034077223645">"Salvestas: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Ühendatud mahupõhise võrguga"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Ühendus loodi automaatselt teenusega %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Ühendus loodi automaatselt võrgukvaliteedi hinnangute pakkuja kaudu"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Ühendatud üksuse %1$s kaudu"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 2582854..6016cd2 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Ez da konektatuko automatikoki"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Ezin da konektatu Internetera"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> aplikazioak gorde du"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Sare neurtu batera konektatuta"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s bidez automatikoki konektatuta"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatikoki konektatuta sare-balorazioen hornitzailearen bidez"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s bidez konektatuta"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index cb598e5..e855570 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -36,8 +36,9 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"اتصال به‌صورت خودکار انجام نمی‌شود"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"دسترسی به اینترنت ندارد"</string>
     <string name="saved_network" msgid="7143698034077223645">"ذخیره‌شده توسط <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"اتصال به شبکه محدود برقرار شد"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"‏اتصال خودکار ازطریق %1$s"</string>
-    <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"اتصال خودکار ازطریق ارائه‌دهنده رتبه‌بندی شبکه"</string>
+    <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"اتصال خودکار ازطریق ارائه‌دهنده رده‌بندی شبکه"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"‏متصل از طریق %1$s"</string>
     <string name="connected_via_app" msgid="3532267661404276584">"متصل شده ازطریق <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="available_via_passpoint" msgid="1716000261192603682">"‏در دسترس از طریق %1$s"</string>
@@ -263,8 +264,8 @@
     <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"‏انتخاب نسخه MAP بلوتوث"</string>
     <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"کدک بلوتوث صوتی"</string>
     <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"راه‌اندازی کدک صوتی بلوتوثی\nانتخاب"</string>
-    <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"سرعت نمونه بلوتوث صوتی"</string>
-    <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"راه‌اندازی کدک صوتی بلوتوثی\nانتخاب: سرعت نمونه"</string>
+    <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"بسامد نمونه صوتی بلوتوث"</string>
+    <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"راه‌اندازی کدک صوتی بلوتوثی\nانتخاب: بسامد نمونه"</string>
     <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"«خاکستری» به این معناست که تلفن یا هدست از آن پشتیبانی نمی‌کند"</string>
     <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"بیت‌های بلوتوث صوتی در هر نمونه"</string>
     <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"راه‌اندازی کدک صوتی بلوتوثی\nانتخاب: تعداد بیت در نمونه"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 31dfe18..724e52a 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Yhteyttä ei muodosteta automaattisesti"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Ei internetyhteyttä"</string>
     <string name="saved_network" msgid="7143698034077223645">"Tallentaja: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Yhdistetty maksulliseen verkkoon"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automaattinen yhteys muodostettu palvelun %1$s kautta"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Yhdistetty automaattisesti verkon arviointipalvelun kautta"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Yhdistetty seuraavan kautta: %1$s"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index a8476dd..74c3005 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Reconnexion automatique impossible"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Aucun accès à Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Enregistrés par <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Appareil connecté à un réseau mesuré"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatiquement connecté par %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Connecté automatiquement par le fournisseur d\'avis sur le réseau"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Connecté par %1$s"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index a79ed0c..c9651ce 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Reconnexion automatique impossible"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Aucun accès à Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Enregistré lors de : <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Connecté au réseau facturé à l\'usage"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Connecté automatiquement via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Connecté automatiquement via un fournisseur d\'évaluation de l\'état du réseau"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Connecté via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 97662a6..f499f58 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Non se conectará automaticamente"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Sen acceso a Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Gardada por <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Estableceuse conexión coa rede de pago por consumo"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Conectouse automaticamente a través de %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Conectada automaticamente a través dun provedor de valoración de redes"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Conectado a través de %1$s"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 46bd71b..23ee1de 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ઑટોમૅટિક રીતે કનેક્ટ કરશે નહીં"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"કોઈ ઇન્ટરનેટ ઍક્સેસ નથી"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> દ્વારા સચવાયું"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"મીટર્ડ (ડેટા નિયંત્રણ) નેટવર્ક સાથે કનેક્ટેડ છે"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s દ્વારા સ્વત: કનેક્ટ થયેલ"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"નેટવર્ક રેટિંગ પ્રદાતા દ્વારા ઑટોમૅટિક રીતે કનેક્ટ થયું"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s દ્વારા કનેક્ટ થયેલ"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 9cae311..a2fc43c 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"अपने आप कनेक्ट नहीं होगा"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"इंटरनेट नहीं है"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> के द्वारा सहेजा गया"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"सीमित डेटा वाले नेटवर्क से कनेक्ट किया गया"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s के ज़रिए ऑटोमैटिक रूप से कनेक्ट है"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"नेटवर्क रेटिंग कंपनी के ज़रिए अपने आप कनेक्ट है"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s के द्वारा उपलब्ध"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index 83bb2d1..396051d 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Neće se povezati automatski"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nema pristupa internetu"</string>
     <string name="saved_network" msgid="7143698034077223645">"Spremila aplik. <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Povezano s mrežom s ograničenim prometom"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatski povezan putem %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatski povezan putem ocjenjivača mreže"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Povezano putem %1$s"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 9f184c7..0c9bc54 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Nem csatlakozik automatikusan"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nincs internet-hozzáférés"</string>
     <string name="saved_network" msgid="7143698034077223645">"Mentette: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Forgalomkorlátos hálózathoz csatlakozva"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatikusan csatlakozott a következőn keresztül: %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatikusan csatlakozott a hálózatértékelés szolgáltatóján keresztül"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Csatlakozva a következőn keresztül: %1$s"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index cd6cbf3..56c93b6 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Չի միանա ավտոմատ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Ինտերնետ կապ չկա"</string>
     <string name="saved_network" msgid="7143698034077223645">"Ով է պահել՝ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Միացած է վճարովի թրաֆիկով ցանցի"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Ավտոմատ կերպով կապակցվել է %1$s-ի միջոցով"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Ավտոմատ միացել է ցանցերի վարկանիշի մատակարարի միջոցով"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Միացված է %1$s-ի միջոցով"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 3b80918..0440f47 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Tidak akan tersambung otomatis"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Tidak ada akses internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Disimpan oleh <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Terhubung ke jaringan berbayar"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Tersambung otomatis melalui %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Otomatis tersambung melalui penyedia rating jaringan"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Terhubung melalui %1$s"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index ce9e665..0bb294f 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Mun ekki tengjast sjálfkrafa"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Enginn netaðgangur"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> vistaði"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Tengdist neti með mældri notkun"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Sjálfkrafa tengt um %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Sjálfkrafa tengt um netgæðaveitu"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Tengt í gegnum %1$s"</string>
@@ -226,12 +227,12 @@
     <string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi-Fi pörunarkóði"</string>
     <string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Pörun mistókst"</string>
     <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Gakktu úr skugga um að tækið sé tengt sama neti."</string>
-    <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Tengja tæki með Wi-Fi með því að skanna QR-kóða"</string>
+    <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Para tæki gegnum Wi-Fi með því að skanna QR-kóða"</string>
     <string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Parar tæki…"</string>
     <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Ekki tókst að para við tækið. Annað hvort var QR-kóðinn rangur eða tækið ekki tengt sama neti."</string>
     <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP-tala og gátt"</string>
     <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Skanna QR-kóða"</string>
-    <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Tengja tæki með Wi-Fi með því að skanna QR-kóða"</string>
+    <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Para tæki gegnum Wi-Fi með því að skanna QR-kóða"</string>
     <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Tengstu Wi-Fi neti"</string>
     <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, villuleit, dev"</string>
     <string name="bugreport_in_power" msgid="8664089072534638709">"Flýtileið í villutilkynningu"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 93b24a0..54049d7 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Non verrà eseguita la connessione automatica"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nessun accesso a Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Salvata da <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Connessione a rete a consumo effettuata"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Collegato automaticamente tramite %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Collegato automaticamente tramite fornitore di servizi di valutazione rete"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Collegato tramite %1$s"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index ab67809..6cc4347 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"לא יתבצע חיבור באופן אוטומטי"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"אין גישה לאינטרנט"</string>
     <string name="saved_network" msgid="7143698034077223645">"נשמר על ידי <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"יש חיבור לרשת המבוססת על חיוב לפי שימוש בנתונים"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"‏מחובר אוטומטית דרך %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"מחובר אוטומטית דרך ספק של דירוג רשת"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"‏מחובר דרך %1$s"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 711a22f..ec674ad 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"自動的に接続されません"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"インターネット接続なし"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g>で保存"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"従量制ネットワークに接続しました"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s 経由で自動的に接続しています"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ネットワーク評価プロバイダ経由で自動的に接続しています"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s経由で接続"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 0813305..30ab3b4 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ავტომატურად დაკავშირება ვერ მოხერხდება"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ინტერნეტ-კავშირი არ არის"</string>
     <string name="saved_network" msgid="7143698034077223645">"შენახული <xliff:g id="NAME">%1$s</xliff:g>-ის მიერ"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"დაკავშირებულია ფასიან ქსელთან"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"ავტომატურად დაკავშირდა %1$s-ის მეშვეობით"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ავტომატურად დაკავშირდა ქსელის ხარისხის შეფასების პროვაიდერის მეშვეობით"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s-ით დაკავშირებული"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 16ee9c0..ef78527 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Автоматты қосылмайды"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Интернетпен байланыс жоқ"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> сақтаған"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Трафик саналатын желіге қосылды."</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s арқылы автоматты қосылды"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Желі рейтингі провайдері арқылы автоматты түрде қосылған"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s арқылы қосылған"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 5b47381..ed59c74 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"នឹងមិនភ្ជាប់ដោយស្វ័យប្រវត្តិទេ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"មិនមាន​ការតភ្ជាប់​អ៊ីនធឺណិតទេ"</string>
     <string name="saved_network" msgid="7143698034077223645">"បានរក្សាទុកដោយ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"បានភ្ជាប់ទៅ​បណ្ដាញដែលផ្អែកតាមទិន្នន័យដែលប្រើ"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"បានភ្ជាប់ដោយស្វ័យប្រវត្តិតាមរយៈ %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"បានភ្ជាប់​ដោយស្វ័យប្រវត្តិ​តាម​រយៈក្រុមហ៊ុនផ្តល់​ការ​វាយ​តម្លៃលើ​បណ្តាញ"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"បានភ្ជាប់តាមរយៈ %1$s"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index fd63dcb..995dc86 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶವಿಲ್ಲ"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> ನಿಂದ ಉಳಿಸಲಾಗಿದೆ"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"ಮಾಪನ ಮಾಡಲಾದ ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s ಮೂಲಕ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ನೆಟ್‌ವರ್ಕ್ ರೇಟಿಂಗ್ ಒದಗಿಸುವವರ ಮೂಲಕ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s ಮೂಲಕ ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index a9c3f31..7863d48 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"자동으로 연결되지 않습니다."</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"인터넷에 연결되어 있지 않음"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g>(으)로 저장됨"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"데이터 전송량 제한이 있는 네트워크에 연결됨"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s을(를) 통해 자동으로 연결됨"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"네트워크 평가 제공업체를 통해 자동으로 연결됨"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s을(를) 통해 연결됨"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index bc3656f..45d05c6 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Автоматтык түрдө туташпайт"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Интернетке туташпай турат"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> тарабынан сакталды"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Чектелген трафикке туташтырылды"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s аркылуу автоматтык түрдө туташты"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Тармактар рейтингинин булагы аркылуу автоматтык түрдө туташты"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s аркылуу жеткиликтүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 8408c93..ca6e06c 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ຈະບໍ່ເຊື່ອມຕໍ່ອັດຕະໂນມັດ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ"</string>
     <string name="saved_network" msgid="7143698034077223645">"ບັນທຶກ​​​ໂດຍ <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"ເຊື່ອມຕໍ່ຫາເຄືອຂ່າຍທີ່ມີການວັດແທກແລ້ວ"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"ເຊື່ອມຕໍ່ຜ່ານທາງ %1$s ໂດຍອັດຕະໂນມັດ"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ເຊື່ອມຕໍ່ກັບອັດຕະໂນມັດແລ້ວຜ່ານຜູ້ໃຫ້ບໍລິການຄະແນນເຄືອຂ່າຍ"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"​ເຊື່ອມຕໍ່​ຜ່ານ %1$s ​ແລ້ວ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 1dcfcf7..173b57e 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Nebus automatiškai prisijungiama"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nėra interneto ryšio"</string>
     <string name="saved_network" msgid="7143698034077223645">"Išsaugojo <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Prisijungta prie matuojamo tinklo"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatiškai prisijungta naudojant „%1$s“"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatiškai prisijungta naudojant tinklo įvertinimo paslaugos teikėjo paslaugomis"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Prisijungta naudojant „%1$s“"</string>
@@ -206,7 +207,7 @@
     <string name="enable_adb" msgid="8072776357237289039">"USB perkrova"</string>
     <string name="enable_adb_summary" msgid="3711526030096574316">"Derinimo režimas, kai prijungtas USB"</string>
     <string name="clear_adb_keys" msgid="3010148733140369917">"Panaikinti USB derinimo prieigos teises"</string>
-    <string name="enable_adb_wireless" msgid="6973226350963971018">"Belaidžio ryšio derinimas"</string>
+    <string name="enable_adb_wireless" msgid="6973226350963971018">"Belaidžio ryšio derin."</string>
     <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Derinimo režimas, kai prisijungta prie „Wi‑Fi“"</string>
     <string name="adb_wireless_error" msgid="721958772149779856">"Klaida"</string>
     <string name="adb_wireless_settings" msgid="2295017847215680229">"Belaidžio ryšio derinimas"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 3d9b78a..a8bd2cc 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Savienojums netiks izveidots automātiski"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nav piekļuves internetam"</string>
     <string name="saved_network" msgid="7143698034077223645">"Saglabāja: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Izveidots savienojums ar maksas tīklu"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automātiski savienots, izmantojot %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automātiski izveidots savienojums, izmantojot tīkla vērtējuma sniedzēju"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Savienots, izmantojot %1$s"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 0674f11..0ef3f0e 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Не може да се поврзе автоматски"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Нема пристап до интернет"</string>
     <string name="saved_network" msgid="7143698034077223645">"Зачувано од <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Поврзано на мрежа со ограничен интернет"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Автоматски поврзано преку %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Автоматски поврзано преку оценувач на мрежа"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Поврзано преку %1$s"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 1455669..e6289ae 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"സ്വയമേവ കണക്‌റ്റുചെയ്യില്ല"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ഇന്റർനെറ്റ് ആക്‌സസ് ഇല്ല"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> സംരക്ഷിച്ചത്"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"മീറ്റർ ചെയ്ത നെറ്റ്‌വർക്കിലേക്ക് കണക്റ്റ് ചെയ്തു"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s വഴി സ്വയമേവ ബന്ധിപ്പിച്ചു"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"നെറ്റ്‌വർക്ക് റേറ്റിംഗ് ദാതാവുമായി സ്വയം കണക്‌റ്റുചെയ്‌തു"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s വഴി ബന്ധിപ്പിച്ചു"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index f13cb0b..ead712c 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Автоматаар холбогдохгүй"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Интернет хандалт алга"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> хадгалсан"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Хязгаартай сүлжээнд холбогдсон"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s-р автоматаар холбогдсон"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Сүлжээний үнэлгээ үзүүлэгчээр автоматаар холбогдох"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s-р холбогдсон"</string>
@@ -396,8 +397,8 @@
     <item msgid="1282170165150762976">"Дижитал агуулгад зориулан тааруулсан өнгө"</item>
   </string-array>
     <string name="inactive_apps_title" msgid="5372523625297212320">"Зогсолтын горимын апп"</string>
-    <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Идэвхгүй байна. Унтраах/асаахын тулд дарна уу."</string>
-    <string name="inactive_app_active_summary" msgid="8047630990208722344">"Идэвхтэй байна. Унтраах/асаахын тулд дарна уу."</string>
+    <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Идэвхгүй байна. Асаах/унтраахын тулд дарна уу."</string>
+    <string name="inactive_app_active_summary" msgid="8047630990208722344">"Идэвхтэй байна. Асаах/унтраахын тулд дарна уу."</string>
     <string name="standby_bucket_summary" msgid="5128193447550429600">"Апп зогсолтын горимын төлөв:<xliff:g id="BUCKET"> %s</xliff:g>"</string>
     <string name="transcode_settings_title" msgid="2581975870429850549">"Медиа хөрвүүлгийн тохиргоо"</string>
     <string name="transcode_user_control" msgid="6176368544817731314">"Хөрвүүлгийн өгөгдмөлийг дарах"</string>
diff --git a/packages/SettingsLib/res/values-mr/arrays.xml b/packages/SettingsLib/res/values-mr/arrays.xml
index aaf51b3..6cc0130 100644
--- a/packages/SettingsLib/res/values-mr/arrays.xml
+++ b/packages/SettingsLib/res/values-mr/arrays.xml
@@ -155,14 +155,28 @@
     <item msgid="253388653486517049">", अ‍ॅक्टिव्ह (मीडिया)"</item>
     <item msgid="5001852592115448348">", अ‍ॅक्टिव्ह (फोन)"</item>
   </string-array>
-    <!-- no translation found for select_logd_size_titles:5 (6078203297886482480) -->
+  <string-array name="select_logd_size_titles">
+    <item msgid="1191094707770726722">"बंद"</item>
+    <item msgid="7839165897132179888">"64K"</item>
+    <item msgid="2715700596495505626">"256K"</item>
+    <item msgid="7099386891713159947">"1M"</item>
+    <item msgid="6069075827077845520">"4M"</item>
+    <item msgid="6078203297886482480">"८MB"</item>
+  </string-array>
   <string-array name="select_logd_size_lowram_titles">
     <item msgid="1145807928339101085">"बंद"</item>
     <item msgid="4064786181089783077">"64K"</item>
     <item msgid="3052710745383602630">"256K"</item>
     <item msgid="3691785423374588514">"1M"</item>
   </string-array>
-    <!-- no translation found for select_logd_size_summaries:5 (2983219471251787208) -->
+  <string-array name="select_logd_size_summaries">
+    <item msgid="409235464399258501">"बंद"</item>
+    <item msgid="4195153527464162486">"प्रति लॉग बफर 64K"</item>
+    <item msgid="7464037639415220106">"प्रति लॉग बफर 256K"</item>
+    <item msgid="8539423820514360724">"प्रति लॉग बफर 1M"</item>
+    <item msgid="1984761927103140651">"प्रति लॉग बफर 4M"</item>
+    <item msgid="2983219471251787208">"८MB प्रति लॉग बफर"</item>
+  </string-array>
   <string-array name="select_logpersist_titles">
     <item msgid="704720725704372366">"बंद"</item>
     <item msgid="6014837961827347618">"सर्व"</item>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 72ddc8f..edcc71b 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"स्वयंचलितपणे कनेक्ट करणार नाही"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"इंटरनेट अ‍ॅक्सेस नाही"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे सेव्ह केले"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"मर्यादित नेटवर्कशी कनेक्ट केले आहे"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s द्वारे स्वयंचलितपणे कनेक्ट केले"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"नेटवर्क रेटिंग प्रदात्याद्वारे स्वयंचलितपणे कनेक्ट केले"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s द्वारे कनेक्‍ट केले"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 9527793..8a14437 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Tidak akan menyambung secara automatik"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Tiada akses Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Diselamatkan oleh <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Disambungkan kepada rangkaian bermeter"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Disambungkan secara automatik melalui %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Disambungkan secara automatik melalui pembekal penilaian rangkaian"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Disambungkan melalui %1$s"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 1dcb3cf..377c8b2 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"အလိုအလျောက်ချိတ်ဆက်မည်မဟုတ်ပါ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"အင်တာနက် ချိတ်ဆက်မှု မရှိပါ"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> က သိမ်းဆည်းခဲ့သည်"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"အခမဲ့မဟုတ်သော ကွန်ရက်သို့ ချိတ်ဆက်ထားသည်"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s မှတစ်ဆင့် အလိုအလျောက် ချိတ်ဆက်ထားပါသည်"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ကွန်ရက်အဆင့်သတ်မှတ်ပေးသူ မှတစ်ဆင့် အလိုအလျောက် ချိတ်ဆက်ထားပါသည်"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 13f69c1..f0e773b 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Kobler ikke til automatisk"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Ingen internettilgang"</string>
     <string name="saved_network" msgid="7143698034077223645">"Lagret av <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Koble til et nettverk med datamåling"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatisk tilkoblet via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatisk tilkoblet via leverandør av nettverksvurdering"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Tilkoblet via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 5795cc9..0bde70f 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"स्वतः जडान हुने छैन"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"इन्टरनेटमाथिको पहुँच छैन"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> द्वारा सुरक्षित गरियो"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"प्रयोगसम्बन्धी सीमा तोकिएको नेटवर्कमा कनेक्ट गरियो"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s मार्फत् स्वतः जडान गरिएको"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"नेटवर्कको दर्जा प्रदायक मार्फत स्वत: जडान गरिएको"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s मार्फत जडित"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 718ce5d..2bc1e8c 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Er wordt niet automatisch verbinding gemaakt"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Geen internettoegang"</string>
     <string name="saved_network" msgid="7143698034077223645">"Opgeslagen door \'<xliff:g id="NAME">%1$s</xliff:g>\'"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Verbonden met netwerk met datalimiet"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatisch verbonden via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatisch verbonden via provider van netwerkbeoordelingen"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Verbonden via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 41e84f9..787c871 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ସ୍ୱଚାଳିତ ଭାବେ ସଂଯୁକ୍ତ ହେବନାହିଁ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ଇଣ୍ଟରନେଟ୍‌ର କୌଣସି ଆକ୍‌ସେସ୍‌ ନାହିଁ"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> ଦ୍ୱାରା ସେଭ କରାଯାଇଛି"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"ମିଟରଯୁକ୍ତ ନେଟୱାର୍କ ସହ ସଂଯୋଗ କରାଯାଇଛି"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s ମାଧ୍ୟମରେ ଅଟୋମେଟିକାଲୀ ସଂଯୁକ୍ତ"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ନେଟୱର୍କ ମୂଲ୍ୟାୟନ ପ୍ରଦାତାଙ୍କ ମାଧ୍ୟମରେ ଅଟୋମେଟିକାଲ୍ୟ ସଂଯୁକ୍ତ"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s ମାଧ୍ୟମରେ ସଂଯୁକ୍ତ"</string>
diff --git a/packages/SettingsLib/res/values-pa/arrays.xml b/packages/SettingsLib/res/values-pa/arrays.xml
index c64ee76..c6116aa 100644
--- a/packages/SettingsLib/res/values-pa/arrays.xml
+++ b/packages/SettingsLib/res/values-pa/arrays.xml
@@ -155,14 +155,28 @@
     <item msgid="253388653486517049">", ਕਿਰਿਆਸ਼ੀਲ (ਮੀਡੀਆ)"</item>
     <item msgid="5001852592115448348">", ਕਿਰਿਆਸ਼ੀਲ (ਫ਼ੋਨ)"</item>
   </string-array>
-    <!-- no translation found for select_logd_size_titles:5 (6078203297886482480) -->
+  <string-array name="select_logd_size_titles">
+    <item msgid="1191094707770726722">"ਬੰਦ"</item>
+    <item msgid="7839165897132179888">"64K"</item>
+    <item msgid="2715700596495505626">"256K"</item>
+    <item msgid="7099386891713159947">"1M"</item>
+    <item msgid="6069075827077845520">"4M"</item>
+    <item msgid="6078203297886482480">"8M"</item>
+  </string-array>
   <string-array name="select_logd_size_lowram_titles">
     <item msgid="1145807928339101085">"ਬੰਦ"</item>
     <item msgid="4064786181089783077">"64K"</item>
     <item msgid="3052710745383602630">"256K"</item>
     <item msgid="3691785423374588514">"1M"</item>
   </string-array>
-    <!-- no translation found for select_logd_size_summaries:5 (2983219471251787208) -->
+  <string-array name="select_logd_size_summaries">
+    <item msgid="409235464399258501">"ਬੰਦ"</item>
+    <item msgid="4195153527464162486">"64K ਪ੍ਰਤੀ ਲੌਗ ਬਫ਼ਰ"</item>
+    <item msgid="7464037639415220106">"256K ਪ੍ਰਤੀ ਲੌਗ ਬਫ਼ਰ"</item>
+    <item msgid="8539423820514360724">"1M ਪ੍ਰਤੀ ਲੌਗ ਬਫ਼ਰ"</item>
+    <item msgid="1984761927103140651">"4M ਪ੍ਰਤੀ ਲੌਗ ਬਫ਼ਰ"</item>
+    <item msgid="2983219471251787208">"8M ਪ੍ਰਤੀ ਲੌਗ ਬਫ਼ਰ"</item>
+  </string-array>
   <string-array name="select_logpersist_titles">
     <item msgid="704720725704372366">"ਬੰਦ"</item>
     <item msgid="6014837961827347618">"ਸਭ"</item>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index e04e201..9483e06 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ਕੋਈ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> ਵੱਲੋਂ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"ਮੀਟਰਬੱਧ ਨੈੱਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਹੈ"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s ਰਾਹੀਂ ਆਪਣੇ-ਆਪ ਕਨੈਕਟ ਹੋਇਆ"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ਨੈੱਟਵਰਕ ਰੇਟਿੰਗ ਪ੍ਰਦਾਨਕ ਰਾਹੀਂ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਕਨੈਕਟ ਹੋਇਆ"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index a57e541..c7f1595 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Nie można połączyć automatycznie"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Brak dostępu do internetu"</string>
     <string name="saved_network" msgid="7143698034077223645">"Zapisane przez: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Połączono z siecią z pomiarem użycia danych"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatycznie połączono przez: %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatycznie połączono przez dostawcę ocen jakości sieci"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Połączono przez %1$s"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index f24b52b..881bccb 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Não se conectará automaticamente"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Sem acesso à Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Salva por <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Conectado a uma rede limitada"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Conectado automaticamente via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Conectado automaticamente via provedor de avaliação de rede"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Conectado via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 42ad0fe..94cad918 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Não é efetuada uma ligação automaticamente"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Sem acesso à Internet."</string>
     <string name="saved_network" msgid="7143698034077223645">"Guardada por <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Ligação estabelecida a uma rede de acesso limitado."</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Ligado automaticamente através de %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Ligado automaticamente através do fornecedor de classificação de rede"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Ligado através de %1$s"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index f24b52b..881bccb 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Não se conectará automaticamente"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Sem acesso à Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Salva por <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Conectado a uma rede limitada"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Conectado automaticamente via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Conectado automaticamente via provedor de avaliação de rede"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Conectado via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 0743fe9..63d8b8f 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Nu se va conecta automat"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nu există acces la internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Salvată de <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"S-a conectat la o rețea contorizată"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Conectată automat prin %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Conectată automat prin furnizor de evaluări ale rețelei"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Conectată prin %1$s"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index d19438a..ac43e49 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Подключение не будет выполняться автоматически"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Без доступа к Интернету"</string>
     <string name="saved_network" msgid="7143698034077223645">"Кто сохранил: <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Подключено к сети с ограниченным трафиком"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Автоматически подключено к %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Автоматически подключено через автора рейтинга сетей"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Подключено к %1$s"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 15fe8c8..08fd9a0 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"ස්වයංක්‍රිය නැවත සම්බන්ධ නොවනු ඇත"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"අන්තර්ජාල ප්‍රවේශය නැත"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> විසින් සුරකින ලදී"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"මනුගත ජාලයට සම්බන්ධ කර ඇත"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s හරහා ස්වයංක්‍රියව සම්බන්ධ විය"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"ජාල ශ්‍රේණිගත සපයන්නා හරහා ස්වයංක්‍රියව සම්බන්ධ විය"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s හරහා සම්බන්ධ විය"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index ee53b7c..8f05684 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Nedôjde k automatickému pripojeniu"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Žiadny prístup k internetu"</string>
     <string name="saved_network" msgid="7143698034077223645">"Uložila aplikácia <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Pripojené k meranej sieti"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automaticky pripojené prostredníctvom %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automaticky pripojené prostredníctvom poskytovateľa hodnotenia siete"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Pripojené prostredníctvom %1$s"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 66d33a7..3dccf3b 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Samodejna vnovična vzpostavitev povezave se ne bo izvedla"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Ni dostopa do interneta"</string>
     <string name="saved_network" msgid="7143698034077223645">"Shranil(-a): <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Povezano z omrežjem z omejenim prenosom podatkov"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Samodejno vzpostavljena povezava prek: %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Samodejno vzpostavljena povezava prek ponudnika ocenjevanja omrežij"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Vzpostavljena povezava prek: %1$s"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index d5c0231..c09ad4c 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Nuk do të lidhet automatikisht"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Nuk ka qasje në internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"E ruajtur nga <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Lidhur me një rrjet me matje"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Lidhur automatikisht përmes %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Lidhur automatikisht nëpërmjet ofruesit të vlerësimit të rrjetit"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"E lidhur përmes %1$s"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index ce74b84..b5a91bf 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Аутоматско повезивање није успело"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Нема приступа интернету"</string>
     <string name="saved_network" msgid="7143698034077223645">"Сачувао/ла је <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Повезани сте на мрежу са ограничењем"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Аутоматски повезано преко %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Аутоматски повезано преко добављача оцене мреже"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Веза је успостављена преко приступне тачке %1$s"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index eacb7a8..16a1be6 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Det går inte att ansluta automatiskt"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Ingen internetåtkomst"</string>
     <string name="saved_network" msgid="7143698034077223645">"Sparades av <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Ansluten till nätverk med datapriser"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatiskt ansluten via %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatiskt ansluten via leverantör av nätverksbetyg"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Anslutet via %1$s"</string>
diff --git a/packages/SettingsLib/res/values-sw/arrays.xml b/packages/SettingsLib/res/values-sw/arrays.xml
index 62ae06b..b95d69c 100644
--- a/packages/SettingsLib/res/values-sw/arrays.xml
+++ b/packages/SettingsLib/res/values-sw/arrays.xml
@@ -161,7 +161,7 @@
     <item msgid="2715700596495505626">"K256"</item>
     <item msgid="7099386891713159947">"M1"</item>
     <item msgid="6069075827077845520">"M4"</item>
-    <item msgid="6078203297886482480">"M8"</item>
+    <item msgid="6078203297886482480">"MB 8"</item>
   </string-array>
   <string-array name="select_logd_size_lowram_titles">
     <item msgid="1145807928339101085">"Imezimwa"</item>
@@ -175,7 +175,7 @@
     <item msgid="7464037639415220106">"K256 kwa kila akiba ya kumbukumbu"</item>
     <item msgid="8539423820514360724">"M1 kwa kila akiba ya kumbukumbu"</item>
     <item msgid="1984761927103140651">"M4 kwa kila akiba ya kumbukumbu"</item>
-    <item msgid="2983219471251787208">"M8 kwa kila akiba ya kumbukumbu"</item>
+    <item msgid="2983219471251787208">"MB 8 kwa kila akiba ya kumbukumbu"</item>
   </string-array>
   <string-array name="select_logpersist_titles">
     <item msgid="704720725704372366">"Yamezimwa"</item>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index e7045a7..58896c8 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Haiwezi kuunganisha kiotomatiki"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Hakuna muunganisho wa intaneti"</string>
     <string name="saved_network" msgid="7143698034077223645">"Ilihifadhiwa na <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Imeunganishwa kwenye mtandao unaopima data"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Imeunganishwa kiotomatiki kupitia %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Imeunganishwa kiotomatiki kupitia mtoa huduma wa ukadiriaji wa mtandao"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Imeunganishwa kupitia %1$s"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 06c7ccb..71cf7d4 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"தானாக இணைக்கப்படாது"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"இண்டர்நெட் அணுகல் இல்லை"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> சேமித்தது"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"கட்டண நெட்வொர்க்குடன் இணைக்கப்பட்டுள்ளது"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s மூலம் தானாக இணைக்கப்பட்டது"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"நெட்வொர்க் மதிப்பீடு வழங்குநரால் தானாக இணைக்கப்பட்டது"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s வழியாக இணைக்கப்பட்டது"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 5e880a9..c3a65e7 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"స్వయంచాలకంగా కనెక్ట్ కాదు"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> ద్వారా సేవ్ చేయబడింది"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"డేటా నియంత్రణ నెట్‌వర్క్‌కు కనెక్ట్ చేయబడింది"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s ద్వారా స్వయంచాలకంగా కనెక్ట్ చేయబడింది"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"నెట్‌వర్క్ రేటింగ్ ప్రదాత ద్వారా స్వయంచాలకంగా కనెక్ట్ చేయబడింది"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s ద్వారా కనెక్ట్ చేయబడింది"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 6316452..903accb 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"จะไม่เชื่อมต่อโดยอัตโนมัติ"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"เข้าถึงอินเทอร์เน็ตไม่ได้"</string>
     <string name="saved_network" msgid="7143698034077223645">"บันทึกโดย<xliff:g id="NAME">%1$s</xliff:g> แล้ว"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"เชื่อมต่อกับเครือข่ายแบบจำกัดปริมาณแล้ว"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"เชื่อมต่ออัตโนมัติผ่าน %1$s แล้ว"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"เชื่อมต่ออัตโนมัติผ่านผู้ให้บริการการจัดอันดับเครือข่าย"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"เชื่อมต่อผ่าน %1$s แล้ว"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 0aabbe5..b259201 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Hindi awtomatikong kokonekta"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Walang access sa internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Na-save ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Nakakonekta sa nakametrong network"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Awtomatikong nakakonekta sa pamamagitan ng %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Awtomatikong nakakonekta sa pamamagitan ng provider ng rating ng network"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Nakakonekta sa pamamagitan ng %1$s"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 44c8f13..a0bc362 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Otomatik olarak bağlanma"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"İnternet erişimi yok"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> tarafından kaydedildi"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Sayaçlı ağa bağlı"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s üzerinden otomatik olarak bağlı"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Ağ derecelendirme sağlayıcı aracılığıyla otomatik olarak bağlandı"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s üzerinden bağlı"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 7851111..509c8a6 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Не під’єднуватиметься автоматично"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Немає доступу до Інтернету"</string>
     <string name="saved_network" msgid="7143698034077223645">"Збережено додатком <xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Установлено з\'єднання з мережею з тарифікацією трафіку"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Автоматично під’єднано через %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Автоматично під’єднано через постачальника оцінки якості мережі"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Під’єднано через %1$s"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 82783b3..e097ab2 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"خودکار طور پر منسلک نہیں ہو گا"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"انٹرنیٹ تک کوئی رسائی نہیں"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> کی جانب سے محفوظ کردہ"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"میٹرڈ نیٹ ورک سے منسلک ہے"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"‏‎%1$s کے ذریعے از خود منسلک کردہ"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"نیٹ ورک درجہ بندی کے فراہم کنندہ کے ذریعے از خود منسلک"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"‏منسلک بذریعہ ‎%1$s"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index c2a96c6..8c2a95d 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Avtomatik ravishda ulanilmaydi"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Internet aloqasi yo‘q"</string>
     <string name="saved_network" msgid="7143698034077223645">"<xliff:g id="NAME">%1$s</xliff:g> tomonidan saqlangan"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Trafik hisoblanadigan tarmoqqa ulandi"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"%1$s orqali avtomatik ulandi"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Tarmoqlar reytingi muallifi orqali avtomatik ulandi"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"%1$s orqali ulangan"</string>
@@ -154,7 +155,7 @@
     <string name="running_process_item_user_label" msgid="3988506293099805796">"Foydalanuvchi: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
     <string name="launch_defaults_some" msgid="3631650616557252926">"Ba’zi birlamchi sozlamalar o‘rnatilgan"</string>
     <string name="launch_defaults_none" msgid="8049374306261262709">"Birlamchi sozlamalar belgilanmagan"</string>
-    <string name="tts_settings" msgid="8130616705989351312">"Nutq sintezi sozlamalari"</string>
+    <string name="tts_settings" msgid="8130616705989351312">"Matnni nutqqa aylantirish sozlamalari"</string>
     <string name="tts_settings_title" msgid="7602210956640483039">"Nutq sintezi"</string>
     <string name="tts_default_rate_title" msgid="3964187817364304022">"Nutq tezligi"</string>
     <string name="tts_default_rate_summary" msgid="3781937042151716987">"Matnni o‘qish tezligi"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 2596424..7a4d89b 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Sẽ không tự động kết nối"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Không có quyền truy cập Internet"</string>
     <string name="saved_network" msgid="7143698034077223645">"Do <xliff:g id="NAME">%1$s</xliff:g> lưu"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Đã kết nối với mạng có đo lượng dữ liệu"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Tự động được kết nối qua %1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Tự động được kết nối qua nhà cung cấp dịch vụ xếp hạng mạng"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Được kết nối qua %1$s"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 60afd6d..7cd61fc 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"无法自动连接"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"无法访问互联网"</string>
     <string name="saved_network" msgid="7143698034077223645">"由“<xliff:g id="NAME">%1$s</xliff:g>”保存"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"已连接到按流量计费的网络"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"已通过%1$s自动连接"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"已自动连接(通过网络评分服务提供方)"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"已通过%1$s连接"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 925f738..458071c 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"不會自動連線"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"無法連接互聯網"</string>
     <string name="saved_network" msgid="7143698034077223645">"由「<xliff:g id="NAME">%1$s</xliff:g>」儲存"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"已連結至按用量收費的網絡"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"已透過 %1$s 自動連線"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"已透過網絡評分供應商自動連線"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"已透過 %1$s 連線"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index e40d351..867ed8b 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"無法自動連線"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"沒有可用的網際網路連線"</string>
     <string name="saved_network" msgid="7143698034077223645">"由「<xliff:g id="NAME">%1$s</xliff:g>」儲存"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"已連線到計量付費網路"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"已透過 %1$s 自動連線"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"已透過網路評分供應商自動連線"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"已透過 %1$s 連線"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index c304c14..e64dbd3 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -36,6 +36,7 @@
     <string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Ngeke ize ixhumeke ngokuzenzakalela"</string>
     <string name="wifi_no_internet" msgid="1774198889176926299">"Akukho ukufinyelela kwe-inthanethi"</string>
     <string name="saved_network" msgid="7143698034077223645">"Kulondolozwe ngu-<xliff:g id="NAME">%1$s</xliff:g>"</string>
+    <string name="connected_to_metered_access_point" msgid="9179693207918156341">"Kuxhunywe kunethiwekhi eyenziwe imitha"</string>
     <string name="connected_via_network_scorer" msgid="7665725527352893558">"Ixhumeke ngokuzenzakalela nge-%1$s"</string>
     <string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Kuxhunywe ngokuzenzakalelayo ngomhlinzeki wesilinganiso wenethiwekhi"</string>
     <string name="connected_via_passpoint" msgid="7735442932429075684">"Kuxhumeke nge-%1$s"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5563003..7556ace 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1484,5 +1484,5 @@
     <!-- Content description of the Ethernet connection when disconnected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_ethernet_disconnected">Ethernet disconnected.</string>
     <!-- Content description of the Ethernet connection when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
-    <string name="accessibility_ethernet_connected">Ethernet connected.</string>
+    <string name="accessibility_ethernet_connected">Ethernet.</string>
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 6d7e86f..34da305 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -108,7 +108,7 @@
      * - If it's the first time and needFirstTimeWarning, show the first time dialog.
      * - If it's 4th time through 8th time, show the schedule suggestion notification.
      *
-     * @param enable true to disable battery saver.
+     * @param enable true to enable battery saver.
      *
      * @return true if the request succeeded.
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index f83c7a2..6cf53d0b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -479,7 +479,7 @@
         }
     }
 
-    class RouterManagerCallback extends MediaRouter2Manager.Callback {
+    class RouterManagerCallback implements MediaRouter2Manager.Callback {
 
         @Override
         public void onRoutesAdded(List<MediaRoute2Info> routes) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 5580afe..78282fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
@@ -38,7 +39,9 @@
 import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Track status of Wi-Fi for the Sys UI.
@@ -50,6 +53,7 @@
     private final NetworkScoreManager mNetworkScoreManager;
     private final ConnectivityManager mConnectivityManager;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Set<Integer> mNetworks = new HashSet<>();
     private final WifiNetworkScoreCache.CacheListener mCacheListener =
             new WifiNetworkScoreCache.CacheListener(mHandler) {
                 @Override
@@ -64,6 +68,20 @@
             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
     private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onAvailable(
+                Network network, NetworkCapabilities networkCapabilities,
+                LinkProperties linkProperties, boolean blocked) {
+            boolean isVcnOverWifi =
+                    networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                            && (Utils.tryGetWifiInfoForVcn(networkCapabilities) != null);
+            boolean isWifi =
+                    networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+            if (isVcnOverWifi || isWifi) {
+                mNetworks.add(network.getNetId());
+            }
+        }
+
         // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
         // and onLinkPropertiesChanged.
         @Override
@@ -84,9 +102,12 @@
 
         @Override
         public void onLost(Network network) {
-            updateWifiInfo(null);
-            updateStatusLabel();
-            mCallback.run();
+            if (mNetworks.contains(network.getNetId())) {
+                mNetworks.remove(network.getNetId());
+                updateWifiInfo(null);
+                updateStatusLabel();
+                mCallback.run();
+            }
         }
     };
     private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback() {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
index 85e2174..1a8477d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/widget/UsageProgressBarPreferenceTest.java
@@ -18,11 +18,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
+
 import android.content.Context;
 import android.text.SpannedString;
 import android.text.style.RelativeSizeSpan;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
@@ -97,4 +101,30 @@
                 .findViewById(android.R.id.progress);
         assertThat(progressBar.getProgress()).isEqualTo((int) (31.0f / 80 * 100));
     }
+
+    @Test
+    public void setCustomContent_setNullImageView_noChild() {
+        mUsageProgressBarPreference.setCustomContent(null /* imageView */);
+
+        mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        final FrameLayout customContent =
+                (FrameLayout) mViewHolder.findViewById(R.id.custom_content);
+        assertThat(customContent.getChildCount()).isEqualTo(0);
+        assertThat(customContent.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void setCustomContent_setImageView_oneChild() {
+        final ImageView imageView = mock(ImageView.class);
+        mUsageProgressBarPreference.setCustomContent(imageView);
+
+        mUsageProgressBarPreference.onBindViewHolder(mViewHolder);
+
+        final FrameLayout customContent =
+                (FrameLayout) mViewHolder.findViewById(R.id.custom_content);
+        assertThat(customContent.getChildCount()).isEqualTo(1);
+        assertThat(customContent.getChildAt(0)).isEqualTo(imageView);
+        assertThat(customContent.getVisibility()).isEqualTo(View.VISIBLE);
+    }
 }
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 6c51f2f..84dacfd 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -239,7 +239,7 @@
     <bool name="def_aware_lock_enabled">false</bool>
 
     <!-- Default for setting for Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED -->
-    <bool name="def_hdmiControlAutoDeviceOff">false</bool>
+    <bool name="def_hdmiControlAutoDeviceOff">true</bool>
 
     <!-- Default for Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED -->
     <bool name="def_swipe_bottom_to_notification_enabled">true</bool>
@@ -249,4 +249,7 @@
 
     <!-- Default for Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY -->
     <integer name="def_accessibility_magnification_capabilities">3</integer>
+
+    <!-- Default for Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW -->
+    <bool name="def_enable_non_resizable_multi_window">false</bool>
 </resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 60620bd..e92591d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -38,6 +38,7 @@
         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
         Settings.Secure.ADAPTIVE_SLEEP,
+        Settings.Secure.CAMERA_AUTOROTATE,
         Settings.Secure.AUTOFILL_SERVICE,
         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
         Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 66165b6..ad6a531 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -147,5 +147,10 @@
         VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.NOTIFICATION_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.RESTRICTED_NETWORKING_MODE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Global.ONE_HANDED_KEYGUARD_SIDE,
+                new InclusiveIntegerRangeValidator(
+                        /* first= */Global.ONE_HANDED_KEYGUARD_SIDE_LEFT,
+                        /* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 3f46262..ed2b6c9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -57,6 +57,7 @@
         VALIDATORS.put(
                 Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_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,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 266bfe0..6568bff 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -846,8 +846,8 @@
             try {
                 stmt = db.compileStatement("INSERT INTO system(name,value)"
                         + " VALUES(?,?);");
-                loadBooleanSetting(stmt, Settings.System.USER_ROTATION,
-                        R.integer.def_user_rotation); // should be zero degrees
+                loadIntegerSetting(stmt, Settings.System.USER_ROTATION,
+                        R.integer.def_user_rotation);
                 db.setTransactionSuccessful();
             } finally {
                 db.endTransaction();
@@ -2265,6 +2265,8 @@
             loadBooleanSetting(stmt, Settings.System.ACCELEROMETER_ROTATION,
                     R.bool.def_accelerometer_rotation);
 
+            loadIntegerSetting(stmt, Settings.System.USER_ROTATION, R.integer.def_user_rotation);
+
             loadDefaultHapticSettings(stmt);
 
             loadBooleanSetting(stmt, Settings.System.NOTIFICATION_LIGHT_PULSE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 44864a6..a6e2af9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -42,6 +42,7 @@
 import android.provider.settings.validators.SecureSettingsValidators;
 import android.provider.settings.validators.SystemSettingsValidators;
 import android.provider.settings.validators.Validator;
+import android.telephony.SubscriptionManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.BackupUtils;
@@ -95,10 +96,11 @@
     private static final String KEY_NETWORK_POLICIES = "network_policies";
     private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config";
     private static final String KEY_DEVICE_SPECIFIC_CONFIG = "device_specific_config";
+    private static final String KEY_SIM_SPECIFIC_SETTINGS = "sim_specific_settings";
 
     // Versioning of the state file.  Increment this version
     // number any time the set of state items is altered.
-    private static final int STATE_VERSION = 8;
+    private static final int STATE_VERSION = 9;
 
     // Versioning of the Network Policies backup payload.
     private static final int NETWORK_POLICIES_BACKUP_VERSION = 1;
@@ -106,19 +108,20 @@
 
     // Slots in the checksum array.  Never insert new items in the middle
     // of this array; new slots must be appended.
-    private static final int STATE_SYSTEM           = 0;
-    private static final int STATE_SECURE           = 1;
-    private static final int STATE_LOCALE           = 2;
-    private static final int STATE_WIFI_SUPPLICANT  = 3;
-    private static final int STATE_WIFI_CONFIG      = 4;
-    private static final int STATE_GLOBAL           = 5;
-    private static final int STATE_LOCK_SETTINGS    = 6;
-    private static final int STATE_SOFTAP_CONFIG    = 7;
-    private static final int STATE_NETWORK_POLICIES = 8;
-    private static final int STATE_WIFI_NEW_CONFIG  = 9;
-    private static final int STATE_DEVICE_CONFIG    = 10;
+    private static final int STATE_SYSTEM                = 0;
+    private static final int STATE_SECURE                = 1;
+    private static final int STATE_LOCALE                = 2;
+    private static final int STATE_WIFI_SUPPLICANT       = 3;
+    private static final int STATE_WIFI_CONFIG           = 4;
+    private static final int STATE_GLOBAL                = 5;
+    private static final int STATE_LOCK_SETTINGS         = 6;
+    private static final int STATE_SOFTAP_CONFIG         = 7;
+    private static final int STATE_NETWORK_POLICIES      = 8;
+    private static final int STATE_WIFI_NEW_CONFIG       = 9;
+    private static final int STATE_DEVICE_CONFIG         = 10;
+    private static final int STATE_SIM_SPECIFIC_SETTINGS = 11;
 
-    private static final int STATE_SIZE             = 11; // The current number of state items
+    private static final int STATE_SIZE                  = 12; // The current number of state items
 
     // Number of entries in the checksum array at various version numbers
     private static final int STATE_SIZES[] = {
@@ -130,7 +133,8 @@
             8,              // version 5 added STATE_SOFTAP_CONFIG
             9,              // version 6 added STATE_NETWORK_POLICIES
             10,             // version 7 added STATE_WIFI_NEW_CONFIG
-            STATE_SIZE      // version 8 added STATE_DEVICE_CONFIG
+            11,             // version 8 added STATE_DEVICE_CONFIG
+            STATE_SIZE      // version 9 added STATE_SIM_SPECIFIC_SETTINGS
     };
 
     private static final int FULL_BACKUP_ADDED_GLOBAL = 2;  // added the "global" entry
@@ -218,6 +222,7 @@
         byte[] netPoliciesData = getNetworkPolicies();
         byte[] wifiFullConfigData = getNewWifiConfigData();
         byte[] deviceSpecificInformation = getDeviceSpecificConfiguration();
+        byte[] simSpecificSettingsData = getSimSpecificSettingsData();
 
         long[] stateChecksums = readOldChecksums(oldState);
 
@@ -246,6 +251,9 @@
         stateChecksums[STATE_DEVICE_CONFIG] =
                 writeIfChanged(stateChecksums[STATE_DEVICE_CONFIG], KEY_DEVICE_SPECIFIC_CONFIG,
                         deviceSpecificInformation, data);
+        stateChecksums[STATE_SIM_SPECIFIC_SETTINGS] =
+                writeIfChanged(stateChecksums[STATE_SIM_SPECIFIC_SETTINGS],
+                        KEY_SIM_SPECIFIC_SETTINGS, simSpecificSettingsData, data);
 
         writeNewChecksums(stateChecksums, newState);
     }
@@ -386,6 +394,12 @@
                             preservedSettings);
                     break;
 
+                case KEY_SIM_SPECIFIC_SETTINGS:
+                    byte[] restoredSimSpecificSettings = new byte[size];
+                    data.readEntityData(restoredSimSpecificSettings, 0, size);
+                    restoreSimSpecificSettings(restoredSimSpecificSettings);
+                    break;
+
                 default :
                     data.skipEntityData();
 
@@ -1189,6 +1203,20 @@
         return true;
     }
 
+    private byte[] getSimSpecificSettingsData() {
+        SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
+        byte[] simSpecificData = subManager.getAllSimSpecificSettingsForBackup();
+        Log.i(TAG, "sim specific data of length + " + simSpecificData.length
+                + " successfully retrieved");
+
+        return simSpecificData;
+    }
+
+    private void restoreSimSpecificSettings(byte[] data) {
+        SubscriptionManager subManager = SubscriptionManager.from(getBaseContext());
+        subManager.restoreAllSimSpecificSettingsFromBackup(data);
+    }
+
     private void updateWindowManagerIfNeeded(Integer previousDensity) {
         int newDensity;
         try {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 83ded43..1ce738e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1969,6 +1969,12 @@
                 Settings.Secure.BLUETOOTH_ON_WHILE_DRIVING,
                 SecureSettingsProto.BLUETOOTH_ON_WHILE_DRIVING);
 
+        final long smartAutoRotateToken = p.start(SecureSettingsProto.CAMERA_AUTOROTATE);
+        dumpSetting(s, p,
+                Settings.Secure.CAMERA_AUTOROTATE,
+                SecureSettingsProto.CameraAutorotate.ENABLED);
+        p.end(smartAutoRotateToken);
+
         final long cameraToken = p.start(SecureSettingsProto.CAMERA);
         dumpSetting(s, p,
                 Settings.Secure.CAMERA_GESTURE_DISABLED,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index edb5506..c7790fd 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3342,7 +3342,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 197;
+            private static final int SETTINGS_VERSION = 198;
 
             private final int mUserId;
 
@@ -4815,6 +4815,24 @@
                     currentVersion = 197;
                 }
 
+                if (currentVersion == 197) {
+                    // Version 197: Set the default value for Global Settings:
+                    // DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final Setting enableNonResizableMultiWindow = globalSettings.getSettingLocked(
+                            Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW);
+                    if (enableNonResizableMultiWindow.isNull()) {
+                        final boolean defEnableNonResizableMultiWindow = getContext().getResources()
+                                .getBoolean(R.bool.def_enable_non_resizable_multi_window);
+                        globalSettings.insertSettingLocked(
+                                Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
+                                defEnableNonResizableMultiWindow ? "1" : "0", null, true,
+                                SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 198;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index efd941e..c11877a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -272,6 +272,7 @@
                     Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
                     Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
                     Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
+                    Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.ENHANCED_4G_MODE_ENABLED,
                     Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
                     Settings.Global.ERROR_LOGCAT_PREFIX,
@@ -280,7 +281,9 @@
                     Settings.Global.EUICC_UNSUPPORTED_COUNTRIES,
                     Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
                     Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
+                    Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS,
                     Settings.Global.FANCY_IME_ANIMATIONS,
+                    Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
                     Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
                     Settings.Global.FORCED_APP_STANDBY_ENABLED,
                     Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 5d8fc0b..eab0990 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -122,6 +122,8 @@
     <uses-permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP" />
     <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
     <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
+    <uses-permission android:name="android.permission.QUERY_USERS" />
+    <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
     <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
     <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/>
@@ -300,8 +302,8 @@
     <!-- Permission needed to test mainline permission module rollback -->
     <uses-permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS" />
 
-    <!-- Permission needed to read wifi network credentials for CtsNetTestCases -->
-    <uses-permission android:name="android.permission.NETWORK_AIRPLANE_MODE" />
+    <!-- Permission needed to restart WiFi Subsystem -->
+    <uses-permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM" />
 
     <!-- Permission needed to read wifi network credentials for CtsNetTestCases -->
     <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index ca9dcd6..5af0244 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -116,7 +116,6 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MONITOR_INPUT" />
     <uses-permission android:name="android.permission.INPUT_CONSUMER" />
-    <uses-permission android:name="android.permission.USE_BACKGROUND_BLUR" />
 
     <!-- DreamManager -->
     <uses-permission android:name="android.permission.READ_DREAM_STATE" />
diff --git a/packages/SystemUI/docs/sos_gesture.md b/packages/SystemUI/docs/sos_gesture.md
new file mode 100644
index 0000000..1a9144b
--- /dev/null
+++ b/packages/SystemUI/docs/sos_gesture.md
@@ -0,0 +1,26 @@
+# How 5-tapping power launches Emergency Sos
+
+_as of Jan 2021_
+
+Note that the flow is a simplified version of the camera launch flow.
+
+
+### Sequence of events
+
+
+1. [PhoneWindowManager.java](/services/core/java/com/android/server/policy/PhoneWindowManager.java) is responsible for all power button presses (see `interceptPowerKeyDown`).
+2. Even though PWMgr has a lot of logic to detect all manner of power button multipresses and gestures, it also checks with GestureLauncherService, which is also [offered the chance](/services/core/java/com/android/server/policy/PhoneWindowManager.java#943) to [intercept](/services/core/java/com/android/server/GestureLauncherService.java#358) the power key.
+3. GLS is responsible for the emergoncy sos timeout, and if it detects one, it [forwards it to the StatusBarManagerService](/services/core/java/com/android/server/GestureLauncherService.java#475) (which hands it off to SystemUI).
+4. Inside SystemUI, [onEmergencyActionLaunchGestureDetected](/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java#4039) and determines
+    1. If the gesture is enabled (else do nothing)
+    2. If there is an app to handle the gesture (else do nothing)
+    2. whether the screen is on; if not, we need to delay until that happens
+5. Assuming there is an app, and the setting is one launch Emergengy Flow immediately. [Callsite](/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java#4077)
+    1. Note that we cannot have an intent resolver, so we launch the default.
+
+**Which intent launches?**
+
+Due to the nature of the gesture, we need the flow to work behind the lockscreen, and without disambiguation.
+Thus, we always launch the same intent, and verify that there is only one matching intent-filter in the system image.
+
+[The emergengy sos intent action](packages/SystemUI/src/com/android/systemui/emergency/EmergencyGesture.java#36).
diff --git a/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
index 99c70a5..b96c07e 100644
--- a/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
+++ b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
@@ -17,9 +17,8 @@
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
     android:color="?attr/wallpaperTextColorSecondary">
     <item android:id="@android:id/background">
-        <shape
-            android:color="@android:color/transparent">
-            <stroke android:width="1dp" android:color="?android:attr/textColorSecondary"/>
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorAccent"/>
             <corners android:radius="24dp"/>
         </shape>
     </item>
@@ -29,4 +28,4 @@
             <corners android:radius="24dp"/>
         </shape>
     </item>
-</ripple>
\ No newline at end of file
+</ripple>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
index 7986809..b5f55af 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer.xml
@@ -24,7 +24,7 @@
     <include
         style="@style/BouncerSecurityContainer"
         layout="@layout/keyguard_host_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 </FrameLayout>
 
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 80c8a28..35a2195 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -76,7 +76,7 @@
             android:textSize="100dp"
             android:includeFontPadding="false"
             android:fontFamily="@font/clock"
-            android:lineSpacingMultiplier=".65"
+            android:lineSpacingMultiplier=".7"
             android:typeface="monospace"
             android:elegantTextHeight="false"
             dozeWeight="200"
@@ -97,7 +97,7 @@
             android:gravity="center_horizontal"
             android:textSize="@dimen/large_clock_text_size"
             android:includeFontPadding="false"
-            android:lineSpacingMultiplier=".65"
+            android:lineSpacingMultiplier=".7"
             android:fontFamily="@font/clock"
             android:typeface="monospace"
             android:elegantTextHeight="false"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
index 370576b..0ee1b69 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
@@ -43,7 +43,7 @@
     <com.android.keyguard.EmergencyButton
         android:id="@+id/emergency_call_button"
         android:layout_width="wrap_content"
-        android:layout_height="32dp"
+        android:layout_height="48dp"
         android:layout_marginBottom="12dp"
         android:text="@*android:string/lockscreen_emergency_call"
         style="@style/Keyguard.TextView.EmergencyButton" />
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
index 796123db..c75ee51 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_host_view.xml
@@ -32,9 +32,8 @@
 
     <com.android.keyguard.KeyguardSecurityContainer
         android:id="@+id/keyguard_security_container"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        androidprv:layout_maxHeight="@dimen/keyguard_security_max_height"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:padding="0dp"
@@ -42,13 +41,14 @@
         android:layout_gravity="center">
         <com.android.keyguard.KeyguardSecurityViewFlipper
             android:id="@+id/view_flipper"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:clipChildren="false"
             android:clipToPadding="false"
             android:paddingTop="@dimen/keyguard_security_view_top_margin"
             android:paddingStart="@dimen/keyguard_security_view_lateral_margin"
             android:paddingEnd="@dimen/keyguard_security_view_lateral_margin"
+            android:layout_gravity="center"
             android:gravity="center">
         </com.android.keyguard.KeyguardSecurityViewFlipper>
     </com.android.keyguard.KeyguardSecurityContainer>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index dc2d11d..1a38585 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -67,6 +67,7 @@
               android:layout_height="wrap_content"
               android:orientation="vertical"
               android:layout_gravity="bottom|center_horizontal"
+              android:layout_marginTop="@dimen/keyguard_eca_top_margin"
               android:gravity="center_horizontal" />
         </LinearLayout>
     </FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index aa14645a..6ae759c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -188,6 +188,7 @@
              android:layout_height="wrap_content"
              android:orientation="vertical"
              android:layout_gravity="bottom|center_horizontal"
+             android:layout_marginTop="@dimen/keyguard_eca_top_margin"
              android:gravity="center_horizontal"/>
 
 </com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index 64ccefd..f709424 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -198,6 +198,7 @@
              android:layout_height="wrap_content"
              android:orientation="vertical"
              android:layout_gravity="bottom|center_horizontal"
+             android:layout_marginTop="@dimen/keyguard_eca_top_margin"
              android:gravity="center_horizontal"/>
 
 </com.android.keyguard.KeyguardSimPinView>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index dc77bd3..2f9fed6 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -46,7 +46,6 @@
             android:layout_height="wrap_content"
             android:orientation="vertical"
             android:gravity="center"
-            android:layout_weight="1"
             android:layoutDirection="ltr"
             >
         <include layout="@layout/keyguard_esim_area"
@@ -200,5 +199,6 @@
              android:layout_height="wrap_content"
              android:orientation="vertical"
              android:layout_gravity="bottom|center_horizontal"
+             android:layout_marginTop="@dimen/keyguard_eca_top_margin"
              android:gravity="center_horizontal"/>
 </com.android.keyguard.KeyguardSimPukView>
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
new file mode 100644
index 0000000..e09bf7e
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/bools.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<resources>
+    <bool name="can_use_one_handed_bouncer">true</bool>
+</resources>
diff --git a/packages/SystemUI/res-keyguard/values/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index 8d9d6ee..6176f7c 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -22,4 +22,5 @@
 
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices [DO NOT TRANSLATE] -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
+    <bool name="can_use_one_handed_bouncer">false</bool>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index aa87107..a928b75 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -40,6 +40,8 @@
     <dimen name="keyguard_security_view_top_margin">8dp</dimen>
     <dimen name="keyguard_security_view_lateral_margin">36dp</dimen>
 
+    <dimen name="keyguard_eca_top_margin">24dp</dimen>
+
     <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text.
          Should be 0 on devices with plenty of room (e.g. tablets) -->
     <dimen name="eca_overlap">-10dip</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 2e99dea..cd82b80 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -23,12 +23,13 @@
         <item name="android:textSize">@dimen/kg_status_line_font_size</item>
     </style>
     <style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
         <item name="android:textSize">14dp</item>
         <item name="android:background">@drawable/kg_emergency_button_background</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:paddingLeft">12dp</item>
         <item name="android:paddingRight">12dp</item>
+        <item name="android:stateListAnimator">@null</item>
     </style>
     <style name="NumPadKey" parent="Theme.SystemUI">
       <item name="android:colorControlNormal">?android:attr/colorBackground</item>
diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
index 108591b..d097472 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_drawable_thick.xml
@@ -15,22 +15,24 @@
   ~ limitations under the License.
   -->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android"
-            android:paddingMode="stack">
+            android:paddingMode="stack" >
     <item android:id="@android:id/background"
         android:gravity="center_vertical|fill_horizontal">
-        <layer-list >
+        <layer-list>
             <item>
                 <shape
                     android:tint="?android:attr/colorControlActivated"
                     android:alpha="?android:attr/disabledAlpha">
-                    <size android:height="48dp" />
+                    <size android:height="@dimen/rounded_slider_height" />
                     <solid android:color="@color/white_disabled" />
-                    <corners android:radius="24dp" />
+                    <corners android:radius="@dimen/rounded_slider_corner_radius" />
                 </shape>
             </item>
             <item
-                android:gravity="center_vertical|start"
-                android:start="32dp">
+                android:gravity="center_vertical|left"
+                android:height="@dimen/rounded_slider_icon_size"
+                android:width="@dimen/rounded_slider_icon_size"
+                android:left="@dimen/rounded_slider_icon_inset">
                 <com.android.systemui.util.AlphaTintDrawableWrapper
                     android:drawable="@drawable/ic_brightness"
                     android:tint="?android:attr/colorControlActivated" />
@@ -39,10 +41,8 @@
     </item>
     <item android:id="@android:id/progress"
           android:gravity="center_vertical|fill_horizontal">
-        <clip
-            android:drawable="@drawable/brightness_progress_full_drawable"
-            android:clipOrientation="horizontal"
-            android:gravity="left"
-        />
+            <com.android.systemui.util.RoundedCornerProgressDrawable
+                android:drawable="@drawable/brightness_progress_full_drawable"
+            />
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
index b5def5e..41140a7 100644
--- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
+++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml
@@ -15,18 +15,21 @@
   ~ limitations under the License.
   -->
 
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:autoMirrored="true">
     <item android:id="@+id/slider_foreground">
         <shape>
-            <size android:height="48dp" />
+            <size android:height="@dimen/rounded_slider_height" />
             <solid android:color="?android:attr/colorControlActivated" />
-            <corners android:radius="24dp"/>
+            <corners android:radius="@dimen/rounded_slider_corner_radius"/>
         </shape>
     </item>
     <item
         android:id="@+id/slider_icon"
-        android:gravity="center_vertical|start"
-        android:start="32dp">
+        android:gravity="center_vertical|right"
+        android:height="@dimen/rounded_slider_icon_size"
+        android:width="@dimen/rounded_slider_icon_size"
+        android:right="@dimen/rounded_slider_icon_inset">
         <com.android.systemui.util.AlphaTintDrawableWrapper
             android:drawable="@drawable/ic_brightness"
             android:tint="?android:attr/colorBackground"
diff --git a/packages/SystemUI/res/drawable/controls_dialog_bg.xml b/packages/SystemUI/res/drawable/controls_dialog_bg.xml
new file mode 100644
index 0000000..cb4686dd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/controls_dialog_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="?android:attr/colorBackgroundFloating" />
+    <corners android:radius="@dimen/notification_corner_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
index cf64136..5cb6f46 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_camera.xml
@@ -21,16 +21,16 @@
           >
         <shape android:shape="oval">
             <size
-                android:height="28dp"
-                android:width="28dp"
+                android:height="@dimen/ongoing_appops_dialog_circle_size"
+                android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
             <solid android:color="@color/privacy_circle_camera" />
         </shape>
     </item>
     <item android:id="@id/icon"
           android:gravity="center"
-          android:width="20dp"
-          android:height="20dp"
+          android:width="@dimen/ongoing_appops_dialog_icon_size"
+          android:height="@dimen/ongoing_appops_dialog_icon_size"
           android:drawable="@*android:drawable/perm_group_camera"
     />
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
index 0a6a4a3..bff9b4b 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_location.xml
@@ -21,16 +21,16 @@
           >
         <shape android:shape="oval">
             <size
-                android:height="28dp"
-                android:width="28dp"
+                android:height="@dimen/ongoing_appops_dialog_circle_size"
+                android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
             <solid android:color="@color/privacy_circle_microphone_location" />
         </shape>
     </item>
     <item android:id="@id/icon"
           android:gravity="center"
-          android:width="20dp"
-          android:height="20dp"
-          android:drawable="@*android:drawable/perm_group_microphone"
+          android:width="@dimen/ongoing_appops_dialog_icon_size"
+          android:height="@dimen/ongoing_appops_dialog_icon_size"
+          android:drawable="@*android:drawable/perm_group_location"
     />
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
index 0a6a4a3..28466c8 100644
--- a/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_microphone.xml
@@ -21,16 +21,16 @@
           >
         <shape android:shape="oval">
             <size
-                android:height="28dp"
-                android:width="28dp"
+                android:height="@dimen/ongoing_appops_dialog_circle_size"
+                android:width="@dimen/ongoing_appops_dialog_circle_size"
             />
             <solid android:color="@color/privacy_circle_microphone_location" />
         </shape>
     </item>
     <item android:id="@id/icon"
           android:gravity="center"
-          android:width="20dp"
-          android:height="20dp"
+          android:width="@dimen/ongoing_appops_dialog_icon_size"
+          android:height="@dimen/ongoing_appops_dialog_icon_size"
           android:drawable="@*android:drawable/perm_group_microphone"
     />
 </layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
index 59dad0e..b8ea622 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
@@ -17,6 +17,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle" >
     <solid
-        android:color="?android:attr/textColorPrimary" />
+        android:color="?android:attr/textColorSecondary" />
     <corners android:radius="2dp" />
 </shape>
diff --git a/packages/SystemUI/res/drawable/stat_sys_roaming_large.xml b/packages/SystemUI/res/drawable/stat_sys_roaming_large.xml
new file mode 100644
index 0000000..1511659
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_roaming_large.xml
@@ -0,0 +1,24 @@
+<!--
+     Copyright (C) 2021 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="@dimen/signal_icon_size"
+        android:height="@dimen/signal_icon_size"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M14.21,12.81c0.36,-0.16 0.69,-0.36 0.97,-0.61c0.41,-0.38 0.72,-0.83 0.94,-1.37c0.21,-0.54 0.32,-1.14 0.32,-1.79c0,-0.92 -0.16,-1.7 -0.49,-2.33c-0.32,-0.64 -0.79,-1.12 -1.43,-1.45c-0.62,-0.33 -1.4,-0.49 -2.32,-0.49H8.23V19h1.8v-5.76h2.5L15.06,19h1.92v-0.12L14.21,12.81zM10.03,11.71V6.32h2.18c0.59,0 1.06,0.11 1.42,0.34c0.36,0.22 0.62,0.54 0.78,0.95c0.16,0.41 0.24,0.89 0.24,1.44c0,0.49 -0.09,0.93 -0.27,1.34c-0.18,0.4 -0.46,0.73 -0.82,0.97c-0.36,0.23 -0.82,0.35 -1.37,0.35H10.03z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/udfps_progress_bar.xml b/packages/SystemUI/res/drawable/udfps_progress_bar.xml
new file mode 100644
index 0000000..e5389f3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/udfps_progress_bar.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background">
+        <shape
+            android:innerRadiusRatio="2.2"
+            android:shape="ring"
+            android:thickness="@dimen/udfps_enroll_progress_thickness"
+            android:useLevel="false"
+            android:tint="?android:colorControlNormal">
+            <solid android:color="@*android:color/white_disabled_material" />
+        </shape>
+    </item>
+    <item android:id="@android:id/progress">
+        <rotate
+            android:fromDegrees="270"
+            android:pivotX="50%"
+            android:pivotY="50%"
+            android:toDegrees="270">
+            <shape
+                android:innerRadiusRatio="2.2"
+                android:shape="ring"
+                android:thickness="@dimen/udfps_enroll_progress_thickness"
+                android:tint="?android:attr/colorControlActivated">
+                <solid android:color="@android:color/white" />
+            </shape>
+        </rotate>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
index 71b414f..93664da 100644
--- a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
+++ b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
@@ -25,7 +25,7 @@
     android:layout_marginBottom="@dimen/screenshot_offset_y"
     android:scaleType="fitStart"
     android:elevation="@dimen/screenshot_preview_elevation"
-    android:visibility="gone"
+    android:visibility="invisible"
     android:background="@drawable/screenshot_rounded_corners"
     android:adjustViewBounds="true"
     android:contentDescription="@string/screenshot_edit_label"
diff --git a/packages/SystemUI/res/layout/controls_in_dialog.xml b/packages/SystemUI/res/layout/controls_in_dialog.xml
new file mode 100644
index 0000000..983999f
--- /dev/null
+++ b/packages/SystemUI/res/layout/controls_in_dialog.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/control_detail_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginVertical="@dimen/controls_activity_view_top_offset"
+    android:layout_marginHorizontal="@dimen/controls_activity_view_side_offset"
+    android:padding="8dp"
+    android:orientation="vertical"
+    android:background="@drawable/controls_dialog_bg">
+
+  <com.android.systemui.globalactions.MinHeightScrollView
+      android:layout_width="match_parent"
+      android:layout_height="0dp"
+      android:layout_weight="1"
+      android:orientation="vertical"
+      android:scrollbars="none">
+
+    <LinearLayout
+        android:id="@+id/global_actions_controls"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipChildren="false"
+        android:orientation="vertical"
+        android:clipToPadding="false" />
+
+  </com.android.systemui.globalactions.MinHeightScrollView>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_screenshot_preview.xml b/packages/SystemUI/res/layout/global_screenshot_preview.xml
index 5262407..100213b 100644
--- a/packages/SystemUI/res/layout/global_screenshot_preview.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_preview.xml
@@ -25,7 +25,7 @@
     android:layout_marginBottom="@dimen/screenshot_offset_y"
     android:scaleType="fitEnd"
     android:elevation="@dimen/screenshot_preview_elevation"
-    android:visibility="gone"
+    android:visibility="invisible"
     android:background="@drawable/screenshot_rounded_corners"
     android:adjustViewBounds="true"
     android:contentDescription="@string/screenshot_edit_label"
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 04de978..862076b 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -81,6 +81,17 @@
         android:contentDescription="@string/accessibility_phone_button"
         android:tint="?attr/wallpaperTextColor" />
 
+    <ImageView
+        android:id="@+id/alt_left_button"
+        android:layout_height="@dimen/keyguard_affordance_height"
+        android:layout_width="@dimen/keyguard_affordance_width"
+        android:layout_gravity="bottom|start"
+        android:scaleType="center"
+        android:tint="?attr/wallpaperTextColor"
+        android:layout_marginStart="24dp"
+        android:layout_marginBottom="48dp"
+        android:visibility="gone" />
+
     <FrameLayout
         android:id="@+id/overlay_container"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
new file mode 100644
index 0000000..e2f3e2a
--- /dev/null
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:background="?android:colorBackgroundFloating"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:id="@+id/save"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/save"
+        app:layout_constraintEnd_toStartOf="@id/cancel"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/guideline" />
+
+    <Button
+        android:id="@+id/cancel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/cancel"
+        app:layout_constraintEnd_toStartOf="@id/edit"
+        app:layout_constraintStart_toEndOf="@id/save"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/guideline" />
+
+    <Button
+        android:id="@+id/edit"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/screenshot_edit_label"
+        app:layout_constraintEnd_toStartOf="@id/share"
+        app:layout_constraintStart_toEndOf="@id/cancel"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/guideline" />
+
+    <Button
+        android:id="@+id/share"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@*android:string/share"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/edit"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/guideline" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.1" />
+
+    <ImageView
+        android:id="@+id/preview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="42dp"
+        android:layout_marginHorizontal="48dp"
+        android:adjustViewBounds="true"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintTop_toBottomOf="@id/guideline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        tools:background="?android:colorBackground"
+        tools:minHeight="100dp"
+        tools:minWidth="100dp" />
+
+    <com.android.systemui.screenshot.CropView
+        android:id="@+id/crop_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="42dp"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintTop_toBottomOf="@id/guideline"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:handleThickness="@dimen/screenshot_crop_handle_thickness"
+        app:handleColor="@*android:color/accent_device_default"
+        app:scrimColor="@color/screenshot_crop_scrim"
+        tools:background="?android:colorBackground"
+        tools:minHeight="100dp"
+        tools:minWidth="100dp" />
+
+    <com.android.systemui.screenshot.MagnifierView
+        android:id="@+id/magnifier"
+        android:visibility="invisible"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        app:layout_constraintTop_toBottomOf="@id/guideline"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:handleThickness="@dimen/screenshot_crop_handle_thickness"
+        app:handleColor="@*android:color/accent_device_default"
+        app:scrimColor="@color/screenshot_crop_scrim"
+        app:borderThickness="4dp"
+        app:borderColor="#fff"
+        />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+
diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml
index bfd079b..5552020 100644
--- a/packages/SystemUI/res/layout/mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/mobile_signal_group.xml
@@ -77,4 +77,11 @@
             android:contentDescription="@string/data_connection_roaming"
             android:visibility="gone" />
     </FrameLayout>
+    <ImageView
+        android:id="@+id/mobile_roaming_large"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/stat_sys_roaming_large"
+        android:contentDescription="@string/data_connection_roaming"
+        android:visibility="gone" />
 </com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index 5db247e..4d77a0d 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -22,7 +22,13 @@
     android:layout_height="wrap_content"
     android:layout_marginStart="@dimen/ongoing_appops_dialog_side_margins"
     android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
+    android:layout_marginTop="8dp"
     android:orientation="vertical"
-    android:padding = "8dp"
+    android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
+    android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
+    android:paddingBottom="12dp"
+    android:paddingTop="8dp"
     android:background="@drawable/privacy_dialog_bg"
-/>
\ No newline at end of file
+/>
+<!-- 12dp padding bottom so there's 20dp total under the icon -->
+<!-- 8dp padding top, as there's 4dp margin in each row -->
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml
index 882e968..91ffe22 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_item.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml
@@ -16,19 +16,22 @@
   -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/privacy_item"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="48dp"
     android:orientation="horizontal"
-    android:layout_marginTop="8dp"
+    android:layout_marginTop="4dp"
+    android:importantForAccessibility="yes"
+    android:focusable="true"
     >
+    <!-- 4dp marginTop makes 20dp minimum between icons -->
     <ImageView
         android:id="@+id/icon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
+        android:layout_width="@dimen/ongoing_appops_dialog_circle_size"
+        android:layout_height="@dimen/ongoing_appops_dialog_circle_size"
         android:layout_gravity="center_vertical"
-        android:layout_marginStart="12dp"
-        android:layout_marginEnd="12dp"
+        android:importantForAccessibility="no"
     />
 
     <TextView
@@ -38,25 +41,21 @@
         android:layout_width="0dp"
         android:layout_weight="1"
         android:layout_gravity="center_vertical"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        android:textDirection="locale"
+        android:textAlignment="viewStart"
         android:gravity="start | center_vertical"
-        android:textAppearance="@style/TextAppearance.QS.TileLabel"
+        android:textAppearance="@style/TextAppearance.PrivacyDialog"
+        android:lineHeight="20sp"
     />
 
-    <FrameLayout
-        android:id="@+id/link"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
+    <ImageView
+        android:layout_height="24dp"
+        android:layout_width="24dp"
         android:layout_gravity="center_vertical"
-        android:background="?android:attr/selectableItemBackground"
-    >
-
-        <ImageView
-            android:layout_height="24dp"
-            android:layout_width="24dp"
-            android:layout_gravity="center"
-            android:src="@*android:drawable/ic_chevron_end"
-            android:tint="?android:attr/textColorPrimary"
-            />
-    </FrameLayout>
+        android:src="@*android:drawable/ic_chevron_end"
+        android:tint="?android:attr/textColorPrimary"
+        />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 75f76b4..d6385ff 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -50,18 +50,22 @@
             android:layout="@layout/qs_panel"
             android:layout_width="@dimen/qs_panel_width"
             android:layout_height="match_parent"
-            android:layout_gravity="@integer/notification_panel_layout_gravity"
             android:clipToPadding="false"
             android:clipChildren="false"
-            systemui:viewType="com.android.systemui.plugins.qs.QS" />
+            systemui:viewType="com.android.systemui.plugins.qs.QS"
+            systemui:layout_constraintStart_toStartOf="parent"
+            systemui:layout_constraintEnd_toEndOf="parent"
+        />
 
         <com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
             android:id="@+id/notification_stack_scroller"
             android:layout_marginTop="@dimen/notification_panel_margin_top"
             android:layout_width="@dimen/notification_panel_width"
             android:layout_height="match_parent"
-            android:layout_gravity="@integer/notification_panel_layout_gravity"
-            android:layout_marginBottom="@dimen/close_handle_underlap" />
+            android:layout_marginBottom="@dimen/close_handle_underlap"
+            systemui:layout_constraintStart_toStartOf="parent"
+            systemui:layout_constraintEnd_toEndOf="parent"
+        />
 
         <include layout="@layout/ambient_indication"
             android:id="@+id/ambient_indication_container" />
diff --git a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
index 42d541e3..10d49b3 100644
--- a/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/status_bar_mobile_signal_group.xml
@@ -85,6 +85,13 @@
                 android:contentDescription="@string/data_connection_roaming"
                 android:visibility="gone" />
         </FrameLayout>
+        <ImageView
+            android:id="@+id/mobile_roaming_large"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/stat_sys_roaming_large"
+            android:contentDescription="@string/data_connection_roaming"
+            android:visibility="gone" />
     </com.android.keyguard.AlphaOptimizedLinearLayout>
 </com.android.systemui.statusbar.StatusBarMobileView>
 
diff --git a/packages/SystemUI/res/layout/udfps_animation_view.xml b/packages/SystemUI/res/layout/udfps_animation_view.xml
new file mode 100644
index 0000000..380dd85
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_animation_view.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<com.android.systemui.biometrics.UdfpsAnimationView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_animation_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/layout/udfps_surface_view.xml b/packages/SystemUI/res/layout/udfps_surface_view.xml
new file mode 100644
index 0000000..18858d6
--- /dev/null
+++ b/packages/SystemUI/res/layout/udfps_surface_view.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  -->
+<com.android.systemui.biometrics.UdfpsSurfaceView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/udfps_surface_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index c078805..6ae306e 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -20,4 +20,17 @@
     android:id="@+id/udfps_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    systemui:sensorTouchAreaCoefficient="0.5"/>
+    systemui:sensorTouchAreaCoefficient="0.5">
+
+    <!-- Enrollment progress bar-->
+    <com.android.systemui.biometrics.UdfpsProgressBar
+        android:id="@+id/progress_bar"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:max="100"
+        android:padding="@dimen/udfps_enroll_progress_thickness"
+        android:progress="0"
+        android:layout_gravity="center"
+        android:visibility="gone"/>
+
+</com.android.systemui.biometrics.UdfpsView>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 6dcb502..5db7c25 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Skermopname"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Begin"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stop"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Om voort te gaan, moet &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; toegang tot jou toestel se mikrofoon hê."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Om voort te gaan, moet &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; toegang tot jou toestel se kamera hê."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Toestel"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swiep op om programme te wissel"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Sleep regs om programme vinnig te wissel"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Tik weer om oop te maak"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Swiep op om oop te maak"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Swiep op om weer te probeer"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Ontsluit om NFC te gebruik"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Hierdie toestel behoort aan jou organisasie"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Hierdie toestel behoort aan <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Swiep vanaf ikoon vir foon"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Programme gebruik tans jou <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" en "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> gebruik tans die <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> het onlangs die <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> gebruik"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(onderneming)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Oproep"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(deur <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ligging"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofoon"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index d1954a6..1af18fe 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"መተግበሪያዎች የእርስዎን <xliff:g id="TYPES_LIST">%s</xliff:g> እየተጠቀሙ ነው።"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"፣ "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" እና "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>ን እየተጠቀመ ነው"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> በቅርብ ጊዜ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>ን ይጠቀማል።"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(ድርጅት)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"የስልክ ጥሪ"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(እስከ <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"ካሜራ"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"አካባቢ"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"ማይክሮፎን"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index f2652d3..b34b39a 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -424,10 +424,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"تسجيل الشاشة"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"بدء"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"إيقاف"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"‏للمتابعة، يحتاج تطبيق &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; إلى الوصول إلى ميكروفون الجهاز."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"‏للمتابعة، يحتاج تطبيق &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; إلى الوصول إلى كاميرا الجهاز."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"الجهاز"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"مرّر سريعًا لأعلى لتبديل التطبيقات"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"اسحب لليسار للتبديل السريع بين التطبيقات"</string>
@@ -450,8 +448,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"انقر مرة أخرى للفتح"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"يمكنك الفتح بالتمرير سريعًا لأعلى."</string>
     <string name="keyguard_retry" msgid="886802522584053523">"مرِّر سريعًا للأعلى لإعادة المحاولة."</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"‏يجب فتح قفل الشاشة لاستخدام تقنية الاتصال قصير المدى (NFC)."</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"هذا الجهاز يخص مؤسستك."</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"هذا الجهاز يخص <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>."</string>
     <string name="phone_hint" msgid="6682125338461375925">"يمكنك التمرير سريعًا من الرمز لتشغيل الهاتف"</string>
@@ -990,6 +987,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"تستخدم التطبيقات <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"، "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" و "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"يستخدم تطبيق <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> عملية <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> الآن."</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"استخدَم تطبيق <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> عملية <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> مؤخرًا."</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(للمؤسسات)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"مكالمة هاتفية"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(من خلال <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"الكاميرا"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"الموقع"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"الميكروفون"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 8abb5ed..88b2bb2 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"এপ্লিকেশ্বনসমূহে আপোনাৰ <xliff:g id="TYPES_LIST">%s</xliff:g> ব্যৱহাৰ কৰি আছে।"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" আৰু "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>এ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ব্যৱহাৰ কৰি আছে"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>এ শেহতীয়াকৈ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ব্যৱহাৰ কৰিছে"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(এণ্টাৰপ্ৰাইজ)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g>ৰ জৰিয়তে)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"কেমেৰা"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"অৱস্থান"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"মাইক্ৰ\'ফ\'ন"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 352282c..6f1c347 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekran yazması"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Başlayın"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Dayandırın"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Davam etmək üçün &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; tətbiqi cihazın mikrofonuna giriş tələb edir."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Davam etmək üçün &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; tətbiqi cihazın kamerasına giriş tələb edir."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Cihaz"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Tətbiqi dəyişmək üçün yuxarı sürüşdürün"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Tətbiqləri cəld dəyişmək üçün sağa çəkin"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Açmaq üçün yenidən tıklayın"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Açmaq üçün yuxarı sürüşdürün"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Yenidən cəhd etmək üçün yuxarı sürüşdürün"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC istifadə etmək üçün kiliddən çıxarın"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Bu cihaz təşkilatınıza məxsusdur"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Bu cihaz <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> təşkilatına məxsusdur"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Telefon üçün ikonadan sürüşdürün"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Tətbiqlər <xliff:g id="TYPES_LIST">%s</xliff:g> istifadə edir."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" və "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> istifadə edir"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> bu yaxınlarda <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> istifadə edib"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(korporativ)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefon zəngi"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> vasitəsilə)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"məkan"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index ba131c4..2185a66 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -418,10 +418,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Snimak ekrana"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Počnite"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zaustavite"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; zahteva pristup mikrofonu uređaja radi nastavljanja."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; zahteva pristup kameri uređaja radi nastavljanja."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Uređaj"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Prevucite nagore da biste menjali aplikacije"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Prevucite udesno da biste brzo promenili aplikacije"</string>
@@ -444,8 +442,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Dodirnite ponovo da biste otvorili"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Prevucite nagore da biste otvorili"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Prevucite nagore da biste probali ponovo"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Otključajte da biste koristili NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Ovaj uređaj pripada organizaciji"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Ovaj uređaj pripada organizaciji <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Prevucite od ikone za telefon"</string>
@@ -975,6 +972,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikacije koriste <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" i "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Aplikacija <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> koristi: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Aplikacija <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> je nedavno koristila: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(za preduzeća)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonski poziv"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(preko: <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kameru"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"lokaciju"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 61f7188..e5356ae 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Запіс экрана"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Пачаць"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Спыніць"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Каб працягнуць, дайце праграме &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ да мікрафона прылады."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Каб працягнуць, дайце праграме &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ да камеры прылады."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Прылада"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Правядзіце ўверх, каб пераключыць праграмы"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Каб хутка пераключыцца паміж праграмамі, перацягніце ўправа"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Дакраніцеся яшчэ раз, каб адкрыць"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Каб адкрыць, прагарніце ўверх"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Прагартайце ўверх, каб паўтарыць спробу"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Разблакіруйце, каб выкарыстоўваць NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Гэта прылада належыць вашай арганізацыі"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Гэта прылада належыць арганізацыі \"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>\""</string>
     <string name="phone_hint" msgid="6682125338461375925">"Тэлефон: правядзіце пальцам ад значка"</string>
@@ -980,6 +977,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Праграмы выкарыстоўваюць: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" і "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Праграма \"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>\" выкарыстоўвае праграму \"<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>\""</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Праграма \"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>\" нядаўна выкарыстоўвала праграму \"<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>\""</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(прадпрыемства)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Тэлефонны выклік"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(праз праграму \"<xliff:g id="ATTRIBUTION">%s</xliff:g>\")"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камера"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"геалакацыя"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"мікрафон"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index d6ba258..adf852b 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Записване на екрана"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Старт"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Стоп"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"За да продължите, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; се нуждае от достъп до микрофона на устройството ви."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"За да продължите, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; се нуждае от достъп до камерата на устройството ви."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Устройство"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Прекарайте пръст нагоре, за да превключите между приложенията"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Плъзнете надясно за бързо превключване между приложенията"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Докоснете отново, за да отворите"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Прекарайте пръст нагоре, за да отключите"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Плъзнете бързо нагоре, за да опитате отново"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Отключете, за да използвате NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Това устройство принадлежи на организацията ви"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Това устройство принадлежи на <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Плъзнете с пръст от иконата, за да използвате телефона"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Някои приложения използват <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" и "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> използва <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> наскоро използва <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(корпоративна версия)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Телефонно обаждане"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(чрез <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камерата"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"местополож."</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"микрофона"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 14c7779..437abb8 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"স্ক্রিন রেকর্ড"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"শুরু করুন"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"বন্ধ করুন"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"চালিয়ে যেতে, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; আপনার ডিভাইসের মাইক্রোফোন অ্যাক্সেস করতে চায়।"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"চালিয়ে যেতে, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; আপনার ডিভাইসের ক্যামেরা অ্যাক্সেস করতে চায়।"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"ডিভাইস"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"অন্য অ্যাপে যেতে উপরের দিকে সোয়াইপ করুন"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"একটি অ্যাপ ছেড়ে দ্রুত অন্য অ্যাপে যেতে ডান দিকে টেনে আনুন"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"খোলার জন্য আবার আলতো চাপুন"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"খোলার জন্য উপরে সোয়াইপ করুন"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"আবার চেষ্টা করতে উপরের দিকে সোয়াইপ করুন"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC ব্যবহার করতে আনলক করুন"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"এই ডিভাইসটি আপনার প্রতিষ্ঠানের"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"এই ডিভাইসটি <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>-এর"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ফোনের জন্য আইকন থেকে সোয়াইপ করুন"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"অ্যাপ্লিকেশনগুলি আপনার <xliff:g id="TYPES_LIST">%s</xliff:g> ব্যবহার করছে।"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" এবং "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> এখন <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ব্যবহার করছে"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> সম্প্রতি <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ব্যবহার করেছে"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"এন্টারপ্রাইজ"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"ফোনকল"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g>-এর মাধ্যমে)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"ক্যামেরা"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"লোকেশন"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"মাইক্রোফোন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index d2f0c0d..f2b3edc 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -972,6 +972,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikacije koriste <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" i "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Aplikacija <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> koristi aplikaciju <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Aplikacija <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> je nedavno koristila aplikaciju <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(preduzeće)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonski poziv"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(putem aplikacije <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kameru"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"lokaciju"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 1cc7288..bdcde31 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Gravació de pantalla"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Inicia"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Atura"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Per continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necessita accedir al micròfon del dispositiu."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Per continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necessita accedir a la càmera del dispositiu."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Dispositiu"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Llisca cap amunt per canviar d\'aplicació"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrossega el dit cap a la dreta per canviar ràpidament d\'aplicació"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Torna a tocar per obrir-la."</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Llisca cap amunt per obrir"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Llisca cap a dalt per tornar-ho a provar"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Desbloqueja per utilitzar l\'NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Aquest dispositiu pertany a la teva organització"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Aquest dispositiu pertany a <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Llisca des de la icona per obrir el telèfon"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Algunes aplicacions estan fent servir el següent: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" i "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> està utilitzant: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Recentment <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ha utilitzat: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(empresa)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Trucada"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(a través de: <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"càmera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ubicació"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"micròfon"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 13fe676..aa5a1dd 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Záznam obrazovky"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Spustit"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ukončit"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Než budete pokračovat, udělte aplikaci &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; přístup k mikrofonu na zařízení."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Než budete pokračovat, udělte aplikaci &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; přístup k fotoaparátu na zařízení."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Zařízení"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Přejetím nahoru přepnete aplikace"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Přetažením doprava rychle přepnete aplikace"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Oznámení otevřete opětovným klepnutím"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Otevřete přejetím prstem nahoru"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Přejetím nahoru to zkusíte znovu"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC vyžaduje odemknutou obrazovku"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Toto zařízení patří vaší organizaci"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Toto zařízení patří organizaci <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Telefon otevřete přejetím prstem od ikony"</string>
@@ -980,6 +977,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikace využívají tato oprávnění: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" a "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Aplikace <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> používá aplikaci <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Aplikace <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> nedávno použila aplikaci <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(podniková verze)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonní hovor"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(prostřednictvím aplikace <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"fotoaparát"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"poloha"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 18dfc06..5630293 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Optag skærm"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Start"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stop"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; skal have adgang til din enheds mikrofon, før den kan fortsætte."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; skal have adgang til din enheds kamera, før den kan fortsætte."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Enhed"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Stryg opad for at skifte apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Træk til højre for hurtigt at skifte app"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Tryk igen for at åbne"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Stryg opad for at åbne"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Stryg opad for at prøve igen"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Lås op for at bruge NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Denne enhed tilhører din organisation"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Denne enhed tilhører <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Stryg fra telefonikonet"</string>
@@ -784,7 +781,7 @@
     <string name="keyboard_key_tab" msgid="4592772350906496730">"Tab"</string>
     <string name="keyboard_key_space" msgid="6980847564173394012">"Mellemrumstast"</string>
     <string name="keyboard_key_enter" msgid="8633362970109751646">"Enter"</string>
-    <string name="keyboard_key_backspace" msgid="4095278312039628074">"Tilbagetast"</string>
+    <string name="keyboard_key_backspace" msgid="4095278312039628074">"Backspace"</string>
     <string name="keyboard_key_media_play_pause" msgid="8389984232732277478">"Afspil/pause"</string>
     <string name="keyboard_key_media_stop" msgid="1509943745250377699">"Stop"</string>
     <string name="keyboard_key_media_next" msgid="8502476691227914952">"Næste"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Apps anvender enhedens <xliff:g id="TYPES_LIST">%s</xliff:g>"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" og "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> anvender <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> anvendte <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> for nylig"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(til virksomhedsbrug)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonopkald"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(via <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"placering"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 8485e84..e2468bf 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Bildschirmaufnahme"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starten"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Beenden"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Zum Fortfahren benötigt, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; Zugriff auf das Mikrofon deines Geräts."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Zum Fortfahren benötigt &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; Zugriff auf die Kamera deines Geräts."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Gerät"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Nach oben wischen, um Apps zu wechseln"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Zum schnellen Wechseln der Apps nach rechts ziehen"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Erneut tippen, um Benachrichtigung zu öffnen"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Zum Öffnen nach oben wischen"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Zum Wiederholen nach oben wischen"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Zur Verwendung von NFC entsperren"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Dieses Gerät gehört deiner Organisation"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Dieses Gerät gehört <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Zum Öffnen des Telefons vom Symbol wegwischen"</string>
@@ -970,6 +967,16 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Apps verwenden gerade Folgendes: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" und "</string>
+    <!-- no translation found for ongoing_privacy_dialog_using_op (4125175620929701569) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_recent_op (4255923947334262404) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_enterprise (4082735415905550729) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_phonecall (3526223335298089311) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_attribution_text (9186683306719924646) -->
+    <skip />
     <string name="privacy_type_camera" msgid="7974051382167078332">"Kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"Standort"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"Mikrofon"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index f21a9c9..aa747f2 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Εγγραφή οθόνης"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Έναρξη"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Διακοπή"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Για να συνεχίσετε, η εφαρμογή &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&amp;gt, χρειάζεται πρόσβαση στο μικρόφωνο της συσκευής σας."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Για να συνεχίσετε, η εφαρμογή &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; χρειάζεται πρόσβαση στην κάμερα της συσκευής σας."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Συσκευή"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Σύρετε προς τα επάνω για εναλλαγή των εφαρμογών"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Σύρετε προς τα δεξιά για γρήγορη εναλλαγή εφαρμογών"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Πατήστε ξανά για να ανοίξετε"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Σύρετε προς τα επάνω για άνοιγμα"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Σύρετε προς τα πάνω για να δοκιμάσετε ξανά"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Ξεκλείδωμα για χρήση του NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Αυτή η συσκευή ανήκει στον οργανισμό σας."</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Αυτή η συσκευή ανήκει στον οργανισμό <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Σύρετε προς τα έξω για τηλέφωνο"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Οι εφαρμογές χρησιμοποιούν τις λειτουργίες <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" και "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Χρήση <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> από την εφαρμογή <xliff:g id="APPLICATION_NAME">%1$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Πρόσφατη χρήση <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> από την εφαρμογή <xliff:g id="APPLICATION_NAME">%1$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(για επιχειρήσεις)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Τηλεφωνική κλήση"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(μέσω <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"κάμερα"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"τοποθεσία"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"μικρόφωνο"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 4ae38f5..88a40c3 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Applications are using your <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" and "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> is using the <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> used the <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> recently"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(through <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"camera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"location"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microphone"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index ff5e873e..bf7883a 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Applications are using your <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" and "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> is using the <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> used the <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> recently"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(through <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"camera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"location"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microphone"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 4ae38f5..88a40c3 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Applications are using your <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" and "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> is using the <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> used the <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> recently"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(through <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"camera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"location"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microphone"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 4ae38f5..88a40c3 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Applications are using your <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" and "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> is using the <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> used the <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> recently"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(through <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"camera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"location"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microphone"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index cddd75e..a14566a 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎Applications are using your ‎‏‎‎‏‏‎<xliff:g id="TYPES_LIST">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎, ‎‏‎‎‏‎ "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎ and ‎‏‎‎‏‎ "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is using the ‎‏‎‎‏‏‎<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ used the ‎‏‎‎‏‏‎<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ recently‎‏‎‎‏‎"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎(enterprise)‎‏‎‎‏‎"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎Phonecall‎‏‎‎‏‎"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‎‏‎‎‏‏‎‎(through ‎‏‎‎‏‏‎<xliff:g id="ATTRIBUTION">%s</xliff:g>‎‏‎‎‏‏‏‎)‎‏‎‎‏‎"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎‏‏‏‏‎‎‎camera‎‏‎‎‏‎"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‏‎location‎‏‎‎‏‎"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‎microphone‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 021d944..4b482b9 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Hay aplicaciones que están usando tu <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" y "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> está usando <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> usó <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> recientemente"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(empresarial)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Teléfono"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(a través de <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"cámara"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ubicación"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"micrófono"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 584116e..36fee3f 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Grabar pantalla"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Detener"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Para continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necesita tener acceso al micrófono del dispositivo."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Para continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necesita tener acceso a la cámara del dispositivo."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Dispositivo"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Desliza el dedo hacia arriba para cambiar de aplicación"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrastra hacia la derecha para cambiar rápidamente de aplicación"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Toca de nuevo para abrir"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Desliza el dedo hacia arriba para abrir"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Desliza el dedo hacia arriba para volverlo a intentar"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Desbloquear para usar NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Este dispositivo pertenece a tu organización"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Este dispositivo pertenece a <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Desliza desde el icono para abrir el teléfono"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Hay aplicaciones que usan tu <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" y "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> está usando este elemento: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ha usado recientemente este elemento: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(empresa)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Llamada telefónica"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(a través de <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"cámara"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ubicación"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"micrófono"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 72c6907..ff043e0 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekraanisalvestus"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Alustage"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Peatage"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Jätkamiseks vajab rakendus &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; juurdepääsu teie seadme mikrofonile."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Jätkamiseks vajab rakendus &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; juurdepääsu teie seadme kaamerale."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Seade"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Rakenduste vahetamiseks pühkige üles"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Lohistage paremale, et rakendusi kiiresti vahetada"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Avamiseks puudutage uuesti"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Pühkige avamiseks üles"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Uuesti proovimiseks pühkige üles"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC kasutamiseks avage."</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"See seade kuulub teie organisatsioonile"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Selle seadme omanik on <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Telefoni kasutamiseks pühkige ikoonilt eemale"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Rakendused kasutavad järgmisi: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ja "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> kasutab järgmist: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> kasutas hiljuti järgmist: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(ettevõte)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonikõne"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(üksuse <xliff:g id="ATTRIBUTION">%s</xliff:g> kaudu)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kaamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"asukoht"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index ab26b92..7517f17 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Pantaila-grabaketa"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Hasi"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Gelditu"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Aurrera egiteko, gailuaren mikrofonoa atzitzeko baimena behar du &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; aplikazioak."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Aurrera egiteko, gailuaren kamera atzitzeko baimena behar du &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; aplikazioak."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Gailua"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Egin gora aplikazioa aldatzeko"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrastatu eskuinera aplikazioa azkar aldatzeko"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Irekitzeko, ukitu berriro"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Pasatu hatza gora irekitzeko"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Berriro saiatzeko, pasatu hatza gora"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Desblokeatu NFC erabiltzeko"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Gailu hau zure erakundearena da"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Gailu hau <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> erakundearena da"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Pasatu hatza ikonotik, telefonoa irekitzeko"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikazio batzuk <xliff:g id="TYPES_LIST">%s</xliff:g> erabiltzen ari dira."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" eta "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> aplikazioa <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> erabiltzen ari da"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> aplikazioak <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> erabili du duela gutxi"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enpresa)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefono-deia"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> aplikazioaren bidez)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"kokapena"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofonoa"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index dccea5a..231eb45 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"برنامه‌ها از <xliff:g id="TYPES_LIST">%s</xliff:g> شما استفاده می‌‌کنند."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"، "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" و "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> درحال استفاده از <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> است"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> اخیراً از <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> استفاده کرده است"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(شرکتی)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(ازطریق <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"دوربین"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"مکان"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"میکروفون"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 5b43cf9..a6c43d9 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Tallennus"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Aloita"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Lopeta"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Jotta voit jatkaa, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; tarvitsee pääsyn laitteesi mikrofoniin."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Jotta voit jatkaa, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; tarvitsee pääsyn laitteesi kameraan."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Laite"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Vaihda sovellusta pyyhkäisemällä ylös"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Vaihda sovellusta nopeasti vetämällä oikealle"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Avaa napauttamalla uudelleen"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Avaa pyyhkäisemällä ylös"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Yritä uudelleen pyyhkäisemällä ylös"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Avaa lukitus käyttääksesi NFC:tä"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Organisaatiosi omistaa tämän laitteen"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g> omistaa tämän laitteen"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Avaa puhelu pyyhkäisemällä."</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"<xliff:g id="TYPES_LIST">%s</xliff:g> ovat sovellusten käytössä."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ja "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> käyttää kohdetta <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> käytti kohdetta <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> äskettäin"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(yritys)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Puhelu"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(kautta: <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"sijainti"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofoni"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 2e36a92..07f8af5 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Des applications utilisent votre <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" et "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> utilise cet élément : <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> a récemment utilisé cet élément : <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(entreprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Appel téléphonique"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(par l\'intermédiaire de <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"appareil photo"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"position"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microphone"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index b8a70ee..7bbbff6 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Enregistrement de l\'écran"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Démarrer"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Arrêter"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Pour continuer, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; a besoin d\'accéder au micro de votre appareil."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Pour continuer, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; a besoin d\'accéder à l\'appareil photo de votre appareil."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Appareil"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Balayer l\'écran vers le haut pour changer d\'application"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Déplacer vers la droite pour changer rapidement d\'application"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Appuyer à nouveau pour ouvrir"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Balayer vers le haut pour ouvrir"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Balayez l\'écran vers le haut pour réessayer"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Déverrouillez l\'écran pour pouvoir utiliser NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Cet appareil appartient à votre organisation"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Cet appareil appartient à <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Balayer pour téléphoner"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Des applications utilisent votre <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" et "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> utilise <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> a utilisé <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> récemment"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(Enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Appel téléphonique"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(via <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"appareil photo"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"position"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"micro"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 40a514e..0ac8261 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Gravación da pantalla"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Iniciar"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Deter"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Para continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; precisa acceder ao micrófono do dispositivo."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Para continuar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; precisa acceder á cámara do dispositivo."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Dispositivo"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Pasar o dedo cara arriba para cambiar de aplicación"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrastra cara á dereita para cambiar de aplicacións rapidamente"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Toca de novo para abrir"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Pasa o dedo cara arriba para abrir"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Pasa o dedo cara arriba para tentalo de novo"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Desbloquea o dispositivo para utilizar a NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Este dispositivo pertence á túa organización"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Este dispositivo pertence a <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Pasa o dedo desde a icona para acceder ao teléfono"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Hai aplicacións que están utilizando <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" e "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> está utilizando a aplicación <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> utilizou recentemente a aplicación <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(versión empresarial)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Chamada de teléfono"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(mediante <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"a cámara"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"a localiz."</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"o micrófono"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 2c37ce7..f4e1333 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"સ્ક્રીન રેકૉર્ડ કરો"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"શરૂ કરો"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"રોકો"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"ચાલુ રાખવા માટે, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;ને તમારા ડિવાઇસના માઇક્રોફોનના ઍક્સેસની જરૂર છે."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"ચાલુ રાખવા માટે, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;ને તમારા ડિવાઇસના કૅમેરાના ઍક્સેસની જરૂર છે."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"ડિવાઇસ"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ઍપ સ્વિચ કરવા માટે ઉપરની તરફ સ્વાઇપ કરો"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ઍપને ઝડપથી સ્વિચ કરવા માટે જમણે ખેંચો"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"ખોલવા માટે ફરીથી ટૅપ કરો"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"ખોલવા માટે ઉપરની તરફ સ્વાઇપ કરો"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"ફરી પ્રયાસ કરવા માટે ઉપરની તરફ સ્વાઇપ કરો"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFCનો ઉપયોગ કરવા માટે અનલૉક કરો"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"આ ડિવાઇસ તમારી સંસ્થાની માલિકીનું છે"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"આ ડિવાઇસ <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>ની માલિકીનું છે"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ફોન માટે આયકનમાંથી સ્વાઇપ કરો"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ઍપ્લિકેશન તમારા <xliff:g id="TYPES_LIST">%s</xliff:g>નો ઉપયોગ કરી રહી છે."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" અને "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>, <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>નો ઉપયોગ કરી રહી છે"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>એ તાજેતરમાં જ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>નો ઉપયોગ કર્યો છે"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(એન્ટરપ્રાઇઝ)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"ફોન કૉલ"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> મારફતે)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"કૅમેરા"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"સ્થાન"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"માઇક્રોફોન"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index af9bf1e..50b078f 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -418,10 +418,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"स्क्रीन रिकॉर्ड"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"शुरू करें"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"रोकें"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"जारी रखने के लिए, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; को आपके डिवाइस का माइक्रोफ़ोन ऐक्सेस करने की ज़रूरत है."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"जारी रखने के लिए, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; को आपके डिवाइस का कैमरा ऐक्सेस करने की ज़रूरत है."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"डिवाइस"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ऐप्लिकेशन बदलने के लिए ऊपर स्वाइप करें"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ऐप्लिकेशन को झटपट स्विच करने के लिए उसे दाईं ओर खींचें और छोड़ें"</string>
@@ -444,8 +442,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"खोलने के लिए फिर से टैप करें"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"खोलने के लिए ऊपर स्वाइप करें"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"फिर से कोशिश करने के लिए ऊपर की ओर स्वाइप करें"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"एनएफ़सी इस्तेमाल करने के लिए, स्क्रीन को अनलॉक करें"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"इस डिवाइस का मालिकाना हक आपके संगठन के पास है"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"इस डिवाइस का मालिकाना हक <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> के पास है"</string>
     <string name="phone_hint" msgid="6682125338461375925">"फ़ोन के लिए आइकॉन से स्वाइप करें"</string>
@@ -972,6 +969,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ऐप्लिकेशन आपकी <xliff:g id="TYPES_LIST">%s</xliff:g> का इस्तेमाल कर रहे हैं."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" और "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>, <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> का इस्तेमाल कर रहा है"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ने हाल ही में <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> का इस्तेमाल किया"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"एंटरप्राइज़ वर्शन"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"<xliff:g id="ATTRIBUTION">%s</xliff:g> के ज़रिए"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"कैमरा"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"जगह"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"माइक्रोफ़ोन"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 73e1349..c80f262 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -418,10 +418,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Snimač zaslona"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Početak"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zaustavi"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Da bi nastavila s radom, aplikacija &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; treba pristupiti mikrofonu vašeg uređaja."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Da bi nastavila s radom, aplikacija &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; treba pristupiti fotoaparatu vašeg uređaja."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Uređaj"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Prijeđite prstom prema gore da biste promijenili aplikaciju"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Povucite udesno da biste brzo promijenili aplikaciju"</string>
@@ -444,8 +442,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Dodirnite opet za otvaranje"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Prijeđite prstom prema gore da biste otvorili"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Prijeđite prstom prema gore za ponovni pokušaj"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Otključajte da biste upotrijebili NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Ovaj uređaj pripada vašoj organizaciji"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Ovaj uređaj pripada organizaciji <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Prijeđite prstom od ikone za telefon"</string>
@@ -975,6 +972,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikacije upotrebljavaju <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" i "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Aplikacija <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> koristi sljedeće: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Aplikacija <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> nedavno je koristila sljedeće: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(za poslovne korisnike)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonski poziv"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(putem aplikacije <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"fotoaparat"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"lokaciju"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index c947754..868b2e7 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Képernyő rögzítése"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Indítás"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Leállítás"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"A folytatáshoz a(z) &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; alkalmazásnak hozzáférésre van szüksége az eszköze mikrofonjához."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"A folytatáshoz a(z) &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; alkalmazásnak hozzáférésre van szüksége az eszköze kamerájához."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Eszköz"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Váltás az alkalmazások között felfelé csúsztatással"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Húzza jobbra az ujját az alkalmazások közötti gyors váltáshoz"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Koppintson ismét a megnyitáshoz"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Csúsztasson felfelé a megnyitáshoz"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Az újrapróbálkozáshoz csúsztassa felfelé az ujját"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Az NFC használatához oldja fel a képernyőzárat"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Ez az eszköz az Ön szervezetének tulajdonában van"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Ez az eszköz a(z) <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> tulajdonában van"</string>
     <string name="phone_hint" msgid="6682125338461375925">"A telefonhoz csúsztasson az ikonról"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Több alkalmazás használja a következőket: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" és "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"A(z) <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> használja a következőt: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"A(z) <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> nemrég használta a következőt: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(vállalati)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonhívás"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(a következőn keresztül: <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"helyadatok"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 1ef540c..3c08874 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Էկրանի ձայնագրում"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Սկսել"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Կանգնեցնել"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Շարունակելու համար &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; հավելվածին անհրաժեշտ է ձեր սարքի խոսափողի օգտագործման թույլտվություն։"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Շարունակելու համար &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; հավելվածին անհրաժեշտ է ձեր սարքի տեսախցիկի օգտագործման թույլտվություն։"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Սարք"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Սահեցրեք վերև՝ մյուս հավելվածին անցնելու համար"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Քաշեք աջ՝ հավելվածների միջև անցնելու համար"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Կրկին հպեք՝ բացելու համար"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Բացելու համար սահեցրեք վերև"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Սահեցրեք վերև՝ նորից փորձելու համար"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Ապակողպեք՝ NFC-ն օգտագործելու համար"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Այս սարքը պատկանում է ձեր կազմակերպությանը"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Այս սարքը պատկանում է «<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>» կազմակերպությանը"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Սահահարվածեք հեռախոսի պատկերակից"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Հավելվածներն օգտագործում են ձեր <xliff:g id="TYPES_LIST">%s</xliff:g>:"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" և "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> հավելվածն օգտագործում է «<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>» գործառույթը"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> հավելվածը վերջերս օգտագործել է «<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>» գործառույթը"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(կորպորատիվ տարբերակ)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Հեռախոսազանգ"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g>-ի միջոցով)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"տեսախցիկը"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"վայրը"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"խոսափողը"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 0de7efa..a5d8a27 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Rekaman Layar"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Mulai"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Berhenti"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Untuk melanjutkan, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; memerlukan akses ke mikrofon perangkat."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Untuk melanjutkan, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; memerlukan akses ke kamera perangkat."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Perangkat"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Geser ke atas untuk beralih aplikasi"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Tarik ke kanan untuk beralih aplikasi dengan cepat"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Ketuk lagi untuk membuka"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Geser ke atas untuk membuka"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Geser ke atas untuk mencoba lagi"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Buka kunci untuk menggunakan NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Perangkat ini milik organisasi Anda"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Perangkat ini milik <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Geser dari ikon untuk telepon"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikasi menggunakan <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" dan "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> sedang menggunakan <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> menggunakan <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> baru-baru ini"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(perusahaan)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Panggilan telepon"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(melalui <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"lokasi"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 777479d..5f794e7 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Skjáupptaka"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Hefja"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stöðva"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Til að halda áfram þarf &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; aðgang að hljóðnema tækisins."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Til að halda áfram þarf &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; aðgang að myndavél tækisins."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Tæki"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Strjúktu upp til að skipta á milli forrita"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Dragðu til hægri til að skipta hratt á milli forrita"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Ýttu aftur til að opna"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Strjúktu upp til að opna"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Strjúktu upp til að reyna aftur"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Taktu úr lás til að nota NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Þetta tæki tilheyrir fyrirtækinu þínu"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Þetta tæki tilheyrir <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Strjúktu frá tákninu fyrir síma"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Forrit eru að nota <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" og "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> er að nota <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> notaði <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> nýlega"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(fyrirtæki)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Símtal"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(í gegnum <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"myndavél"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"staðsetning"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"hljóðnemi"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 2af2d5f..cfd0797 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Registrazione schermo"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Inizia"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Interrompi"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Per continuare, l\'app &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; deve accedere al microfono del dispositivo."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Per continuare, l\'app &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; deve accedere alla videocamera del dispositivo."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Dispositivo"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Scorri verso l\'alto per passare ad altre app"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Trascina verso destra per cambiare velocemente app"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Tocca ancora per aprire"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Scorri verso l\'alto per aprire"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Scorri verso l\'alto per riprovare"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Sblocca per usare NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Questo dispositivo appartiene alla tua organizzazione"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Questo dispositivo appartiene a <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Scorri per accedere al telefono"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Le app stanno usando <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" e "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> sta usando: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ha usato di recente: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(tramite <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"Fotocamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"luogo"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"un microfono"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index e8a1ede..c4d088d 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"הקלטת המסך"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"התחלה"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"עצירה"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"‏כדי להמשיך, האפליקציה &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; צריכה גישה למיקרופון של המכשיר שלך."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"‏כדי להמשיך, האפליקציה &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; צריכה גישה למצלמה של המכשיר שלך."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"מכשיר"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"יש להחליק מעלה כדי להחליף אפליקציות"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"יש לגרור ימינה כדי לעבור במהירות בין אפליקציות"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"הקש שוב כדי לפתוח"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"צריך להחליק כדי לפתוח"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"יש להחליק למעלה כדי לנסות שוב"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"‏יש לבטל נעילה כדי להשתמש ב-NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"המכשיר הזה שייך לארגון שלך"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"המכשיר הזה שייך לארגון <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"החלק מהסמל כדי להפעיל את הטלפון"</string>
@@ -980,6 +977,16 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"אפליקציות משתמשות ב<xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" וגם "</string>
+    <!-- no translation found for ongoing_privacy_dialog_using_op (4125175620929701569) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_recent_op (4255923947334262404) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_enterprise (4082735415905550729) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_phonecall (3526223335298089311) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_attribution_text (9186683306719924646) -->
+    <skip />
     <string name="privacy_type_camera" msgid="7974051382167078332">"מצלמה"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"מיקום"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"מיקרופון"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index e15366c..3fcc713 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"スクリーン レコード"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"開始"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"停止"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"続行するには、&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; にデバイスのマイクへのアクセスを許可する必要があります。"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"続行するには、&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; にデバイスのカメラへのアクセスを許可する必要があります。"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"デバイス"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"アプリを切り替えるには上にスワイプ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"右にドラッグするとアプリを素早く切り替えることができます"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"開くにはもう一度タップしてください"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"開くには上にスワイプします"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"上にスワイプしてもう一度お試しください"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC を使用するには、ロックを解除してください"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"これは組織が所有するデバイスです"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"これは <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> が所有するデバイスです"</string>
     <string name="phone_hint" msgid="6682125338461375925">"右にスワイプして通話"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"アプリは<xliff:g id="TYPES_LIST">%s</xliff:g>を使用しています。"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"、 "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" 、 "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> は <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> を使用しています"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> は最近 <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> を使用しました"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(エンタープライズ版)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"電話"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> 経由)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"カメラ"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"現在地情報"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"マイク"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 386847e..5c11fa4 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ეკრანის ჩანაწერი"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"დაწყება"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"შეწყვეტა"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"გასაგრძელებლად &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;-ს თქვენი მოწყობილობის მიკროფონზე წვდომა სჭირდება."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"გასაგრძელებლად &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;-ს თქვენი მოწყობილობის კამერაზე წვდომა სჭირდება."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"მოწყობილობა"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"გადაფურცლეთ ზემოთ აპების გადასართავად"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"აპების სწრაფად გადასართავად ჩავლებით გადაიტანეთ მარჯვნივ"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"შეეხეთ ისევ გასახსნელად"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"გასახსნელად გადაფურცლეთ ზემოთ"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"ხელახლა საცდელად გადაფურცლეთ ზემოთ"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"განბლოკეთ NFC-ის გამოსაყენებლად"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ამ მოწყობილობას ფლობს თქვენი ორგანიზაცია"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ამ მოწყობილობას ფლობს <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ტელეფონისთვის გადაფურცლეთ ხატულადან"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"აპლიკაციების მიერ გამოიყენება თქვენი <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" და "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> იყენებს აპს <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"აპმა <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ახლახან გამოიყენა <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(კორპორაციული)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"სატელეფონო ზარი"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(აპის <xliff:g id="ATTRIBUTION">%s</xliff:g> მეშვეობით)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"კამერა"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"მდებარეობა"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"მიკროფონი"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index db318ea..89f905733f 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -285,8 +285,8 @@
     <string name="accessibility_quick_settings_work_mode_on" msgid="2779253456042059110">"Жұмыс режимі қосулы."</string>
     <string name="accessibility_quick_settings_work_mode_changed_off" msgid="6256690740556798683">"Жұмыс режимі өшірілді."</string>
     <string name="accessibility_quick_settings_work_mode_changed_on" msgid="1105258550138313384">"Жұмыс режимі қосылды."</string>
-    <string name="accessibility_quick_settings_data_saver_changed_off" msgid="4910847127871603832">"Data Saver өшірілді."</string>
-    <string name="accessibility_quick_settings_data_saver_changed_on" msgid="6370606590802623078">"Data Saver қосылды."</string>
+    <string name="accessibility_quick_settings_data_saver_changed_off" msgid="4910847127871603832">"Трафикті үнемдеу режимі өшірілді."</string>
+    <string name="accessibility_quick_settings_data_saver_changed_on" msgid="6370606590802623078">"Трафикті үнемдеу режимі қосылды."</string>
     <string name="accessibility_quick_settings_sensor_privacy_changed_off" msgid="7608378211873807353">"Sensor Privacy функциясы өшірулі."</string>
     <string name="accessibility_quick_settings_sensor_privacy_changed_on" msgid="4267393685085328801">"Sensor Privacy функциясы қосулы."</string>
     <string name="accessibility_brightness" msgid="5391187016177823721">"Дисплей жарықтығы"</string>
@@ -383,7 +383,7 @@
     <string name="quick_settings_tethering_label" msgid="5257299852322475780">"Тетеринг"</string>
     <string name="quick_settings_hotspot_label" msgid="1199196300038363424">"Хотспот"</string>
     <string name="quick_settings_hotspot_secondary_label_transient" msgid="7585604088079160564">"Қосылуда…"</string>
-    <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Data Saver қосулы"</string>
+    <string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Трафикті үнемдеу режимі қосулы"</string>
     <plurals name="quick_settings_hotspot_secondary_label_num_devices" formatted="false" msgid="3142308865165871976">
       <item quantity="other">%d құрылғы</item>
       <item quantity="one">%d құрылғы</item>
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Экранды жазу"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Бастау"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Тоқтату"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Жалғастыру үшін &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; қолданбасы құрылғыңыздың микрофонына рұқсат алу керек."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Жалғастыру үшін &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; қолданбасы құрылғыңыздың камерасына рұқсат алу керек."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Құрылғы"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Қолданбалар арасында ауысу үшін жоғары сырғытыңыз"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Қолданбаларды жылдам ауыстырып қосу үшін оңға қарай сүйреңіз"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Ашу үшін қайта түртіңіз"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Ашу үшін жоғары қарай сырғытыңыз."</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Әрекетті қайталау үшін жоғары сырғытыңыз."</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC пайдалану үшін құлыпты ашыңыз."</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Бұл құрылғы ұйымыңызға тиесілі."</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Бұл құрылғы <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> ұйымына тиесілі."</string>
     <string name="phone_hint" msgid="6682125338461375925">"Телефонды ашу үшін белгішеден әрі қарай сырғытыңыз"</string>
@@ -825,9 +822,9 @@
     <string name="accessibility_long_click_tile" msgid="210472753156768705">"Параметрлерді ашу"</string>
     <string name="accessibility_status_bar_headphones" msgid="1304082414912647414">"Құлақаспап қосылды"</string>
     <string name="accessibility_status_bar_headset" msgid="2699275863720926104">"Құлақаспап жинағы қосылды"</string>
-    <string name="data_saver" msgid="3484013368530820763">"Data Saver"</string>
-    <string name="accessibility_data_saver_on" msgid="5394743820189757731">"Дерек сақтағыш қосулы"</string>
-    <string name="accessibility_data_saver_off" msgid="58339669022107171">"Дерек сақтағышы өшірулі"</string>
+    <string name="data_saver" msgid="3484013368530820763">"Трафикті үнемдеу"</string>
+    <string name="accessibility_data_saver_on" msgid="5394743820189757731">"Трафикті үнемдеу режимі қосулы"</string>
+    <string name="accessibility_data_saver_off" msgid="58339669022107171">"Трафикті үнемдеу режимі өшірулі"</string>
     <string name="switch_bar_on" msgid="1770868129120096114">"Қосулы"</string>
     <string name="switch_bar_off" msgid="5669805115416379556">"Өшірулі"</string>
     <string name="tile_unavailable" msgid="3095879009136616920">"Қолжетімді емес"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Қолданбаларда <xliff:g id="TYPES_LIST">%s</xliff:g> пайдаланылуда."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" және "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> қолданбасы қазір <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> пайдаланады"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> қолданбасы жақында <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> пайдаланды"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(корпоративтік)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Телефон қоңырауы"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> арқылы)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камера"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"геодерек"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"микрофон"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index dca2623..8555707 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ការថត​អេក្រង់"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ចាប់ផ្ដើម"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ឈប់"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"ដើម្បីបន្ត &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ត្រូវការសិទ្ធិចូលប្រើ​មីក្រូហ្វូន​របស់​ឧបករណ៍អ្នក។"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"ដើម្បីបន្ត &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ត្រូវការសិទ្ធិ​ចូលប្រើ​កាមេរ៉ា​របស់ឧបករណ៍អ្នក។"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"ឧបករណ៍"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"អូស​ឡើង​លើ​ដើម្បី​ប្តូរ​កម្មវិធី"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"អូសទៅស្ដាំដើម្បីប្ដូរកម្មវិធីបានរហ័ស"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"ប៉ះ​ម្ដង​ទៀត ដើម្បី​បើក"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"អូសឡើងលើ​ដើម្បីបើក"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"អូសឡើងលើ ដើម្បី​ព្យាយាម​ម្ដងទៀត"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"ដោះសោ ដើម្បីប្រើ NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ឧបករណ៍​នេះគឺជា​កម្មសិទ្ធិរបស់​ស្ថាប័ន​អ្នក"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ឧបករណ៍នេះ​គឺជា​កម្មសិទ្ធិ​របស់ <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"អូសចេញពីរូបតំណាងដើម្បីប្រើទូរស័ព្ទ"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"កម្មវិធី​កំពុងប្រើ <xliff:g id="TYPES_LIST">%s</xliff:g> របស់អ្នក។"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" និង "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> កំពុងប្រើ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> បានប្រើ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ថ្មីៗនេះ"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(សហគ្រាស)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"ការហៅទូរសព្ទ"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(តាមរយៈ <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"កាមេរ៉ា"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ទីតាំង"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"មីក្រូហ្វូន"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index e08a296..c920cc2 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ಪ್ರಾರಂಭಿಸಿ"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ನಿಲ್ಲಿಸಿ"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"ಮುಂದುವರಿಯಲು, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ಗೆ ನಿಮ್ಮ ಸಾಧನದ ಮೈಕ್ರೋಫೋನ್‌ನ ಪ್ರವೇಶದ ಅಗತ್ಯವಿದೆ."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"ಮುಂದುವರಿಯಲು, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ಗೆ ನಿಮ್ಮ ಸಾಧನದ ಕ್ಯಾಮರಾದ ಪ್ರವೇಶದ ಅಗತ್ಯವಿದೆ."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"ಸಾಧನ"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಬದಲಿಸಲು ತ್ವರಿತವಾಗಿ ಬಲಕ್ಕೆ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"ತೆರೆಯಲು ಮತ್ತೆ ಟ್ಯಾಪ್‌ ಮಾಡಿ"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"ತೆರೆಯಲು ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲು ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC ಬಳಸಲು ಅನ್‌ಲಾಕ್ ಮಾಡಿ"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ಈ ಸಾಧನವು ನಿಮ್ಮ ಸಂಸ್ಥೆಗೆ ಸೇರಿದೆ"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ಈ ಸಾಧನವು <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> ಗೆ ಸೇರಿದೆ"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ಫೋನ್‌ಗಾಗಿ ಐಕಾನ್‌ನಿಂದ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ನಿಮ್ಮ <xliff:g id="TYPES_LIST">%s</xliff:g> ಅನ್ನು ಆ್ಯಪ್‌ಗಳು ಬಳಸುತ್ತಿವೆ."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ಮತ್ತು "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>, <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ಅನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ಇತ್ತೀಚೆಗೆ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ಅನ್ನು ಬಳಸಿದೆ"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(ಎಂಟರ್‌ಪ್ರೈಸ್)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"ಫೋನ್ ಕರೆ"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> ಮೂಲಕ)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"ಕ್ಯಾಮರಾ"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ಸ್ಥಳ"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"ಮೈಕ್ರೋಫೋನ್‌"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 2b6e2d5..362c0bc 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"화면 녹화"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"시작"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"중지"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"계속하려면 &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;에서 기기 마이크에 액세스해야 합니다."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"계속하려면 &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;에서 기기 카메라에 액세스해야 합니다."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"기기"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"위로 스와이프하여 앱 전환"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"앱을 빠르게 전환하려면 오른쪽으로 드래그"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"다시 탭하여 열기"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"위로 스와이프하여 열기"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"위로 스와이프하여 다시 시도해 주세요"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"잠금 해제하여 NFC 사용"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"내 조직에 속한 기기입니다."</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>에 속한 기기입니다."</string>
     <string name="phone_hint" msgid="6682125338461375925">"전화 기능을 사용하려면 아이콘에서 스와이프하세요."</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"애플리케이션이 <xliff:g id="TYPES_LIST">%s</xliff:g>을(를) 사용 중입니다."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" 및 "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>에서 <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> 사용 중"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>에서 최근에 <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>을(를) 사용함"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(기업용)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"전화 통화"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> 사용)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"카메라"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"위치"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"마이크"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index ffb66f9..fe69443 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -418,10 +418,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Экранды жаздыруу"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Баштадык"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Токтотуу"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Улантуу үчүн &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; колдонмосуна түзмөгүңүздүн микрофонун пайдаланууга уруксат беришиңиз керек."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Улантуу үчүн &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; колдонмосуна түзмөгүңүздүн камерасын пайдаланууга уруксат беришиңиз керек."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Түзмөк"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Башка колдонмого которулуу үчүн,, өйдө сүрүңүз"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Колдонмолорду тез которуштуруу үчүн, оңго сүйрөңүз"</string>
@@ -444,8 +442,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Ачуу үчүн кайра таптап коюңуз"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Ачуу үчүн өйдө сүрүңүз"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Кайталоо үчүн экранды өйдө сүрүңүз"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC технологиясын колдонуу үчүн кулпуcун ачыңыз"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Бул түзмөк уюмуңузга таандык"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Бул түзмөк төмөнкүгө таандык: <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Сүрөтчөнү серпип телефонго өтүңүз"</string>
@@ -972,6 +969,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Колдонмолор төмөнкүлөрдү пайдаланып жатышат: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" жана "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> колдонуп жатат"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Жакында <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> колдонулду"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(корпоративдик)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Телефон чалуу"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> аркылуу)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камера"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"жайгашкан жер"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"микрофон"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 6e367a1..e5e89e44 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ການບັນທຶກໜ້າຈໍ"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ເລີ່ມ"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ຢຸດ"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"ເພື່ອດຳເນີນການຕໍ່, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ຕ້ອງການສິດເຂົ້າເຖິງໄມໂຄຣໂຟນອຸປະກອນຂອງທ່ານ."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"ເພື່ອດຳເນີນການຕໍ່, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ຕ້ອງການສິດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງອຸປະກອນທ່ານ."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"ອຸປະກອນ"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ປັດຂື້ນເພື່ອສະຫຼັບແອັບ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ລາກໄປຂວາເພື່ອສະຫຼັບແອັບດ່ວນ"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"ແຕະ​ອີກ​ຄັ້ງ​ເພື່ອ​ເປີດ"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"ປັດຂຶ້ນເພື່ອເປີດ"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"ປັດຂຶ້ນເພື່ອລອງໃໝ່"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"ປົດລັອກເພື່ອໃຊ້ NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ອຸປະກອນນີ້ເປັນຂອງອົງການທ່ານ"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ອຸ​ປະ​ກອນ​ນີ້​ເປັນ​ຂອງ <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ປັດ​ຈາກ​ໄອ​ຄອນ​ສຳ​ລັບ​ໂທ​ລະ​ສັບ"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ແອັບພລິເຄຊັນກຳລັງໃຊ້ <xliff:g id="TYPES_LIST">%s</xliff:g> ຂອງທ່ານ."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ແລະ "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ຢູ່"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ໃຊ້ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ເມື່ອບໍ່ດົນມານີ້"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(ອົງກອນ)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"ໂທລະສັບ"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(ຜ່ານ <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"ກ້ອງຖ່າຍຮູບ"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ສະຖານທີ່"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"ໄມໂຄຣໂຟນ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 34275f4..a5de095 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekrano įrašas"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Pradėti"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stabdyti"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Kad būtų galima tęsti, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; reikalinga prieiga prie įrenginio mikrofono."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Kad būtų galima tęsti, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; reikalinga prieiga prie įrenginio fotoaparato."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Įrenginys"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Perbraukite aukštyn, kad perjungtumėte programas"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Vilkite į dešinę, kad greitai perjungtumėte programas"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Palieskite dar kartą, kad atidarytumėte"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Perbraukite aukštyn, kad atidarytumėte"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Jei norite bandyti dar kartą, perbraukite aukštyn"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Norėdami naudoti NFC, atrakinkite"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Šis įrenginys priklauso jūsų organizacijai"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Šis įrenginys priklauso „<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>“"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Perbraukite iš telefono piktogramos"</string>
@@ -980,6 +977,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Programos naudoja: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ir "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Programa „<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>“ naudoja: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Programa „<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>“ neseniai naudojo: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(įmonės versija)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefono skambutis"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(naud. <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"fotoaparatą"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"vietovę"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofoną"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 2d427b7..019810f 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -418,10 +418,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekrāna ierakstīšana"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Sākt"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Apturēt"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Lai turpinātu, lietotnei &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; nepieciešama piekļuve jūsu ierīces mikrofonam."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Lai turpinātu, lietotnei &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; nepieciešama piekļuve jūsu ierīces kamerai."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Ierīce"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Velciet augšup, lai pārslēgtu lietotnes"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Lai ātri pārslēgtu lietotnes, velciet pa labi"</string>
@@ -444,8 +442,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Pieskarieties vēlreiz, lai atvērtu"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Velciet augšup, lai atvērtu"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Velciet augšup, lai mēģinātu vēlreiz"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Atbloķējiet ierīci, lai izmantotu NFC."</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Šī ierīce pieder jūsu organizācijai."</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Šī ierīce pieder organizācijai <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>."</string>
     <string name="phone_hint" msgid="6682125338461375925">"Lai lietotu tālruni, velciet no ikonas"</string>
@@ -975,6 +972,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Lietojumprogrammas izmanto šādas funkcijas: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" un "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> pašlaik izmanto šādu darbību: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> nesen izmantoja šādu darbību: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(uzņēmumiem)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Tālruņa zvaniem"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(izmantojot lietotni <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"atrašanās vieta"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofons"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 17d6e10..c124eef 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Снимање екран"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Започни"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Сопри"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"За да продолжи, на &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ѝ е потребен пристап до микрофонот на уредот."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"За да продолжи, на &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ѝ е потребен пристап до камерата на уредот."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Уред"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Повлечете нагоре за да се префрлите од една на друга апликација"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Повлечете надесно за брзо префрлање меѓу апликациите"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Допрете повторно за да се отвори"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Повлечете за да отворите"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Повлечете нагоре за да се обидете повторно"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Отклучете за да користите NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Уредов е во сопственост на организацијата"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Уредов е во сопственост на <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Повлечете од иконата за телефонот"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Апликациите користат <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" и "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> користи <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> користеше <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> неодамна"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(претпријатие)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Телефонски повик"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(преку <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камера"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"локација"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"микрофон"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 916fbe1..965f4df 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"സ്‌ക്രീൻ റെക്കോർഡ്"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ആരംഭിക്കുക"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"നിര്‍ത്തുക"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"തുടരാൻ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ആപ്പിന് നിങ്ങളുടെ ഉപകരണത്തിന്റെ മൈക്രോഫോണിലേക്ക് ആക്‌സസ് നൽകേണ്ടതുണ്ട്."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"തുടരാൻ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ആപ്പിന് നിങ്ങളുടെ ഉപകരണത്തിന്റെ ക്യാമറയിലേക്ക് ആക്‌സസ് നൽകേണ്ടതുണ്ട്."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"ഉപകരണം"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ആപ്പുകൾ മാറാൻ മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ആപ്പുകൾ പെട്ടെന്ന് മാറാൻ വലത്തോട്ട് വലിച്ചിടുക"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"തുറക്കുന്നതിന് വീണ്ടും ടാപ്പുചെയ്യുക"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"തുറക്കാൻ മുകളിലോട്ട് സ്വൈപ്പ് ചെയ്യുക"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"വീണ്ടും ശ്രമിക്കാൻ മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC ഉപയോഗിക്കാൻ അൺലോക്ക് ചെയ്യുക"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ഈ ഉപകരണം നിങ്ങളുടെ സ്ഥാപനത്തിന്റേതാണ്"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ഈ ഉപകരണം <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> എന്ന സ്ഥാപനത്തിന്റേതാണ്"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ഫോൺ ഐക്കണിൽ നിന്ന് സ്വൈപ്പുചെയ്യുക"</string>
@@ -970,6 +967,16 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ആപ്പുകൾ നിങ്ങളുടെ <xliff:g id="TYPES_LIST">%s</xliff:g> ഉപയോഗിക്കുന്നു."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" കൂടാതെ "</string>
+    <!-- no translation found for ongoing_privacy_dialog_using_op (4125175620929701569) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_recent_op (4255923947334262404) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_enterprise (4082735415905550729) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_phonecall (3526223335298089311) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_attribution_text (9186683306719924646) -->
+    <skip />
     <string name="privacy_type_camera" msgid="7974051382167078332">"ക്യാമറ"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ലൊക്കേഷന്‍"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"മൈക്രോഫോൺ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 38bb386..400352b 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Дэлгэцийн бичлэг хийх"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Эхлүүлэх"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Зогсоох"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Үргэлжлүүлэхийн тулд &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; таны төхөөрөмжийн микрофонд хандах шаардлагатай."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Үргэлжлүүлэхийн тулд &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; таны төхөөрөмжийн камерт хандах шаардлагатай."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Төхөөрөмж"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Апп сэлгэхийн тулд дээш шударна уу"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Аппуудыг хурдан сэлгэхийн тулд баруун тийш чирнэ үү"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Нээхийн тулд дахин товшино уу"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Нээхийн тулд дээш шударна уу"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Дахин оролдохын тулд дээш шударна уу"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC-г ашиглахын тулд түгжээг тайлна уу"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Энэ төхөөрөмж танай байгууллагад харьяалагддаг"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Энэ төхөөрөмж <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>-д харьяалагддаг"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Утсыг гаргахын тулд дүрс тэмдгээс шудрах"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Аппууд таны <xliff:g id="TYPES_LIST">%s</xliff:g>-г ашиглаж байна."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" болон "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>-г ашиглаж байна"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>-г саяхан ашигласан"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(байгууллага)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Утасны дуудлага"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g>-р)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камер"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"байршил"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"микрофон"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 1c9cbd2..fe1c910 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -344,10 +344,8 @@
     <string name="quick_settings_ime_label" msgid="3351174938144332051">"इनपुट पद्धत"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"स्थान"</string>
     <string name="quick_settings_location_off_label" msgid="7923929131443915919">"स्थान बंद"</string>
-    <!-- no translation found for quick_settings_camera_label (1367149596242401934) -->
-    <skip />
-    <!-- no translation found for quick_settings_mic_label (8245831073612564953) -->
-    <skip />
+    <string name="quick_settings_camera_label" msgid="1367149596242401934">"कॅमेरा ब्लॉक करा"</string>
+    <string name="quick_settings_mic_label" msgid="8245831073612564953">"मायक्रोफोन म्यूट करा"</string>
     <string name="quick_settings_media_device_label" msgid="8034019242363789941">"मीडिया डिव्हाइस"</string>
     <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string>
     <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"फक्त आणीबाणीचे कॉल"</string>
@@ -418,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"स्क्रीन रेकॉर्ड"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"सुरू"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"थांबा"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"पुढे सुरू ठेवण्यासाठी, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ला तुमच्या डिव्हाइसचा मायक्रोफोन अ‍ॅक्सेस करण्याची आवश्यकता आहे."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"पुढे सुरू ठेवण्यासाठी, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ला तुमच्या डिव्हाइसचा कॅमेरा अ‍ॅक्सेस करण्याची आवश्यकता आहे."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"डिव्हाइस"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"अ‍ॅप्स स्विच करण्यासाठी वर स्वाइप करा"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"अ‍ॅप्स वर झटपट स्विच करण्यासाठी उजवीकडे ड्रॅग करा"</string>
@@ -444,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"उघडण्यासाठी पुन्हा टॅप करा"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"उघडण्यासाठी वर स्वाइप करा"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"पुन्हा प्रयत्न करण्यासाठी वर स्‍वाइप करा"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC वापरण्यासाठी स्क्रीन अनलॉक करा"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"हे डिव्हाइस तुमच्या संस्थेचे आहे"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"हे डिव्हाइस <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> चे आहे"</string>
     <string name="phone_hint" msgid="6682125338461375925">"फोनसाठी चिन्हावरून स्वाइप करा"</string>
@@ -972,6 +967,16 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ॲप्लिकेशन्स तुमचे <xliff:g id="TYPES_LIST">%s</xliff:g> वापरत आहे."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" आणि "</string>
+    <!-- no translation found for ongoing_privacy_dialog_using_op (4125175620929701569) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_recent_op (4255923947334262404) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_enterprise (4082735415905550729) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_phonecall (3526223335298089311) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_attribution_text (9186683306719924646) -->
+    <skip />
     <string name="privacy_type_camera" msgid="7974051382167078332">"कॅमेरा"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"स्थान"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"मायक्रोफोन"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 4751f40..b1d0b47 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Rakam Skrin"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Mula"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Berhenti"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Untuk meneruskan proses, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; memerlukan akses kepada mikrofon peranti anda."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Untuk meneruskan proses, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; memerlukan akses kepada kamera peranti anda."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Peranti"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Leret ke atas untuk menukar apl"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Seret ke kanan untuk beralih apl dengan pantas"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Ketik lagi untuk membuka"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Leret ke atas untuk buka"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Leret ke atas untuk mencuba lagi"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Buka kunci untuk menggunakan NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Peranti ini milik organisasi anda"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Peranti ini milik <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Leret dari ikon untuk telefon"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikasi sedang menggunakan <xliff:g id="TYPES_LIST">%s</xliff:g> anda."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" dan "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> sedang menggunakan <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> menggunakan <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> baru-baru ini"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(perusahaan)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Panggilan telefon"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(melalui <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"lokasi"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index abd2b23..42b5b23 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ဖန်သားပြင် မှတ်တမ်းတင်ရန်"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"စတင်ရန်"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ရပ်ရန်"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"ဆက်လက်လုပ်ဆောင်ရန် &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; က သင့်စက်၏ မိုက်ခရိုဖုန်းကို အသုံးပြုခွင့်ရရန် လိုအပ်သည်။"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"ဆက်လက်လုပ်ဆောင်ရန် &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; က သင့်စက်၏ ကင်မရာကို အသုံးပြုခွင့်ရရန် လိုအပ်သည်။"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"စက်"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"အက်ပ်များကို ဖွင့်ရန် အပေါ်သို့ ပွတ်ဆွဲပါ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"အက်ပ်များကို ပြောင်းရန် ညာဘက်သို့ ဖိဆွဲပါ"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"ဖွင့်ရန် ထပ်ပြီး ပုတ်ပါ"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"ဖွင့်ရန် အပေါ်သို့ပွတ်ဆွဲပါ"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"ထပ်စမ်းကြည့်ရန် အပေါ်သို့ပွတ်ဆွဲပါ"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC ကို အသုံးပြုရန် လော့ခ်ဖွင့်ပါ"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ဤစက်ကို သင့်အဖွဲ့အစည်းက ပိုင်ဆိုင်သည်"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ဤစက်ကို <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> က ပိုင်ဆိုင်သည်"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ဖုန်းအတွက် သင်္ကေတပုံအား ပွတ်ဆွဲပါ"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"အပလီကေးရှင်းများက သင်၏ <xliff:g id="TYPES_LIST">%s</xliff:g> ကို အသုံးပြုနေသည်။"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"၊ "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" နှင့် "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> က <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ကို အသုံးပြုနေသည်"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> က <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ကို မကြာသေးမီက အသုံးပြုထားသည်"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(လုပ်ငန်းသုံး)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"ဖုန်းခေါ်ဆိုမှု"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> မှတစ်ဆင့်)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"ကင်မရာ"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"တည်နေရာ"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"မိုက်ခရိုဖုန်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index e914399..ef48f8f 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Skjermopptak"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Start"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stopp"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"For å fortsette må &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ha tilgang til enhetsmikrofonen."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"For å fortsette må &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ha tilgang til enhetskameraet."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Enhet"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Sveip opp for å bytte apper"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Dra til høyre for å bytte apper raskt"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Trykk på nytt for å åpne"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Sveip opp for å åpne"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Sveip opp for å prøve igjen"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Lås opp for å bruke NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Denne enheten tilhører organisasjonen din"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Denne enheten tilhører <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Sveip ikonet for å åpne telefon"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Apper bruker <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" og "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> bruker <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> har brukt <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> nylig"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonsamtale"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(til og med <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"posisjon"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index ad087c5..2594e8c 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"स्रिनको रेकर्ड"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"सुरु गर्नुहोस्"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"रोक्नुहोस्"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"जारी राख्न &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; लाई तपाईंको यन्त्रको माइक्रोफोन प्रयोग गर्ने अनुमति दिनु पर्ने हुन्छ।"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"जारी राख्न &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; लाई तपाईंको यन्त्रको क्यामेरा प्रयोग गर्ने अनुमति दिनु पर्ने हुन्छ।"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"यन्त्र"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"एपहरू बदल्न माथितिर स्वाइप गर्नुहोस्"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"एपहरू बदल्न द्रुत गतिमा दायाँतिर ड्र्याग गर्नुहोस्"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"खोल्न पुनः ट्याप गर्नुहोस्"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"खोल्न माथितिर स्वाइप गर्नुहोस्"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"फेरि प्रयास गर्न माथितिर स्वाइप गर्नुहोस्"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC प्रयोग गर्न स्क्रिन अनलक गर्नुहोस्"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"यो यन्त्र तपाईंको सङ्गठनको स्वामित्वमा छ"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"यो यन्त्र <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> को स्वामित्वमा छ"</string>
     <string name="phone_hint" msgid="6682125338461375925">"फोनको लागि आइकनबाट स्वाइप गर्नुहोस्"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"एपहरूले तपाईंको <xliff:g id="TYPES_LIST">%s</xliff:g> प्रयोग गर्दै छन्‌।"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" र "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ले <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> प्रयोग गरिरहेको छ"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ले हालसालै <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> प्रयोग गरेको छ"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(इन्टरप्राइज)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"फोन कल"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> मार्फत)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"क्यामेरा"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"स्थान"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"माइक्रोफोन"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 614d374..0274132 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Schermopname"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starten"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stoppen"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; heeft toegang tot de microfoon van je apparaat nodig om door te gaan."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; heeft toegang tot de camera van je apparaat nodig om door te gaan."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Apparaat"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swipe omhoog om te schakelen tussen apps"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Sleep naar rechts om snel tussen apps te schakelen"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Tik nog eens om te openen"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Swipe omhoog om te openen"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Swipe omhoog om het opnieuw te proberen"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Ontgrendel het apparaat om NFC te gebruiken"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Dit apparaat is eigendom van je organisatie"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Dit apparaat is eigendom van <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Swipen voor telefoon"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Apps gebruiken je <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" en "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> gebruikt de <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> heeft de <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> recent gebruikt"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(zakelijke versie)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefoongesprek"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(via <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"camera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"locatie"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microfoon"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 97af29b..16d8a296 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ସ୍କ୍ରିନ୍ ରେକର୍ଡ"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ଆରମ୍ଭ କରନ୍ତୁ"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ବନ୍ଦ କରନ୍ତୁ"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"ଜାରି ରଖିବାକୁ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ଆପଣଙ୍କ ଡିଭାଇସର ମାଇକ୍ରୋଫୋନକୁ ଆକ୍ସେସ୍ ଆବଶ୍ୟକ କରେ।"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"ଜାରି ରଖିବାକୁ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ଆପଣଙ୍କ ଡିଭାଇସର କ୍ୟାମେରାକୁ ଆକ୍ସେସ୍ ଆବଶ୍ୟକ କରେ।"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"ଡିଭାଇସ୍"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ଆପ୍‌କୁ ବଦଳ କରିବା ପାଇଁ ସ୍ଵାଇପ୍ କରନ୍ତୁ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ଆପ୍‌ଗୁଡ଼ିକ ମଧ୍ୟରେ ଶୀଘ୍ର ବଦଳ କରିବା ପାଇଁ ଡାହାଣକୁ ଡ୍ରାଗ୍ କରନ୍ତୁ"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"ଖୋଲିବା ପାଇଁ ପୁଣି ଟାପ୍‍ କରନ୍ତୁ"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"ଖୋଲିବା ପାଇଁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"ପୁଣି ଚେଷ୍ଟା କରିବା ପାଇଁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC ବ୍ୟବହାର କରିବାକୁ ଅନଲକ୍ କରନ୍ତୁ"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ଏହି ଡିଭାଇସଟି ଆପଣଙ୍କ ସଂସ୍ଥାର ଅଟେ"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ଏହି ଡିଭାଇସଟି <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>ର ଅଟେ"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ଫୋନ୍‍ ପାଇଁ ଆଇକନରୁ ସ୍ୱାଇପ୍‍ କରନ୍ତୁ"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ଆପ୍ଲିକେସନ୍‍ଗୁଡିକ ଆପଣଙ୍କ <xliff:g id="TYPES_LIST">%s</xliff:g> ବ୍ୟବହାର କରୁଛନ୍ତି।"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ଏବଂ "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ବ୍ୟବହାର କରୁଛି"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ଏବେ <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ବ୍ୟବହାର କରିଛି"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(ଏଣ୍ଟରପ୍ରାଇଜ୍)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"ଫୋନକଲ୍"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> ମାଧ୍ୟମରେ)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"କ୍ୟାମେରା"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ଲୋକେସନ୍‍"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"ମାଇକ୍ରୋଫୋନ୍"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index e7ffe49..d33455d 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -344,10 +344,8 @@
     <string name="quick_settings_ime_label" msgid="3351174938144332051">"ਇਨਪੁੱਟ ਵਿਧੀ"</string>
     <string name="quick_settings_location_label" msgid="2621868789013389163">"ਟਿਕਾਣਾ"</string>
     <string name="quick_settings_location_off_label" msgid="7923929131443915919">"ਨਿਰਧਾਰਿਤ ਸਥਾਨ ਬੰਦ"</string>
-    <!-- no translation found for quick_settings_camera_label (1367149596242401934) -->
-    <skip />
-    <!-- no translation found for quick_settings_mic_label (8245831073612564953) -->
-    <skip />
+    <string name="quick_settings_camera_label" msgid="1367149596242401934">"ਕੈਮਰਾ ਬਲਾਕ ਕਰੋ"</string>
+    <string name="quick_settings_mic_label" msgid="8245831073612564953">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਮਿਊਟ ਕਰੋ"</string>
     <string name="quick_settings_media_device_label" msgid="8034019242363789941">"ਮੀਡੀਆ ਡੀਵਾਈਸ"</string>
     <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string>
     <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"ਸਿਰਫ਼ ਸੰਕਟਕਾਲੀਨ ਕਾਲਾਂ"</string>
@@ -418,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਰ"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ਸ਼ੁਰੂ ਕਰੋ"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ਰੋਕੋ"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"ਜਾਰੀ ਰੱਖਣ ਲਈ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ਨੂੰ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਤੱਕ ਪਹੁੰਚ ਦੀ ਲੋੜ ਹੈ।"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"ਜਾਰੀ ਰੱਖਣ ਲਈ, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ਨੂੰ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੇ ਕੈਮਰਾ ਤੱਕ ਪਹੁੰਚ ਦੀ ਲੋੜ ਹੈ।"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"ਡੀਵਾਈਸ"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ਐਪਾਂ ਵਿਚਾਲੇ ਤੇਜ਼ੀ ਨਾਲ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਸੱਜੇ ਪਾਸੇ ਵੱਲ ਘਸੀਟੋ"</string>
@@ -444,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"ਖੋਲ੍ਹਣ ਲਈ ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"ਖੋਲ੍ਹਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਉੱਤੇ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC ਵਰਤਣ ਲਈ ਅਣਲਾਕ ਕਰੋ"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ਇਹ ਡੀਵਾਈਸ ਤੁਹਾਡੀ ਸੰਸਥਾ ਨਾਲ ਸੰਬੰਧਿਤ ਹੈ"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ਇਹ ਡੀਵਾਈਸ <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> ਨਾਲ ਸੰਬੰਧਿਤ ਹੈ"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ਫ਼ੋਨ ਲਈ ਪ੍ਰਤੀਕ ਤੋਂ ਸਵਾਈਪ ਕਰੋ"</string>
@@ -972,6 +967,16 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ਐਪਲੀਕੇਸ਼ਨਾਂ ਤੁਹਾਡੇ <xliff:g id="TYPES_LIST">%s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀਆਂ ਹਨ।"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ਅਤੇ "</string>
+    <!-- no translation found for ongoing_privacy_dialog_using_op (4125175620929701569) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_recent_op (4255923947334262404) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_enterprise (4082735415905550729) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_phonecall (3526223335298089311) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_attribution_text (9186683306719924646) -->
+    <skip />
     <string name="privacy_type_camera" msgid="7974051382167078332">"ਕੈਮਰਾ"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ਟਿਕਾਣਾ"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 0275626..6440a65 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Nagrywanie ekranu"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Rozpocznij"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zatrzymaj"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Aby kontynuować, musisz przyznać aplikacji „<xliff:g id="APP">%s</xliff:g>” dostęp do mikrofonu urządzenia."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Aby kontynuować, musisz przyznać aplikacji „<xliff:g id="APP">%s</xliff:g>” dostęp do aparatu urządzenia."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Urządzenie"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Przesuń w górę, by przełączyć aplikacje"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Szybko przeciągnij w prawo, by przełączyć aplikacje"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Kliknij ponownie, by otworzyć"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Przesuń w górę, by otworzyć"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Przesuń w górę, by spróbować ponownie"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Odblokuj, by użyć NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"To urządzenie należy do Twojej organizacji"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Właściciel tego urządzenia: <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Aby włączyć telefon, przesuń palcem od ikony"</string>
@@ -980,6 +977,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikacje używają: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" i "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Aplikacja <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> używa: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Aplikacja <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> używała ostatnio: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(wersja firmowa)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Rozmowa telefoniczna"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(przez: <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"aparat"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"lokalizacja"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index b629b64..40dc2de 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplicativos estão usando <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" e "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"O app <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> está usando <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"O app <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> usou <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> recentemente"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(empresarial)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Chamada telefônica"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(pelo app <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"câmera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"localização"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microfone"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 43933a4..22241f8 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"As aplicações estão a utilizar o(a) <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" e "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"A app <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> está a utilizar a app <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>."</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Recentemente, a app <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> utilizou a app <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>."</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(empresarial)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Chamada telefónica"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(através de <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"câmara"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"localização"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microfone"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index b629b64..40dc2de 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -967,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplicativos estão usando <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" e "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"O app <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> está usando <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"O app <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> usou <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> recentemente"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(empresarial)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Chamada telefônica"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(pelo app <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"câmera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"localização"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microfone"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 5197de3..f32453b9 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -418,10 +418,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Înregistrarea ecranului"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Începeți"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Opriți"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Pentru a continua, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necesită acces la microfonul dispozitivului."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Pentru a continua, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; necesită acces la camera dispozitivului."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Dispozitiv"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Glisați în sus pentru a comuta între aplicații"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Glisați la dreapta pentru a comuta rapid între aplicații"</string>
@@ -444,8 +442,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Atingeți din nou pentru a deschide"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Glisați în sus pentru a deschide"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Glisați pentru a încerca din nou"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Deblocați pentru a folosi NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Dispozitivul aparține organizației dvs."</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Acest dispozitiv aparține organizației <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Glisați dinspre telefon"</string>
@@ -975,6 +972,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplicațiile folosesc <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" și "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> folosește <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> a folosit recent <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(prin <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"cameră foto"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"locație"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"microfon"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 17221c9..40fb258 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Запись экрана"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Начать"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Остановить"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Чтобы продолжить, предоставьте приложению &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ к микрофону устройства."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Чтобы продолжить, предоставьте приложению &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ к камере устройства."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Устройство"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Чтобы переключиться между приложениями, проведите по экрану вверх."</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Перетащите вправо, чтобы быстро переключиться между приложениями"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Нажмите ещё раз, чтобы открыть"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Проведите вверх, чтобы открыть"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Чтобы повторить попытку, проведите вверх"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Чтобы использовать NFC, разблокируйте устройство."</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Это устройство принадлежит вашей организации"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Этим устройством владеет организация \"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>\""</string>
     <string name="phone_hint" msgid="6682125338461375925">"Телефон: проведите от значка"</string>
@@ -980,6 +977,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"В приложениях используется <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" и "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Приложение \"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>\" использует другое (<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>)."</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Приложение \"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>\" недавно использовало другое (<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>)."</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(корпоративная версия)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Приложение для звонков"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(через приложение \"<xliff:g id="ATTRIBUTION">%s</xliff:g>\")"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камера"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"местоположение"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"микрофон"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 20bc150..f3d0409 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"තිර පටිගත කිරීම"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ආරම්භ කරන්න"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"නතර කරන්න"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"දිගටම කර ගෙන යාමට, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; හට ඔබගේ උපාංගයෙහි මයික්‍රෆෝනයට ප්‍රවේශය අවශ්‍යයි."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"දිගටම කර ගෙන යාමට, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; හට ඔබගේ උපාංගයෙහි කැමරාවට ප්‍රවේශය අවශ්‍යයි."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"උපාංගය"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"යෙදුම් මාරු කිරීමට ස්වයිප් කරන්න"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ඉක්මනින් යෙදුම් මාරු කිරීමට දකුණට අදින්න"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"විවෘත කිරීමට නැවත තට්ටු කරන්න"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"විවෘත කිරීමට ස්වයිප් කරන්න"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"නැවත උත්සාහ කිරීමට ඉහළට ස්වයිප් කරන්න"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC භාවිත කිරීමට අගුලු හරින්න"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"මෙම උපාංගය ඔබේ සංවිධානයට අයිතිය"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"මෙම උපාංගය <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> සංවිධානයට අයිතිය"</string>
     <string name="phone_hint" msgid="6682125338461375925">"දුරකථනය සඳහා නිරූපකය වෙතින් ස්වයිප් කරන්න"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"යෙදුම් ඔබේ <xliff:g id="TYPES_LIST">%s</xliff:g> භාවිත කරමින් සිටී."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" සහ "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> භාවිත කරමින් ඇත"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> මෑතකදී <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> භාවිත කළේය"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(ව්‍යවසාය)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"දුරකථන ඇමතුම"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> හරහා)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"කැමරාව"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ස්ථානය"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"මයික්‍රෝෆෝනය"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 4984f0f..7e3f3c0 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Záznam obrazovky"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Začať"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ukončiť"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Ak chcete pokračovať, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; požaduje prístup k mikrofónu zariadenia."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Ak chcete pokračovať, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; požaduje prístup k fotoaparátu zariadenia."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Zariadenie"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Potiahnutím nahor prepnete aplikácie"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Presunutím doprava rýchlo prepnete aplikácie"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Upozornenie otvoríte opätovným klepnutím"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Otvorte potiahnutím prstom nahor"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Potiahnutím nahor to skúste znova"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Ak chcete použiť NFC, odomknite"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Toto zariadenie patrí vašej organizácii"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Toto zariadení patrí organizácii <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Telefón otvoríte prejdením prstom od ikony"</string>
@@ -980,6 +977,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikácie používajú zoznam <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" a "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> používa aplikáciu <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Aplikácia <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> použila nedávno aplikáciu <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(podniková verzia)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonický hovor"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(prostredníctvom aplikácie <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"fotoaparát"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"poloha"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofón"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index ffc6c66..b7ebea9 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Snemanje zaslona"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Začni"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ustavi"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Za nadaljevanje potrebuje aplikacija &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; dostop do mikrofona v napravi."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Za nadaljevanje potrebuje aplikacija &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; dostop do fotoaparata v napravi."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Naprava"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Za preklop aplikacij povlecite navzgor"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Povlecite v desno za hiter preklop med aplikacijami"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Znova se dotaknite, da odprete"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Povlecite navzgor, da odprete"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Povlecite navzgor za vnovičen poskus"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Odklenite napravo, če želite uporabljati vmesnik NFC."</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Ta naprava pripada vaši organizaciji"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Ta naprava pripada organizaciji <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Povlecite z ikone za telefon"</string>
@@ -980,6 +977,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikacije uporabljajo <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" in "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Aplikacija <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> uporablja: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Aplikacija <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> je nedavno uporabila: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(za podjetja)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonski klici"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(prek aplikacije <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"fotoaparat"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"lokacijo"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 21ac1e0..fede55c 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Regjistrimi i ekranit"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Nis"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ndalo"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Për të vazhduar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ka nevojë të qaset në mikrofonin e pajisjes sate."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Për të vazhduar, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ka nevojë të qaset në kamerën e pajisjes sate."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Pajisja"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Rrëshqit shpejt lart për të ndërruar aplikacionet"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Zvarrit djathtas për të ndërruar aplikacionet me shpejtësi"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Trokit përsëri për ta hapur"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Rrëshqit lart për ta hapur"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Rrëshqit lart për të provuar përsëri"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Shkyçe për të përdorur NFC-në"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Kjo pajisje i përket organizatës sate"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Kjo pajisje i përket <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Rrëshqit për të hapur telefonin"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikacionet po përdorin <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" dhe "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> po përdor <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ka përdorur <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> së fundi"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(ndërmarrje)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Phonecall"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(nëpërmjet <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamerën"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"vendndodhjen"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofonin"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index e154cc5..0d82246 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -418,10 +418,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Снимак екрана"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Почните"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Зауставите"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; захтева приступ микрофону уређаја ради настављања."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; захтева приступ камери уређаја ради настављања."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Уређај"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Превуците нагоре да бисте мењали апликације"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Превуците удесно да бисте брзо променили апликације"</string>
@@ -444,8 +442,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Додирните поново да бисте отворили"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Превуците нагоре да бисте отворили"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Превуците нагоре да бисте пробали поново"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Откључајте да бисте користили NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Овај уређај припада организацији"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Овај уређај припада организацији <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Превуците од иконе за телефон"</string>
@@ -975,6 +972,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Апликације користе <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" и "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Апликација <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> користи: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Апликација <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> је недавно користила: <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(за предузећа)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Телефонски позив"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(преко: <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камеру"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"локацију"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"микрофон"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 66ae227..969bdcd 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Skärminspelning"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starta"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stoppa"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; behöver behörighet till enhetens mikrofon för att fortsätta."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; behöver behörighet till enhetens kamera för att fortsätta."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Enhet"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Byt appar genom att svepa uppåt"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Tryck och dra åt höger för att snabbt byta mellan appar"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Tryck igen för att öppna"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Öppna genom att svepa uppåt"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Svep uppåt om du vill försöka igen"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Lås upp om du vill använda NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Den här enheten tillhör organisationen"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Den här enheten tillhör <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Svep från ikonen och öppna telefonen"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"<xliff:g id="TYPES_LIST">%s</xliff:g> används av appar."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" och "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> använder <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> använde <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> nyligen"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(företag)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefonsamtal"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(genom <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"plats"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 26e6b8c..4055e8d 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Rekodi ya Skrini"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Anza kurekodi"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Acha kurekodi"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Ili uendelee, &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; inahitaji ruhusa ya kufikia maikrofoni ya kifaa chako."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Ili uendelee, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; inahitaji ruhusa ya kufikia kamera ya kifaa chako."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Kifaa"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Telezesha kidole juu ili ubadilishe programu"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Buruta kulia ili ubadilishe programu haraka"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Gusa tena ili ufungue"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Telezesha kidole juu ili ufungue"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Telezesha kidole juu ili ujaribu tena"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Fungua ili utumie NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Kifaa hiki kinamilikiwa na shirika lako"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Kifaa hiki kinamilikiwa na <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Telezesha kidole kutoka kwa aikoni ili ufikie simu"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Programu zinatumia <xliff:g id="TYPES_LIST">%s</xliff:g> yako."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" na "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> inatumia <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ilitumia <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> hivi majuzi"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(biashara)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Simu"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(kupitia <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"mahali"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"maikrofoni"</string>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index 6dff2e3..a6321fe 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -19,4 +19,7 @@
 
     <!-- Max number of columns for quick controls area -->
     <integer name="controls_max_columns">2</integer>
+
+    <!-- Whether to use the split 2-column notification shade -->
+    <bool name="config_use_split_notification_shade">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 132713b..cbac87e 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ஸ்கிரீன் ரெக்கார்டு"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"தொடங்கு"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"நிறுத்து"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"தொடர, உங்கள் சாதனத்தின் மைக்ரோஃபோனை அணுகுவதற்கு &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ஆப்ஸுக்கு அனுமதி வேண்டும்."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"தொடர, உங்கள் சாதனத்தின் கேமராவை அணுகுவதற்கு &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ஆப்ஸுக்கு அனுமதி வேண்டும்."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"சாதனம்"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ஆப்ஸிற்கு இடையே மாற்றுவதற்கு, மேல்நோக்கி ஸ்வைப் செய்க"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ஆப்ஸை வேகமாக மாற்ற, வலப்புறம் இழுக்கவும்"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"திறக்க, மீண்டும் தட்டவும்"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"திறப்பதற்கு மேல் நோக்கி ஸ்வைப் செய்யவும்"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"மீண்டும் முயல மேல்நோக்கி ஸ்வைப் செய்யவும்"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFCயைப் பயன்படுத்த அன்லாக் செய்யவும்"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"இந்த சாதனம் உங்கள் நிறுவனத்துக்கு சொந்தமானது"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"இந்த சாதனம் <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> நிறுவனத்துக்கு சொந்தமானது"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ஃபோனிற்கு ஐகானிலிருந்து ஸ்வைப் செய்யவும்"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"உங்கள் <xliff:g id="TYPES_LIST">%s</xliff:g> ஆகியவற்றை ஆப்ஸ் பயன்படுத்துகின்றன."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" மற்றும் "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ஆப்ஸ் <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> பயன்படுத்துகிறது"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"சமீபத்தில் <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ஆப்ஸ் <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> பயன்படுத்தியுள்ளது"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(நிறுவனப் பதிப்பு)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"மொபைல் அழைப்பு"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> மூலம்)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"கேமரா"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"இருப்பிடம்"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"மைக்ரோஃபோன்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 13758d9..45ef339 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"స్క్రీన్ రికార్డ్"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ప్రారంభించు"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ఆపు"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"కొనసాగించడానికి, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;కు మీ పరికరం యొక్క మైక్రోఫోన్ యాక్సెస్ అవసరం."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"కొనసాగించడానికి, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&amp;gtకు మీ పరికరం యొక్క కెమెరా యాక్సెస్ అవసరం."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"పరికరం"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"యాప్‌లను మార్చడం కోసం ఎగువకు స్వైప్ చేయండి"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"యాప్‌లను శీఘ్రంగా స్విచ్ చేయడానికి కుడి వైపుకు లాగండి"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"తెరవడానికి మళ్లీ నొక్కండి"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"తెరవడానికి, పైకి స్వైప్ చేయండి"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"మళ్ళీ ప్రయత్నించడానికి పైకి స్వైప్ చేయండి"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFCను ఉపయోగించడానికి అన్‌లాక్ చేయండి"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"ఈ పరికరం మీ సంస్థకు చెందినది"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"ఈ పరికరం <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>కు చెందినది"</string>
     <string name="phone_hint" msgid="6682125338461375925">"ఫోన్ కోసం చిహ్నాన్ని స్వైప్ చేయండి"</string>
@@ -970,6 +967,16 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"అప్లికేషన్‌లు మీ <xliff:g id="TYPES_LIST">%s</xliff:g>ని ఉపయోగిస్తున్నాయి."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" మరియు "</string>
+    <!-- no translation found for ongoing_privacy_dialog_using_op (4125175620929701569) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_recent_op (4255923947334262404) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_enterprise (4082735415905550729) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_phonecall (3526223335298089311) -->
+    <skip />
+    <!-- no translation found for ongoing_privacy_dialog_attribution_text (9186683306719924646) -->
+    <skip />
     <string name="privacy_type_camera" msgid="7974051382167078332">"కెమెరా"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"లొకేషన్"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"మైక్రోఫోన్"</string>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index dec83d9..722f148 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -39,7 +39,6 @@
         <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
         <item>@string/config_systemUIVendorServiceComponent</item>
         <item>com.android.systemui.SliceBroadcastRelayHandler</item>
-        <item>com.android.systemui.SizeCompatModeActivityController</item>
         <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
         <item>com.android.systemui.toast.ToastUI</item>
         <item>com.android.systemui.wmshell.WMShell</item>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 5c08293..8b197ac 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"บันทึกหน้าจอ"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"เริ่ม"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"หยุด"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ต้องได้รับสิทธิ์เข้าถึงไมโครโฟนของอุปกรณ์เพื่อดำเนินการต่อ"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ต้องได้รับสิทธิ์เข้าถึงกล้องของอุปกรณ์เพื่อดำเนินการต่อ"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"อุปกรณ์"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"เลื่อนขึ้นเพื่อสลับแอป"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ลากไปทางขวาเพื่อสลับแอปอย่างรวดเร็ว"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"แตะอีกครั้งเพื่อเปิด"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"เลื่อนขึ้นเพื่อเปิด"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"เลื่อนขึ้นเพื่อลองอีกครั้ง"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"ปลดล็อกเพื่อใช้ NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"องค์กรของคุณเป็นเจ้าของอุปกรณ์นี้"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g> เป็นเจ้าของอุปกรณ์นี้"</string>
     <string name="phone_hint" msgid="6682125338461375925">"เลื่อนไอคอนโทรศัพท์"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"หลายแอปพลิเคชันใช้<xliff:g id="TYPES_LIST">%s</xliff:g>ของคุณอยู่"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" และ "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> กำลังใช้<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ใช้<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>เมื่อเร็วๆ นี้"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(องค์กร)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"โทรศัพท์"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(ผ่านทาง <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"กล้องถ่ายรูป"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"ตำแหน่ง"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"ไมโครโฟน"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 9f32232..a4a768b 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Pag-record ng Screen"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Magsimula"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ihinto"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Para magpatuloy, kailangan ng &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ng access sa mikropono ng iyong device."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Para magpatuloy, kailangan ng &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; ng access sa camera ng iyong device."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Device"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Mag-swipe pataas upang lumipat ng app"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"I-drag pakanan para mabilisang magpalipat-lipat ng app"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"I-tap ulit upang buksan"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Mag-swipe pataas para buksan"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Mag-swipe pataas para subukan ulit"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"I-unlock para magamit ang NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Pagmamay-ari ng iyong organisasyon ang device na ito"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Pagmamay-ari ng <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> ang device na ito"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Mag-swipe mula sa icon para sa telepono"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Ginagamit ng mga application ang iyong <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" at "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Ginagamit ng <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ang <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Kamakailang ginamit ng <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> ang <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(enterprise)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Tawag sa telepono"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(sa pamamagitan ng <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"camera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"lokasyon"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikropono"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 8c1016d..3040ea0 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekran Kaydı"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Başlat"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Durdur"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Devam etmek için &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; uygulamasının cihazınızın mikrofonuna erişmesi gerekiyor."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Devam etmek için &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; uygulamasının cihazınızın kamerasına erişmesi gerekiyor."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Cihaz"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Uygulamalar arasında geçiş yapmak için yukarı kaydırın"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Uygulamaları hızlıca değiştirmek için sağa kaydırın"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Açmak için tekrar dokunun"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Açmak için yukarı kaydırın"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Tekrar denemek için yukarı kaydırın"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC\'yi kullanmak için kilidi açın"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Bu cihaz, kuruluşunuza ait"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Bu cihaz <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> adlı kuruluşa ait"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Telefon için, simgeden hızlıca kaydırın"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Uygulamalar şunları kullanıyor: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" ve "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>, <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> uygulamasını kullanıyor"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>, yakın zamanda <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> uygulamasını kullandı"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(kurumsal)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefon çağrısı"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> aracılığıyla)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"konum"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 45d4ad9..b6b7cee 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -420,10 +420,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Запис екрана"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Почати"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Зупинити"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Щоб продовжити, надайте додатку &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ до мікрофона пристрою."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Щоб продовжити, надайте додатку &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; доступ до камери пристрою."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Пристрій"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Проводьте пальцем угору, щоб переходити між додатками"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Перетягуйте праворуч, щоб швидко переходити між додатками"</string>
@@ -446,8 +444,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Торкніться знову, щоб відкрити"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Проведіть пальцем угору, щоб відкрити"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Проведіть пальцем угору, щоб повторити спробу"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Розблокуйте екран, щоб скористатись NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Цей пристрій належить вашій організації"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Цей пристрій належить організації \"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>\""</string>
     <string name="phone_hint" msgid="6682125338461375925">"Телефон: проведіть пальцем від значка"</string>
@@ -980,6 +977,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Додатки використовують <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" і "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"Додаток <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> використовує функцію \"<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>\""</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Додаток <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> нещодавно використав функцію \"<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>\""</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(корпоративний додаток)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Додаток для телефонних дзвінків"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(через <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"камеру"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"місце"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"мікрофон"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 0216797..15c9607 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"اسکرین ریکارڈر کریں"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"آغاز"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"روکیں"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"‏جاری رکھنے کیلئے ‎&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;‎ کو آپ کے آلے کے مائیکروفون تک رسائی درکار ہے۔"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"‏جاری رکھنے کیلئے ‎&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;‎ کو آپ کے آلے کے کیمرے تک رسائی درکار ہے۔"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"آلہ"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ایپس سوئچ کرنے کیلئے اوپر سوائپ کریں"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"تیزی سے ایپس کو سوئچ کرنے کے لیے دائیں طرف گھسیٹیں"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"کھولنے کیلئے دوبارہ تھپتھپائیں"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"کھولنے کے لیے اوپر سوائپ کريں"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"دوبارہ کوشش کرنے کے لیے اوپر سوائپ کريں"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"‏NFC استعمال کرنے کیلئے غیر مقفل کریں"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"یہ آلہ آپ کی تنظیم کا ہے"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"یہ آلہ <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> کا ہے"</string>
     <string name="phone_hint" msgid="6682125338461375925">"فون کیلئے آئیکن سے سوائپ کریں"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"ایپلیکیشنز آپ کی <xliff:g id="TYPES_LIST">%s</xliff:g> کا استعمال کر رہی ہیں۔"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"، "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" اور "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> کا استعمال کر رہی ہے"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> نے حال ہی میں <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> کا استعمال کیا"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(انٹرپرائز)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"فون کال"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> کے ذریعے)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"کیمرا"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"مقام"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"مائیکروفون"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e9bc22a..3044a98 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -345,7 +345,7 @@
     <string name="quick_settings_location_label" msgid="2621868789013389163">"Joylashuv"</string>
     <string name="quick_settings_location_off_label" msgid="7923929131443915919">"Joylashuvni aniqlash xizmati yoqilmagan"</string>
     <string name="quick_settings_camera_label" msgid="1367149596242401934">"Kamerani bloklash"</string>
-    <string name="quick_settings_mic_label" msgid="8245831073612564953">"Mikrofonni oʻchirish"</string>
+    <string name="quick_settings_mic_label" msgid="8245831073612564953">"Mikrofonni ovozsiz qilish"</string>
     <string name="quick_settings_media_device_label" msgid="8034019242363789941">"Media qurilma"</string>
     <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string>
     <string name="quick_settings_rssi_emergency_only" msgid="7499207215265078598">"Favqulodda chaqiruvlar"</string>
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekranni yozib olish"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Boshlash"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Toʻxtatish"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Davom etish uchun &lt;b&gt; <xliff:g id="APP">%s</xliff:g>&lt;/b&gt; mikrofoningizdan foydalanishi kerak."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Davom etish uchun &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; qurilmangiz kamerasiga kirishi kerak."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Qurilma"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Ilovalarni almashtirish uchun ekranni tepaga suring"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Ilovalarni tezkor almashtirish uchun o‘ngga torting"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Ochish uchun yana bosing"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Ochish uchun tepaga suring"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Qayta urinish uchun tepaga suring"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"NFC ishlatish uchun qurilma qulfini oching"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Bu qurilma tashkilotingizga tegishli"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Bu qurilma <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> tashkilotiga tegishli"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Telefonni ochish uchun suring"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Ilovalarda ishlatilmoqda: <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" va "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> hozir <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ishlatmoqda"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> yaqinda <xliff:g id="APP_OPP_NAME">%2$s</xliff:g> ishlatgan"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(korporativ)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Telefon chaqiruvi"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(<xliff:g id="ATTRIBUTION">%s</xliff:g> orqali)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"kamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"joylashuv"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"mikrofon"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index e62a33b..4913be4 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ghi lại nội dung trên màn hình"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Bắt đầu"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Dừng"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Để tiếp tục, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; cần quyền truy cập vào micrô trên thiết bị của bạn."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Để tiếp tục, &lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; cần quyền truy cập vào máy ảnh trên thiết bị của bạn."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Thiết bị"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Vuốt lên để chuyển đổi ứng dụng"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Kéo sang phải để chuyển đổi nhanh giữa các ứng dụng"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Nhấn lại để mở"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Vuốt lên để mở"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Vuốt lên để thử lại"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Mở khóa để sử dụng NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Thiết bị này thuộc về tổ chức của bạn"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Thiết bị này thuộc về <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Vuốt từ biểu tượng để mở điện thoại"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Các ứng dụng đang dùng <xliff:g id="TYPES_LIST">%s</xliff:g> của bạn."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" và "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> đang sử dụng <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"Gần đây, <xliff:g id="APPLICATION_NAME">%1$s</xliff:g> đã sử dụng <xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(doanh nghiệp)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Gọi điện thoại"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(thông qua <xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"máy ảnh"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"vị trí"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"micrô"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 050eea5..3479d00 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"屏幕录制"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"开始"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"停止"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"如要继续操作,请向&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;授予设备的麦克风使用权。"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"如要继续操作,请向&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt;授予设备的相机使用权。"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"设备"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑动可切换应用"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖动可快速切换应用"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"再次点按即可打开"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"向上滑动即可打开"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"向上滑动即可重试"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"需要解锁才能使用 NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"此设备归贵单位所有"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"此设备归<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>所有"</string>
     <string name="phone_hint" msgid="6682125338461375925">"滑动图标即可拨打电话"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"有多个应用正在使用您的<xliff:g id="TYPES_LIST">%s</xliff:g>。"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"、 "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" 和 "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>正在使用<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>最近曾使用<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(企业版)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"电话"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(通过<xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"相机"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"位置信息"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"麦克风"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 15709f7..314b728 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"畫面錄影"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"開始"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"停"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"如要繼續,&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; 需要裝置的麥克風存取權。"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"如要繼續,&lt;b&gt;<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; 需要裝置的相機存取權。"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"裝置"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑動即可切換應用程式"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖曳即可快速切換應用程式"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"再次輕按即可開啟"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"向上滑動即可開啟"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"請向上滑動以再試一次"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"解鎖以使用 NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"此裝置屬於您的機構"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"此裝置屬於「<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>」"</string>
     <string name="phone_hint" msgid="6682125338461375925">"從圖示滑動即可使用手機功能"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"有多個應用程式正在使用<xliff:g id="TYPES_LIST">%s</xliff:g>。"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"、 "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" 和 "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"「<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>」正在使用<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"「<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>」最近曾使用<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(企業版本)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"電話"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(透過「<xliff:g id="ATTRIBUTION">%s</xliff:g>」)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"相機"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"位置"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"麥克風"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 90cf0b1..68b631f 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"螢幕畫面錄製"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"開始"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"停止"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"如要繼續操作,請將裝置的麥克風存取權授予「<xliff:g id="APP">%s</xliff:g>」&lt;b&gt;&lt;/b&gt;。"</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"如要繼續操作,請將裝置的相機存取權授予「<xliff:g id="APP">%s</xliff:g>」&lt;b&gt;&lt;/b&gt;。"</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"裝置"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑動即可切換應用程式"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖曳即可快速切換應用程式"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"再次輕觸即可開啟"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"向上滑動即可開啟"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"向上滑動即可重試"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"如要使用 NFC,請先解鎖"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"這部裝置的擁有者為貴機構"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"這部裝置的擁有者為「<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>」"</string>
     <string name="phone_hint" msgid="6682125338461375925">"滑動手機圖示即可啟用"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"有多個應用程式正在使用<xliff:g id="TYPES_LIST">%s</xliff:g>。"</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"、 "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" 和 "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"「<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>」正在使用<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"「<xliff:g id="APPLICATION_NAME">%1$s</xliff:g>」最近曾使用<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(企業版)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"電話"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(透過「<xliff:g id="ATTRIBUTION">%s</xliff:g>」)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"相機"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"位置"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"麥克風"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 3a77730..78c4f17 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -416,10 +416,8 @@
     <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Irekhodi lesikrini"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Qala"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Misa"</string>
-    <!-- no translation found for sensor_privacy_start_use_mic_dialog_content (8643239110815357707) -->
-    <skip />
-    <!-- no translation found for sensor_privacy_start_use_camera_dialog_content (7773612142162829116) -->
-    <skip />
+    <string name="sensor_privacy_start_use_mic_dialog_content" msgid="8643239110815357707">"Ukuze uqhubeke, &lt;b&gt;i-<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; idinga ukufinyelela imakrofoni yedivayisi yakho."</string>
+    <string name="sensor_privacy_start_use_camera_dialog_content" msgid="7773612142162829116">"Ukuze uqhubeke, &lt;b&gt;i-<xliff:g id="APP">%s</xliff:g>&lt;/b&gt; idinga ukufinyelela ikhamera yakho."</string>
     <string name="media_seamless_remote_device" msgid="177033467332920464">"Idivayisi"</string>
     <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swayiphela phezulu ukuze ushintshe izinhlelo zokusebenza"</string>
     <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Hudula ngqo ukuze ushintshe ngokushesha izinhlelo zokusebenza"</string>
@@ -442,8 +440,7 @@
     <string name="notification_tap_again" msgid="4477318164947497249">"Thepha futhi ukuze uvule"</string>
     <string name="keyguard_unlock" msgid="8031975796351361601">"Swayiphela phezulu ukuze uvule"</string>
     <string name="keyguard_retry" msgid="886802522584053523">"Swayiphela phezulu ukuze uzame futhi"</string>
-    <!-- no translation found for require_unlock_for_nfc (1305686454823018831) -->
-    <skip />
+    <string name="require_unlock_for_nfc" msgid="1305686454823018831">"Vula ukuze usebenzise i-NFC"</string>
     <string name="do_disclosure_generic" msgid="4896482821974707167">"Le divayisi eyenhlangano yakho"</string>
     <string name="do_disclosure_with_name" msgid="2091641464065004091">"Le divayisi ngeye-<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="6682125338461375925">"Swayiphela ifoni kusukela kusithonjana"</string>
@@ -970,6 +967,11 @@
     <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Izinhlelo zokusebenza zisebenzisa i-<xliff:g id="TYPES_LIST">%s</xliff:g> yakho."</string>
     <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string>
     <string name="ongoing_privacy_dialog_last_separator" msgid="5615876114268009767">" kanye "</string>
+    <string name="ongoing_privacy_dialog_using_op" msgid="4125175620929701569">"I-<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> isebenzisa i-<xliff:g id="APP_OPP_NAME">%2$s</xliff:g>"</string>
+    <string name="ongoing_privacy_dialog_recent_op" msgid="4255923947334262404">"I-<xliff:g id="APPLICATION_NAME">%1$s</xliff:g> isebenzise i-<xliff:g id="APP_OPP_NAME">%2$s</xliff:g> kamuva nje"</string>
+    <string name="ongoing_privacy_dialog_enterprise" msgid="4082735415905550729">"(ibhizinisi)"</string>
+    <string name="ongoing_privacy_dialog_phonecall" msgid="3526223335298089311">"Ikholi yefoni"</string>
+    <string name="ongoing_privacy_dialog_attribution_text" msgid="9186683306719924646">"(kuya ku-<xliff:g id="ATTRIBUTION">%s</xliff:g>)"</string>
     <string name="privacy_type_camera" msgid="7974051382167078332">"ikhamera"</string>
     <string name="privacy_type_location" msgid="7991481648444066703">"indawo"</string>
     <string name="privacy_type_microphone" msgid="9136763906797732428">"imakrofoni"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 4059b49..8166e35 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -171,5 +171,23 @@
     <declare-styleable name="PagedTileLayout">
         <attr name="sideLabels" format="boolean"/>
     </declare-styleable>
+
+    <declare-styleable name="CropView">
+        <attr name="handleThickness" format="dimension" />
+        <attr name="handleColor" format="color" />
+        <attr name="scrimColor" format="color" />
+    </declare-styleable>
+
+    <declare-styleable name="MagnifierView">
+        <attr name="handleThickness" format="dimension" />
+        <attr name="handleColor" format="color" />
+        <attr name="scrimColor" format="color" />
+        <attr name="borderThickness" format="dimension" />
+        <attr name="borderColor" format="color" />
+    </declare-styleable>
+
+    <declare-styleable name="RoundedCornerProgressDrawable">
+        <attr name="android:drawable" />
+    </declare-styleable>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index a7cf3e9..5fb6de7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -197,6 +197,9 @@
     <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color>
     <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
 
+    <!-- Long screenshot UI -->
+    <color name="screenshot_crop_scrim">#9444</color>
+
     <!-- GM2 colors -->
     <color name="GM2_grey_50">#F8F9FA</color>
     <color name="GM2_grey_100">#F1F3F4</color>
@@ -261,6 +264,7 @@
     <color name="control_enabled_cool_foreground">@color/GM2_blue_300</color>
     <color name="control_thumbnail_tint">#33000000</color>
     <color name="control_thumbnail_shadow_color">@*android:color/black</color>
+    <color name="controls_lockscreen_scrim">#AA000000</color>
 
     <!-- Docked misalignment message -->
     <color name="misalignment_text_color">#F28B82</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 0a17828..bb04c3b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -301,7 +301,6 @@
         <item>com.android.systemui.ScreenDecorations</item>
         <item>com.android.systemui.biometrics.AuthController</item>
         <item>com.android.systemui.SliceBroadcastRelayHandler</item>
-        <item>com.android.systemui.SizeCompatModeActivityController</item>
         <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
         <item>com.android.systemui.theme.ThemeOverlayController</item>
         <item>com.android.systemui.accessibility.WindowMagnification</item>
@@ -351,7 +350,7 @@
     <bool name="config_showNotificationGear">true</bool>
 
     <!-- Whether or not a background should be drawn behind a notification. -->
-    <bool name="config_drawNotificationBackground">false</bool>
+    <bool name="config_drawNotificationBackground">true</bool>
 
     <!-- Whether or the notifications can be shown and dismissed with a drag. -->
     <bool name="config_enableNotificationShadeDrag">true</bool>
@@ -566,4 +565,7 @@
 
     <!-- Whether wallet view is shown in landscape / seascape orientations -->
     <bool name="global_actions_show_landscape_wallet_view">false</bool>
+
+    <!-- Whether to use the split 2-column notification shade -->
+    <bool name="config_use_split_notification_shade">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 79cb236..afa98b5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -155,7 +155,7 @@
     <dimen name="notification_max_heads_up_height_increased">188dp</dimen>
 
     <!-- Side padding on the lockscreen on the side of notifications -->
-    <dimen name="notification_side_paddings">16dp</dimen>
+    <dimen name="notification_side_paddings">4dp</dimen>
 
     <!-- padding between the heads up and the statusbar -->
     <dimen name="heads_up_status_bar_padding">8dp</dimen>
@@ -177,7 +177,10 @@
     <dimen name="notification_min_interaction_height">40dp</dimen>
 
     <!-- Radius for notifications corners without adjacent notifications -->
-    <dimen name="notification_corner_radius">28dp</dimen>
+    <dimen name="notification_corner_radius">8dp</dimen>
+
+    <!-- Radius for notifications corners with adjacent notifications -->
+    <dimen name="notification_corner_radius_small">0dp</dimen>
 
     <!-- the padding of the shelf icon container -->
     <dimen name="shelf_icon_container_padding">13dp</dimen>
@@ -342,6 +345,7 @@
     <dimen name="screenshot_action_chip_padding_end">16dp</dimen>
     <dimen name="screenshot_action_chip_text_size">14sp</dimen>
     <dimen name="screenshot_dismissal_height_delta">80dp</dimen>
+    <dimen name="screenshot_crop_handle_thickness">3dp</dimen>
 
 
     <!-- The width of the view containing navigation buttons -->
@@ -619,7 +623,7 @@
     <dimen name="z_distance_between_notifications">0.5dp</dimen>
 
     <!-- The height of the divider between the individual notifications. -->
-    <dimen name="notification_divider_height">2dp</dimen>
+    <dimen name="notification_divider_height">1dp</dimen>
 
     <!-- The corner radius of the shadow behind the notification. -->
     <dimen name="notification_shadow_radius">0dp</dimen>
@@ -632,7 +636,7 @@
     <dimen name="notification_children_container_divider_height">0.5dp</dimen>
 
     <!-- The horizontal margin of the content in the notification shade -->
-    <dimen name="notification_shade_content_margin_horizontal">16dp</dimen>
+    <dimen name="notification_shade_content_margin_horizontal">4dp</dimen>
 
     <!-- The top margin for the notification children container in its non-expanded form. -->
     <dimen name="notification_children_container_margin_top">
@@ -1113,6 +1117,9 @@
     <!-- Y translation for credential contents when animating in -->
     <dimen name="biometric_dialog_credential_translation_offset">60dp</dimen>
 
+    <!-- UDFPS enrollment progress bar thickness -->
+    <dimen name="udfps_enroll_progress_thickness">12dp</dimen>
+
     <!-- Wireless Charging Animation values -->
     <dimen name="wireless_charging_dots_radius_start">0dp</dimen>
     <dimen name="wireless_charging_dots_radius_end">4dp</dimen>
@@ -1178,6 +1185,12 @@
 
     <dimen name="ongoing_appops_dialog_side_margins">@dimen/notification_shade_content_margin_horizontal</dimen>
 
+    <dimen name="ongoing_appops_dialog_circle_size">32dp</dimen>
+
+    <dimen name="ongoing_appops_dialog_icon_size">20dp</dimen>
+
+    <dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
+
     <!-- Size of the RAT type for CellularTile -->
     <dimen name="celltile_rat_type_size">10sp</dimen>
 
@@ -1250,6 +1263,7 @@
 
     <!-- Home Controls activity view detail panel-->
     <dimen name="controls_activity_view_top_offset">100dp</dimen>
+    <dimen name="controls_activity_view_side_offset">12dp</dimen>
     <dimen name="controls_activity_view_text_size">17sp</dimen>
     <dimen name="controls_activity_view_corner_radius">@*android:dimen/config_bottomDialogCornerRadius</dimen>
 
@@ -1324,4 +1338,12 @@
     <dimen name="people_space_widget_radius">24dp</dimen>
     <dimen name="people_space_widget_round_radius">100dp</dimen>
     <dimen name="people_space_widget_background_padding">6dp</dimen>
+
+    <dimen name="rounded_slider_height">48dp</dimen>
+    <!-- rounded_slider_height / 2 -->
+    <dimen name="rounded_slider_corner_radius">24dp</dimen>
+    <!-- rounded_slider_height / 2 -->
+    <dimen name="rounded_slider_icon_size">24dp</dimen>
+    <!-- rounded_slider_icon_size / 2 -->
+    <dimen name="rounded_slider_icon_inset">12dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 905a575..01e54ff 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -16,10 +16,12 @@
   -->
 
 <resources>
-    <bool name="are_flags_overrideable">true</bool>
+    <bool name="are_flags_overrideable">false</bool>
 
     <bool name="flag_notification_pipeline2">false</bool>
     <bool name="flag_notification_pipeline2_rendering">false</bool>
+    <bool name="flag_notif_updates">false</bool>
+
     <bool name="flag_shade_is_opaque">false</bool>
 
     <!-- b/171917882 -->
@@ -29,4 +31,9 @@
 
     <!-- AOD/Lockscreen alternate layout -->
     <bool name="flag_keyguard_layout">false</bool>
+
+    <bool name="flag_brightness_slider">false</bool>
+
+    <!-- People Tile flag -->
+    <bool name="flag_conversations">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4baa06a..d932395 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1888,7 +1888,7 @@
     <!-- Notification Inline controls: describes how the notification was adjusted [CHAR_LIMIT=NONE] -->
     <string name="feedback_demoted">This notification was automatically &lt;b>ranked lower&lt;/b> in your shade.</string>
     <!-- Notification Inline controls: prompts the user for feedback [CHAR_LIMIT=NONE] -->
-    <string name="feedback_prompt">Was this correct?</string>
+    <string name="feedback_prompt">Let the developer know your feedback. Was this correct?</string>
     <!-- Notification Inline controls: responds to user provided feedback [CHAR_LIMIT=NONE] -->
     <string name="feedback_response">Thanks for your feedback!</string>
     <string name="feedback_ok">OK</string>
@@ -2556,8 +2556,8 @@
     <!-- Text for privacy dialog, indicating that the application is the enterprise version [CHAR LIMIT=NONE] -->
     <string name="ongoing_privacy_dialog_enterprise">(enterprise)</string>
 
-    <!-- Text for privacy dialog, identifying the phonecall app [CHAR LIMIT=NONE]-->
-    <string name="ongoing_privacy_dialog_phonecall">Phonecall</string>
+    <!-- Text for privacy dialog, identifying the phone call app [CHAR LIMIT=NONE]-->
+    <string name="ongoing_privacy_dialog_phonecall">Phone call</string>
 
     <!-- Text for privacy dialog, indicating that an app is using an op on behalf of another [CHAR LIMIT=NONE] -->
     <string name="ongoing_privacy_dialog_attribution_text">(through <xliff:g id="attribution" example="Special app">%s</xliff:g>)</string>
@@ -2580,9 +2580,6 @@
     <!-- What to show on the ambient display player when song doesn't have a title. [CHAR LIMIT=20] -->
     <string name="music_controls_no_title">No title</string>
 
-    <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
-    <string name="restart_button_description">Tap to restart this app and go full screen.</string>
-
     <!-- Action in accessibility menu to move the stack of bubbles [CHAR LIMIT=20] -->
     <string name="bubble_accessibility_action_move">Move</string>
 
@@ -2771,13 +2768,6 @@
     <!-- Controls menu, edit [CHAR_LIMIT=30] -->
     <string name="controls_menu_edit">Edit controls</string>
 
-    <!-- Device-specific path to the node for enabling/disabling the high-brightness mode -->
-    <string name="udfps_hbm_sysfs_path" translatable="false"></string>
-    <!-- Device-specific payload for enabling the high-brightness mode -->
-    <string name="udfps_hbm_enable_command" translatable="false"></string>
-    <!-- Device-specific payload for disabling the high-brightness mode -->
-    <string name="udfps_hbm_disable_command" translatable="false"></string>
-
     <!-- Title for the media output group dialog with media related devices [CHAR LIMIT=50] -->
     <string name="media_output_dialog_add_output">Add outputs</string>
     <!-- Title for the media output slice with group devices [CHAR LIMIT=50] -->
@@ -2826,4 +2816,6 @@
 
     <!-- No translation [CHAR LIMIT=0] -->
     <string name="qs_remove_labels" translatable="false"></string>
+
+    <string name="qs_tile_label_fontFamily" translatable="false">@*android:string/config_headlineFontFamily</string>
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index db260ce..7c72548 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -196,7 +196,7 @@
 
     <style name="TextAppearance.QS.TileLabel">
         <item name="android:textSize">@dimen/qs_tile_text_size</item>
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:fontFamily">@string/qs_tile_label_fontFamily</item>
     </style>
 
     <style name="TextAppearance.QS.TileLabel.Secondary">
@@ -354,15 +354,17 @@
     </style>
 
     <style name="LockPatternStyle">
-        <item name="*android:regularColor">?android:attr/textColorPrimary</item>
+        <item name="*android:regularColor">?android:attr/colorAccent</item>
         <item name="*android:successColor">?android:attr/textColorPrimary</item>
         <item name="*android:errorColor">?android:attr/colorError</item>
+        <item name="*android:dotColor">?android:attr/textColorSecondary</item>
     </style>
 
     <style name="LockPatternStyleBiometricPrompt">
         <item name="*android:regularColor">?android:attr/colorForeground</item>
         <item name="*android:successColor">?android:attr/colorForeground</item>
         <item name="*android:errorColor">?android:attr/colorError</item>
+        <item name="*android:dotColor">?android:attr/textColorSecondary</item>
     </style>
 
     <style name="qs_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
@@ -660,6 +662,19 @@
       <item name="android:windowExitAnimation">@anim/bottomsheet_out</item>
     </style>
 
+    <style name="Theme.SystemUI.Dialog.Control.LockScreen" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
+      <item name="android:windowAnimationStyle">@style/Animation.Fade</item>
+      <item name="android:windowFullscreen">true</item>
+      <item name="android:windowIsFloating">false</item>
+      <item name="android:windowBackground">@color/controls_lockscreen_scrim</item>
+      <item name="android:backgroundDimEnabled">true</item>
+    </style>
+
+    <style name="Animation.Fade">
+      <item name="android:windowEnterAnimation">@android:anim/fade_in</item>
+      <item name="android:windowExitAnimation">@android:anim/fade_out</item>
+    </style>
+
     <style name="Control" />
 
     <style name="Control.MenuItem">
@@ -745,4 +760,17 @@
           * Title: headline, medium 20sp
           * Message: body, 16 sp -->
     <style name="Theme.ControlsRequestDialog" parent="@*android:style/Theme.DeviceDefault.Dialog.Alert"/>
+
+    <style name="TextAppearance.PrivacyDialog">
+        <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+    </style>
+
+    <style name="UdfpsProgressBarStyle"
+        parent="android:style/Widget.Material.ProgressBar.Horizontal">
+        <item name="android:indeterminate">false</item>
+        <item name="android:max">10000</item>
+        <item name="android:mirrorForRtl">false</item>
+        <item name="android:progressDrawable">@drawable/udfps_progress_bar</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index 10e28c4..d0c63a8 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -15,8 +15,10 @@
   -->
 
 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
-    android:minWidth="180dp"
+    android:minWidth="140dp"
     android:minHeight="40dp"
+    android:minResizeWidth="110dp"
+    android:minResizeHeight="40dp"
     android:updatePeriodMillis="60000"
     android:previewImage="@drawable/ic_android"
     android:resizeMode="horizontal|vertical"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java
index c3815e4..42bc1d0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInitializer.java
@@ -37,4 +37,9 @@
      * Called from {@link PluginManagerImpl#handleWtfs()}.
      */
     void handleWtfs();
+
+    /**
+     * Returns if pluging manager should run in debug mode.
+     */
+    boolean isDebuggable();
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
index ee7030a8..1a4e2d1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java
@@ -28,7 +28,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -72,7 +71,7 @@
     PluginInstanceManager(Context context, String action, PluginListener<T> listener,
             boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) {
         this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
-                manager, Build.IS_DEBUGGABLE, manager.getWhitelistedPlugins());
+                manager, manager.isDebuggable(), manager.getWhitelistedPlugins());
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 6d67f21..f5ed9da 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -64,8 +64,6 @@
     private static final String TAG = PluginManagerImpl.class.getSimpleName();
     static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN";
 
-    private static PluginManager sInstance;
-
     private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
             = new ArrayMap<>();
     private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>();
@@ -73,7 +71,7 @@
     private final ArraySet<String> mWhitelistedPlugins = new ArraySet<>();
     private final Context mContext;
     private final PluginInstanceManagerFactory mFactory;
-    private final boolean isDebuggable;
+    private final boolean mIsDebuggable;
     private final PluginPrefs mPluginPrefs;
     private final PluginEnabler mPluginEnabler;
     private final PluginInitializer mPluginInitializer;
@@ -83,7 +81,7 @@
     private Looper mLooper;
 
     public PluginManagerImpl(Context context, PluginInitializer initializer) {
-        this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE,
+        this(context, new PluginInstanceManagerFactory(), initializer.isDebuggable(),
                 Thread.getUncaughtExceptionPreHandler(), initializer);
     }
 
@@ -93,7 +91,7 @@
         mContext = context;
         mFactory = factory;
         mLooper = initializer.getBgLooper();
-        isDebuggable = debuggable;
+        mIsDebuggable = debuggable;
         mWhitelistedPlugins.addAll(Arrays.asList(initializer.getWhitelistedPlugins(mContext)));
         mPluginPrefs = new PluginPrefs(mContext);
         mPluginEnabler = initializer.getPluginEnabler(mContext);
@@ -111,6 +109,10 @@
         });
     }
 
+    public boolean isDebuggable() {
+        return mIsDebuggable;
+    }
+
     public String[] getWhitelistedPlugins() {
         return mWhitelistedPlugins.toArray(new String[0]);
     }
@@ -297,7 +299,7 @@
 
     /** Returns class loader specific for the given plugin. */
     public ClassLoader getClassLoader(ApplicationInfo appInfo) {
-        if (!isDebuggable && !isPluginPackageWhitelisted(appInfo.packageName)) {
+        if (!mIsDebuggable && !isPluginPackageWhitelisted(appInfo.packageName)) {
             Log.w(TAG, "Cannot get class loader for non-whitelisted plugin. Src:"
                     + appInfo.sourceDir + ", pkg: " + appInfo.packageName);
             return null;
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
similarity index 68%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
index 8b5b251..e5ced3e 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISplitScreenListener.aidl
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+package com.android.systemui.shared.recents;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
  */
-@android.annotation.Hide
-package com.android.role;
+oneway interface ISplitScreenListener {
+    void onStagePositionChanged(int stage, int position);
+    void onTaskStageChanged(int taskId, int stage);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 388eeb6..e38cf23 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shared.recents;
 
+import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
@@ -23,9 +24,11 @@
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.view.MotionEvent;
 
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.shared.recents.ISplitScreenListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
 
@@ -202,4 +205,49 @@
 
     /** Unegisters a RemoteTransitionCompat that will handle transitions. */
     void unregisterRemoteTransition(in RemoteTransitionCompat remoteTransition) = 33;
+
+// SplitScreen APIs...copied from SplitScreen.java
+    /**
+     * Stage position isn't specified normally meaning to use what ever it is currently set to.
+     */
+    //int STAGE_POSITION_UNDEFINED = -1;
+    /**
+     * Specifies that a stage is positioned at the top half of the screen if
+     * in portrait mode or at the left half of the screen if in landscape mode.
+     */
+    //int STAGE_POSITION_TOP_OR_LEFT = 0;
+    /**
+     * Specifies that a stage is positioned at the bottom half of the screen if
+     * in portrait mode or at the right half of the screen if in landscape mode.
+     */
+    //int STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
+    /**
+     * Stage type isn't specified normally meaning to use what ever the default is.
+     * E.g. exit split-screen and launch the app in fullscreen.
+     */
+    //int STAGE_TYPE_UNDEFINED = -1;
+    /**
+     * The main stage type.
+     * @see MainStage
+     */
+    //int STAGE_TYPE_MAIN = 0;
+    /**
+     * The side stage type.
+     * @see SideStage
+     */
+    //int STAGE_TYPE_SIDE = 1;
+
+    void registerSplitScreenListener(in ISplitScreenListener listener) = 34;
+    void unregisterSplitScreenListener(in ISplitScreenListener listener) = 35;
+
+    /** Hides the side-stage if it is currently visible. */
+    void setSideStageVisibility(in boolean visible) = 36;
+    /** Removes the split-screen stages. */
+    void exitSplitScreen() = 37;
+    void startTask(in int taskId, in int stage, in int position, in Bundle options) = 38;
+    void startShortcut(in String packageName, in String shortcutId, in int stage, in int position,
+            in Bundle options, in UserHandle user) = 39;
+    void startIntent(
+            in PendingIntent intent, in int stage, in int position, in Bundle options) = 40;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index e5c4bf3..9164137 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -24,6 +24,7 @@
 
 import android.annotation.NonNull;
 import android.app.Activity;
+import android.app.ActivityClient;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -140,8 +141,9 @@
      */
     public void invalidateHomeTaskSnapshot(final Activity homeActivity) {
         try {
-            getService().invalidateHomeTaskSnapshot(homeActivity.getActivityToken());
-        } catch (RemoteException e) {
+            ActivityClient.getInstance().invalidateHomeTaskSnapshot(
+                    homeActivity.getActivityToken());
+        } catch (Throwable e) {
             Log.w(TAG, "Failed to invalidate home snapshot", e);
         }
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
index 3584c82..e2ca349 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -72,9 +72,13 @@
         return ActivityOptions.makeRemoteTransition(remoteTransition.getTransition());
     }
 
+    /**
+     * Returns ActivityOptions for overriding task transition animation.
+     */
     public static ActivityOptions makeCustomAnimation(Context context, int enterResId,
             int exitResId, final Runnable callback, final Handler callbackHandler) {
-        return ActivityOptions.makeCustomAnimation(context, enterResId, exitResId, callbackHandler,
+        return ActivityOptions.makeCustomTaskAnimation(context, enterResId, exitResId,
+                callbackHandler,
                 new ActivityOptions.OnAnimationStartedListener() {
                     @Override
                     public void onAnimationStarted() {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index a56c6a1..e6477f1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -18,9 +18,11 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.TransitionOldType;
 
 import android.os.RemoteException;
 import android.util.Log;
@@ -65,13 +67,17 @@
             final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
         return new IRemoteAnimationRunner.Stub() {
             @Override
-            public void onAnimationStart(RemoteAnimationTarget[] apps,
+            public void onAnimationStart(@TransitionOldType int transit,
+                    RemoteAnimationTarget[] apps,
                     RemoteAnimationTarget[] wallpapers,
+                    RemoteAnimationTarget[] nonApps,
                     final IRemoteAnimationFinishedCallback finishedCallback) {
                 final RemoteAnimationTargetCompat[] appsCompat =
                         RemoteAnimationTargetCompat.wrap(apps);
                 final RemoteAnimationTargetCompat[] wallpapersCompat =
                         RemoteAnimationTargetCompat.wrap(wallpapers);
+                final RemoteAnimationTargetCompat[] nonAppsCompat =
+                        RemoteAnimationTargetCompat.wrap(nonApps);
                 final Runnable animationFinishedCallback = new Runnable() {
                     @Override
                     public void run() {
@@ -83,8 +89,8 @@
                         }
                     }
                 };
-                remoteAnimationAdapter.onAnimationStart(appsCompat, wallpapersCompat,
-                        animationFinishedCallback);
+                remoteAnimationAdapter.onAnimationStart(transit, appsCompat, wallpapersCompat,
+                        nonAppsCompat, animationFinishedCallback);
             }
 
             @Override
@@ -104,6 +110,9 @@
                         RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */);
                 final RemoteAnimationTargetCompat[] wallpapersCompat =
                         RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */);
+                // TODO(bc-unlock): Build wrapped object for non-apps target.
+                final RemoteAnimationTargetCompat[] nonAppsCompat =
+                        new RemoteAnimationTargetCompat[0];
                 final Runnable animationFinishedCallback = new Runnable() {
                     @Override
                     public void run() {
@@ -147,7 +156,10 @@
                     }
                 }
                 t.apply();
-                remoteAnimationAdapter.onAnimationStart(appsCompat, wallpapersCompat,
+                // TODO(bc-unlcok): Pass correct transit type.
+                remoteAnimationAdapter.onAnimationStart(
+                        TRANSIT_OLD_NONE,
+                        appsCompat, wallpapersCompat, nonAppsCompat,
                         animationFinishedCallback);
             }
         };
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 33372f6..0076292 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.shared.system;
 
+import android.view.WindowManager;
+
 public interface RemoteAnimationRunnerCompat {
-    void onAnimationStart(RemoteAnimationTargetCompat[] apps,
-            RemoteAnimationTargetCompat[] wallpapers, Runnable finishedCallback);
+    void onAnimationStart(@WindowManager.TransitionOldType int transit,
+            RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
+            RemoteAnimationTargetCompat[] nonApps, Runnable finishedCallback);
     void onAnimationCancelled();
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 2a0715e..87f6b82 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -118,9 +118,9 @@
     }
 
     public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
-        final RemoteAnimationTargetCompat[] appsCompat =
-                new RemoteAnimationTargetCompat[apps != null ? apps.length : 0];
-        for (int i = 0; i < apps.length; i++) {
+        final int length = apps != null ? apps.length : 0;
+        final RemoteAnimationTargetCompat[] appsCompat = new RemoteAnimationTargetCompat[length];
+        for (int i = 0; i < length; i++) {
             appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
         }
         return appsCompat;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index 9df1230..7ba9069 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -192,19 +192,6 @@
     //   Settings > Editor > Code Style > Formatter Control
     //@formatter:off
 
-
-    @DataClass.Generated.Member
-    /* package-private */ RemoteTransitionCompat(
-            @NonNull IRemoteTransition transition,
-            @Nullable TransitionFilter filter) {
-        this.mTransition = transition;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTransition);
-        this.mFilter = filter;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
     @DataClass.Generated.Member
     public @NonNull IRemoteTransition getTransition() {
         return mTransition;
@@ -308,9 +295,8 @@
             if ((mBuilderFieldsSet & 0x2) == 0) {
                 mFilter = null;
             }
-            RemoteTransitionCompat o = new RemoteTransitionCompat(
-                    mTransition,
-                    mFilter);
+            RemoteTransitionCompat o = new RemoteTransitionCompat(mTransition);
+            o.mFilter = this.mFilter;
             return o;
         }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 8d010c7..6c77af7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -19,7 +19,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ITaskStackListener;
 import android.content.ComponentName;
-import android.os.IBinder;
 
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -80,7 +79,6 @@
     public void onTaskDescriptionChanged(RunningTaskInfo taskInfo) { }
 
     public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { }
-    public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { }
 
     public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index a907e66..8f08f5a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -18,15 +18,14 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
-import android.window.TaskSnapshot;
 import android.app.TaskStackListener;
 import android.content.ComponentName;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Trace;
 import android.util.Log;
+import android.window.TaskSnapshot;
 
 import com.android.internal.os.SomeArgs;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -88,13 +87,12 @@
         private static final int ON_TASK_MOVED_TO_FRONT = 14;
         private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 15;
         private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED = 16;
-        private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 17;
-        private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 18;
-        private static final int ON_TASK_DISPLAY_CHANGED = 19;
-        private static final int ON_TASK_LIST_UPDATED = 20;
-        private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 21;
-        private static final int ON_TASK_DESCRIPTION_CHANGED = 22;
-        private static final int ON_ACTIVITY_ROTATION = 23;
+        private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 17;
+        private static final int ON_TASK_DISPLAY_CHANGED = 18;
+        private static final int ON_TASK_LIST_UPDATED = 19;
+        private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 20;
+        private static final int ON_TASK_DESCRIPTION_CHANGED = 21;
+        private static final int ON_ACTIVITY_ROTATION = 22;
 
         /**
          * List of {@link TaskStackChangeListener} registered from {@link #addListener}.
@@ -248,13 +246,6 @@
         }
 
         @Override
-        public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
-            mHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId,
-                    0 /* unused */,
-                    activityToken).sendToTarget();
-        }
-
-        @Override
         public void onTaskDisplayChanged(int taskId, int newDisplayId) {
             mHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
         }
@@ -391,13 +382,6 @@
                         }
                         break;
                     }
-                    case ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onSizeCompatModeActivityChanged(
-                                    msg.arg1, (IBinder) msg.obj);
-                        }
-                        break;
-                    }
                     case ON_BACK_PRESSED_ON_TASK_ROOT: {
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                             mTaskStackListeners.get(i).onBackPressedOnTaskRoot(
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
index 62d3093..ca99563 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
@@ -38,7 +38,7 @@
  * The time's text color is a gradient that changes its colors based on its controller.
  */
 public class AnimatableClockView extends TextView {
-    private static final CharSequence FORMAT_12_HOUR = "h\nmm";
+    private static final CharSequence FORMAT_12_HOUR = "hh\nmm";
     private static final CharSequence FORMAT_24_HOUR = "HH\nmm";
     private static final long ANIM_DURATION = 300;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index b7d7498..707ee29 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -154,7 +154,7 @@
      **/
     public void reloadColors() {
         int color = Utils.getColorAttrDefaultColor(getContext(),
-                android.R.attr.textColorSecondary);
+                android.R.attr.textColorPrimaryInverse);
         setTextColor(color);
         setBackground(getContext()
                 .getDrawable(com.android.systemui.R.drawable.kg_emergency_button_background));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 2d972e0..6eb54c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -192,7 +192,7 @@
             statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
             statusAreaLP.removeRule(RelativeLayout.START_OF);
             statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
-            statusAreaLP.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            statusAreaLP.width = ViewGroup.LayoutParams.MATCH_PARENT;
         }
 
         requestLayout();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index a40dc7a..276036c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -16,6 +16,7 @@
 package com.android.keyguard;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.DEFAULT_DISPLAY_GROUP;
 
 import android.app.Presentation;
 import android.content.Context;
@@ -73,16 +74,7 @@
 
         @Override
         public void onDisplayChanged(int displayId) {
-            if (displayId == DEFAULT_DISPLAY) return;
-            final Presentation presentation = mPresentations.get(displayId);
-            if (presentation != null && mShowing) {
-                hidePresentation(displayId);
-                // update DisplayInfo.
-                final Display display = mDisplayService.getDisplay(displayId);
-                if (display != null) {
-                    showPresentation(display);
-                }
-            }
+
         }
 
         @Override
@@ -116,6 +108,14 @@
             if (DEBUG) Log.i(TAG, "Do not show KeyguardPresentation on a private display");
             return false;
         }
+        if (mTmpDisplayInfo.displayGroupId != DEFAULT_DISPLAY_GROUP) {
+            if (DEBUG) {
+                Log.i(TAG,
+                        "Do not show KeyguardPresentation on a non-default group display");
+            }
+            return false;
+        }
+
         return true;
     }
     /**
@@ -130,8 +130,7 @@
         final int displayId = display.getDisplayId();
         Presentation presentation = mPresentations.get(displayId);
         if (presentation == null) {
-            final Presentation newPresentation = new KeyguardPresentation(mContext, display,
-                    mKeyguardStatusViewComponentFactory);
+            final Presentation newPresentation = createPresentation(display);
             newPresentation.setOnDismissListener(dialog -> {
                 if (newPresentation.equals(mPresentations.get(displayId))) {
                     mPresentations.remove(displayId);
@@ -152,6 +151,10 @@
         return false;
     }
 
+    KeyguardPresentation createPresentation(Display display) {
+        return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
+    }
+
     /**
      * @param displayId The id of the display to hide the presentation off.
      */
@@ -293,15 +296,16 @@
         }
 
         @Override
+        public void onDisplayChanged() {
+            updateBounds();
+            getWindow().getDecorView().requestLayout();
+        }
+
+        @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
 
-            final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics()
-                    .getBounds();
-            mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100;
-            mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100;
-            mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200;
-            mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200;
+            updateBounds();
 
             setContentView(LayoutInflater.from(mContext)
                     .inflate(R.layout.keyguard_presentation, null));
@@ -326,5 +330,14 @@
 
             mKeyguardClockSwitchController.init();
         }
+
+        private void updateBounds() {
+            final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics()
+                    .getBounds();
+            mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100;
+            mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100;
+            mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200;
+            mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index cc59c39..c182fd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -29,12 +29,17 @@
 import android.content.Context;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.util.TypedValue;
+import android.view.Gravity;
 import android.view.MotionEvent;
+import android.view.OrientationEventListener;
 import android.view.VelocityTracker;
+import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewPropertyAnimator;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimationControlListener;
@@ -55,6 +60,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
 import java.util.List;
 
@@ -99,6 +105,12 @@
     private boolean mDisappearAnimRunning;
     private SwipeListener mSwipeListener;
 
+    private boolean mIsSecurityViewLeftAligned = true;
+    private boolean mOneHandedMode = false;
+    private SecurityMode mSecurityMode = SecurityMode.Invalid;
+    private ViewPropertyAnimator mRunningOneHandedAnimator;
+    private final OrientationEventListener mOrientationEventListener;
+
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
             new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
 
@@ -157,16 +169,20 @@
     // Used to notify the container when something interesting happens.
     public interface SecurityCallback {
         boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen);
+
         void userActivity();
+
         void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
 
         /**
-         * @param strongAuth wheher the user has authenticated with strong authentication like
-         *                   pattern, password or PIN but not by trust agents or fingerprint
+         * @param strongAuth   wheher the user has authenticated with strong authentication like
+         *                     pattern, password or PIN but not by trust agents or fingerprint
          * @param targetUserId a user that needs to be the foreground user at the finish completion.
          */
         void finish(boolean strongAuth, int targetUserId);
+
         void reset();
+
         void onCancelClicked();
     }
 
@@ -224,11 +240,136 @@
         super(context, attrs, defStyle);
         mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
         mViewConfiguration = ViewConfiguration.get(context);
+
+        mOrientationEventListener = new OrientationEventListener(context) {
+            @Override
+            public void onOrientationChanged(int orientation) {
+                updateLayoutForSecurityMode(mSecurityMode);
+            }
+        };
     }
 
     void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
+        mSecurityMode = securityMode;
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
         updateBiometricRetry(securityMode, faceAuthEnabled);
+
+        updateLayoutForSecurityMode(securityMode);
+        mOrientationEventListener.enable();
+    }
+
+    void updateLayoutForSecurityMode(SecurityMode securityMode) {
+        mSecurityMode = securityMode;
+        mOneHandedMode = canUseOneHandedBouncer();
+
+        if (mOneHandedMode) {
+            mIsSecurityViewLeftAligned = isOneHandedKeyguardLeftAligned(mContext);
+        }
+
+        updateSecurityViewGravity();
+        updateSecurityViewLocation(false);
+    }
+
+    /** Return whether the one-handed keyguard should be enabled. */
+    private boolean canUseOneHandedBouncer() {
+        // Is it enabled?
+        if (!getResources().getBoolean(
+                com.android.internal.R.bool.config_enableOneHandedKeyguard)) {
+            return false;
+        }
+
+        if (!KeyguardSecurityModel.isSecurityViewOneHanded(mSecurityMode)) {
+            return false;
+        }
+
+        return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+    }
+
+    /** Read whether the one-handed keyguard should be on the left/right from settings. */
+    private boolean isOneHandedKeyguardLeftAligned(Context context) {
+        try {
+            return Settings.Global.getInt(context.getContentResolver(),
+                    Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
+                    == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+        } catch (Settings.SettingNotFoundException ex) {
+            return true;
+        }
+    }
+
+    private void updateSecurityViewGravity() {
+        View securityView = findKeyguardSecurityView();
+
+        if (securityView == null) {
+            return;
+        }
+
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) securityView.getLayoutParams();
+
+        if (mOneHandedMode) {
+            lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
+        } else {
+            lp.gravity = Gravity.CENTER;
+        }
+
+        securityView.setLayoutParams(lp);
+    }
+
+    /**
+     * Moves the inner security view to the correct location (in one handed mode) with animation.
+     * This is triggered when the user taps on the side of the screen that is not currently occupied
+     * by the security view .
+     */
+    private void updateSecurityViewLocation(boolean animate) {
+        View securityView = findKeyguardSecurityView();
+
+        if (securityView == null) {
+            return;
+        }
+
+        if (!mOneHandedMode) {
+            securityView.setTranslationX(0);
+            return;
+        }
+
+        if (mRunningOneHandedAnimator != null) {
+            mRunningOneHandedAnimator.cancel();
+            mRunningOneHandedAnimator = null;
+        }
+
+        int targetTranslation = mIsSecurityViewLeftAligned ? 0 : (int) (getMeasuredWidth() / 2f);
+
+        if (animate) {
+            mRunningOneHandedAnimator = securityView.animate().translationX(targetTranslation);
+            mRunningOneHandedAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+            mRunningOneHandedAnimator.setListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mRunningOneHandedAnimator = null;
+                }
+            });
+
+            mRunningOneHandedAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+            mRunningOneHandedAnimator.start();
+        } else {
+            securityView.setTranslationX(targetTranslation);
+        }
+    }
+
+    @Nullable
+    private KeyguardSecurityViewFlipper findKeyguardSecurityView() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+
+            if (isKeyguardSecurityView(child)) {
+                return (KeyguardSecurityViewFlipper) child;
+            }
+        }
+
+        return null;
+    }
+
+    private boolean isKeyguardSecurityView(View view) {
+        return view instanceof KeyguardSecurityViewFlipper;
     }
 
     public void onPause() {
@@ -237,6 +378,7 @@
             mAlertDialog = null;
         }
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
+        mOrientationEventListener.disable();
     }
 
     @Override
@@ -318,19 +460,44 @@
                 if (mSwipeListener != null) {
                     mSwipeListener.onSwipeUp();
                 }
+            } else {
+                if (!mIsDragging) {
+                    handleTap(event);
+                }
             }
         }
         return true;
     }
 
+    private void handleTap(MotionEvent event) {
+        // If we're using a fullscreen security mode, skip
+        if (!mOneHandedMode) {
+            return;
+        }
+
+        // Did the tap hit the "other" side of the bouncer?
+        if ((mIsSecurityViewLeftAligned && (event.getX() > getWidth() / 2f))
+                || (!mIsSecurityViewLeftAligned && (event.getX() < getWidth() / 2f))) {
+            mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned;
+
+            Settings.Global.putInt(
+                    mContext.getContentResolver(),
+                    Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
+                    mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
+                            : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
+
+            updateSecurityViewLocation(true);
+        }
+    }
+
     void setSwipeListener(SwipeListener swipeListener) {
         mSwipeListener = swipeListener;
     }
 
     private void startSpringAnimation(float startVelocity) {
         mSpringAnimation
-            .setStartVelocity(startVelocity)
-            .animateToFinalPosition(0);
+                .setStartVelocity(startVelocity)
+                .animateToFinalPosition(0);
     }
 
     public void startDisappearAnimation(SecurityMode securitySelection) {
@@ -395,7 +562,9 @@
     }
 
     private void beginJankInstrument(int cuj) {
-        InteractionJankMonitor.getInstance().begin(mSecurityViewFlipper.getSecurityView(), cuj);
+        KeyguardInputView securityView = mSecurityViewFlipper.getSecurityView();
+        if (securityView == null) return;
+        InteractionJankMonitor.getInstance().begin(securityView, cuj);
     }
 
     private void endJankInstrument(int cuj) {
@@ -438,18 +607,17 @@
         return insets.inset(0, 0, 0, inset);
     }
 
-
     private void showDialog(String title, String message) {
         if (mAlertDialog != null) {
             mAlertDialog.dismiss();
         }
 
         mAlertDialog = new AlertDialog.Builder(mContext)
-            .setTitle(title)
-            .setMessage(message)
-            .setCancelable(false)
-            .setNeutralButton(R.string.ok, null)
-            .create();
+                .setTitle(title)
+                .setMessage(message)
+                .setCancelable(false)
+                .setNeutralButton(R.string.ok, null)
+                .create();
         if (!(mContext instanceof Activity)) {
             mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
         }
@@ -487,6 +655,44 @@
         }
     }
 
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int maxHeight = 0;
+        int maxWidth = 0;
+        int childState = 0;
+
+        int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                MeasureSpec.getSize(widthMeasureSpec) / 2,
+                MeasureSpec.getMode(widthMeasureSpec));
+
+        for (int i = 0; i < getChildCount(); i++) {
+            final View view = getChildAt(i);
+            if (view.getVisibility() != GONE) {
+                if (mOneHandedMode && isKeyguardSecurityView(view)) {
+                    measureChildWithMargins(view, halfWidthMeasureSpec, 0,
+                            heightMeasureSpec, 0);
+                } else {
+                    measureChildWithMargins(view, widthMeasureSpec, 0,
+                            heightMeasureSpec, 0);
+                }
+                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+                maxWidth = Math.max(maxWidth,
+                        view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+                maxHeight = Math.max(maxHeight,
+                        view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+                childState = combineMeasuredStates(childState, view.getMeasuredState());
+            }
+        }
+
+        // Check against our minimum height and width
+        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
+                resolveSizeAndState(maxHeight, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
     void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
         String message = null;
         switch (userType) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 1a8d420..fdab8db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -404,6 +404,7 @@
         if (newView != null) {
             newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
             mSecurityViewFlipperController.show(newView);
+            mView.updateLayoutForSecurityMode(securityMode);
         }
 
         mSecurityCallback.onSecurityModeChanged(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index c77c867..631c248 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -92,4 +92,13 @@
                 throw new IllegalStateException("Unknown security quality:" + security);
         }
     }
+
+    /**
+     * Returns whether the given security view should be used in a "one handed" way. This can be
+     * used to change how the security view is drawn (e.g. take up less of the screen, and align to
+     * one side).
+     */
+    public static boolean isSecurityViewOneHanded(SecurityMode securityMode) {
+        return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 7773fe9..75ef4b32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -128,6 +128,8 @@
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
+            if (child.getVisibility() != View.VISIBLE) continue;
+
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
             if (lp.maxWidth > 0 && lp.maxWidth < maxWidth) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9908e67..d9a1eb6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1909,12 +1909,14 @@
     }
 
     /**
-     * Whether to show the lock icon on lock screen and bouncer. This depends on the enrolled
-     * biometrics to the device.
+     * Whether to show the lock icon on lock screen and bouncer.
      */
-    public boolean shouldShowLockIcon() {
-        return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
-                && !isUdfpsEnrolled();
+    public boolean canShowLockIcon() {
+        if (mLockScreenMode == LOCK_SCREEN_MODE_LAYOUT_1) {
+            return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser())
+                    && !isUdfpsEnrolled();
+        }
+        return true;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 8ebcb20..756d610 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -156,9 +156,14 @@
     }
 
     @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams());
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params);
 
+        super.setLayoutParams(params);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         measureChildren(widthMeasureSpec, heightMeasureSpec);
 
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
deleted file mode 100644
index 02f34ac..0000000
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2019 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;
-
-import android.app.ActivityTaskManager;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
-import android.hardware.display.DisplayManager;
-import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.CommandQueue;
-
-import java.lang.ref.WeakReference;
-
-import javax.inject.Inject;
-
-/** Shows a restart-activity button when the foreground activity is in size compatibility mode. */
-@SysUISingleton
-public class SizeCompatModeActivityController extends SystemUI implements CommandQueue.Callbacks {
-    private static final String TAG = "SizeCompatMode";
-
-    /** The showing buttons by display id. */
-    private final SparseArray<RestartActivityButton> mActiveButtons = new SparseArray<>(1);
-    /** Avoid creating display context frequently for non-default display. */
-    private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
-    private final CommandQueue mCommandQueue;
-
-    /** Only show once automatically in the process life. */
-    private boolean mHasShownHint;
-
-    @VisibleForTesting
-    @Inject
-    SizeCompatModeActivityController(Context context, TaskStackChangeListeners listeners,
-            CommandQueue commandQueue) {
-        super(context);
-        mCommandQueue = commandQueue;
-        listeners.registerTaskStackListener(new TaskStackChangeListener() {
-            @Override
-            public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
-                // Note the callback already runs on main thread.
-                updateRestartButton(displayId, activityToken);
-            }
-        });
-    }
-
-    @Override
-    public void start() {
-        mCommandQueue.addCallback(this);
-    }
-
-    @Override
-    public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
-            boolean showImeSwitcher) {
-        RestartActivityButton button = mActiveButtons.get(displayId);
-        if (button == null) {
-            return;
-        }
-        boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
-        int newVisibility = imeShown ? View.GONE : View.VISIBLE;
-        // Hide the button when input method is showing.
-        if (button.getVisibility() != newVisibility) {
-            button.setVisibility(newVisibility);
-        }
-    }
-
-    @Override
-    public void onDisplayRemoved(int displayId) {
-        mDisplayContextCache.remove(displayId);
-        removeRestartButton(displayId);
-    }
-
-    private void removeRestartButton(int displayId) {
-        RestartActivityButton button = mActiveButtons.get(displayId);
-        if (button != null) {
-            button.remove();
-            mActiveButtons.remove(displayId);
-        }
-    }
-
-    private void updateRestartButton(int displayId, IBinder activityToken) {
-        if (activityToken == null) {
-            // Null token means the current foreground activity is not in size compatibility mode.
-            removeRestartButton(displayId);
-            return;
-        }
-
-        RestartActivityButton restartButton = mActiveButtons.get(displayId);
-        if (restartButton != null) {
-            restartButton.updateLastTargetActivity(activityToken);
-            return;
-        }
-
-        Context context = getOrCreateDisplayContext(displayId);
-        if (context == null) {
-            Log.i(TAG, "Cannot get context for display " + displayId);
-            return;
-        }
-
-        restartButton = createRestartButton(context);
-        restartButton.updateLastTargetActivity(activityToken);
-        if (restartButton.show()) {
-            mActiveButtons.append(displayId, restartButton);
-        } else {
-            onDisplayRemoved(displayId);
-        }
-    }
-
-    @VisibleForTesting
-    RestartActivityButton createRestartButton(Context context) {
-        RestartActivityButton button = new RestartActivityButton(context, mHasShownHint);
-        mHasShownHint = true;
-        return button;
-    }
-
-    private Context getOrCreateDisplayContext(int displayId) {
-        if (displayId == Display.DEFAULT_DISPLAY) {
-            return mContext;
-        }
-        Context context = null;
-        WeakReference<Context> ref = mDisplayContextCache.get(displayId);
-        if (ref != null) {
-            context = ref.get();
-        }
-        if (context == null) {
-            Display display = mContext.getSystemService(DisplayManager.class).getDisplay(displayId);
-            if (display != null) {
-                context = mContext.createDisplayContext(display);
-                mDisplayContextCache.put(displayId, new WeakReference<Context>(context));
-            }
-        }
-        return context;
-    }
-
-    @VisibleForTesting
-    static class RestartActivityButton extends ImageButton implements View.OnClickListener,
-            View.OnLongClickListener {
-
-        final WindowManager.LayoutParams mWinParams;
-        final boolean mShouldShowHint;
-        IBinder mLastActivityToken;
-
-        final int mPopupOffsetX;
-        final int mPopupOffsetY;
-        PopupWindow mShowingHint;
-
-        RestartActivityButton(Context context, boolean hasShownHint) {
-            super(context);
-            mShouldShowHint = !hasShownHint;
-            Drawable drawable = context.getDrawable(R.drawable.btn_restart);
-            setImageDrawable(drawable);
-            setContentDescription(context.getString(R.string.restart_button_description));
-
-            int drawableW = drawable.getIntrinsicWidth();
-            int drawableH = drawable.getIntrinsicHeight();
-            mPopupOffsetX = drawableW / 2;
-            mPopupOffsetY = drawableH * 2;
-
-            ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
-            GradientDrawable mask = new GradientDrawable();
-            mask.setShape(GradientDrawable.OVAL);
-            mask.setColor(color);
-            setBackground(new RippleDrawable(color, null /* content */, mask));
-            setOnClickListener(this);
-            setOnLongClickListener(this);
-
-            mWinParams = new WindowManager.LayoutParams();
-            mWinParams.gravity = getGravity(getResources().getConfiguration().getLayoutDirection());
-            mWinParams.width = drawableW * 2;
-            mWinParams.height = drawableH * 2;
-            mWinParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-            mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-            mWinParams.format = PixelFormat.TRANSLUCENT;
-            mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-            mWinParams.setTitle(SizeCompatModeActivityController.class.getSimpleName()
-                    + context.getDisplayId());
-        }
-
-        void updateLastTargetActivity(IBinder activityToken) {
-            mLastActivityToken = activityToken;
-        }
-
-        /** @return {@code false} if the target display is invalid. */
-        boolean show() {
-            try {
-                getContext().getSystemService(WindowManager.class).addView(this, mWinParams);
-            } catch (WindowManager.InvalidDisplayException e) {
-                // The target display may have been removed when the callback has just arrived.
-                Log.w(TAG, "Cannot show on display " + getContext().getDisplayId(), e);
-                return false;
-            }
-            return true;
-        }
-
-        void remove() {
-            if (mShowingHint != null) {
-                mShowingHint.dismiss();
-            }
-            getContext().getSystemService(WindowManager.class).removeViewImmediate(this);
-        }
-
-        @Override
-        public void onClick(View v) {
-            try {
-                ActivityTaskManager.getService().restartActivityProcessIfVisible(
-                        mLastActivityToken);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Unable to restart activity", e);
-            }
-        }
-
-        @Override
-        public boolean onLongClick(View v) {
-            showHint();
-            return true;
-        }
-
-        @Override
-        protected void onAttachedToWindow() {
-            super.onAttachedToWindow();
-            if (mShouldShowHint) {
-                showHint();
-            }
-        }
-
-        @Override
-        public void setLayoutDirection(int layoutDirection) {
-            int gravity = getGravity(layoutDirection);
-            if (mWinParams.gravity != gravity) {
-                mWinParams.gravity = gravity;
-                if (mShowingHint != null) {
-                    mShowingHint.dismiss();
-                    showHint();
-                }
-                getContext().getSystemService(WindowManager.class).updateViewLayout(this,
-                        mWinParams);
-            }
-            super.setLayoutDirection(layoutDirection);
-        }
-
-        void showHint() {
-            if (mShowingHint != null) {
-                return;
-            }
-
-            View popupView = LayoutInflater.from(getContext()).inflate(
-                    R.layout.size_compat_mode_hint, null /* root */);
-            PopupWindow popupWindow = new PopupWindow(popupView,
-                    LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
-            popupWindow.setWindowLayoutType(mWinParams.type);
-            popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
-            popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
-            popupWindow.setClippingEnabled(false);
-            popupWindow.setOnDismissListener(() -> mShowingHint = null);
-            mShowingHint = popupWindow;
-
-            Button gotItButton = popupView.findViewById(R.id.got_it);
-            gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
-                    null /* content */, null /* mask */));
-            gotItButton.setOnClickListener(view -> popupWindow.dismiss());
-            popupWindow.showAtLocation(this, mWinParams.gravity, mPopupOffsetX, mPopupOffsetY);
-        }
-
-        private static int getGravity(int layoutDirection) {
-            return Gravity.BOTTOM
-                    | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 036fcf3..78f7966 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -127,7 +127,7 @@
                     }
 
                     // If SHOW_PEOPLE_SPACE is true, enable People Space widget provider.
-                    // TODO(b/170396074): Remove this when we don't need a widget anymore.
+                    // TODO(b/170396074): Migrate to new feature flag (go/silk-flags-howto)
                     try {
                         int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(),
                                 Settings.Global.SHOW_PEOPLE_SPACE, 1);
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
index 93a8df4..cd3d6a8 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpItem.java
@@ -26,8 +26,7 @@
     private String mPackageName;
     private long mTimeStarted;
     private StringBuilder mState;
-    // This is only used for items with mCode == AppOpsManager.OP_RECORD_AUDIO
-    private boolean mSilenced;
+    private boolean mIsDisabled;
 
     public AppOpItem(int code, int uid, String packageName, long timeStarted) {
         this.mCode = code;
@@ -58,16 +57,16 @@
         return mTimeStarted;
     }
 
-    public void setSilenced(boolean silenced) {
-        mSilenced = silenced;
+    public void setDisabled(boolean misDisabled) {
+        this.mIsDisabled = misDisabled;
     }
 
-    public boolean isSilenced() {
-        return mSilenced;
+    public boolean isDisabled() {
+        return mIsDisabled;
     }
 
     @Override
     public String toString() {
-        return mState.append(mSilenced).append(")").toString();
+        return mState.append(mIsDisabled).append(")").toString();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 1036c99..d8ca639 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.appops;
 
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
 import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
 
 import android.Manifest;
@@ -45,6 +47,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.util.Assert;
 
 import java.io.FileDescriptor;
@@ -64,7 +67,8 @@
 @SysUISingleton
 public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController,
         AppOpsManager.OnOpActiveChangedInternalListener,
-        AppOpsManager.OnOpNotedListener, Dumpable {
+        AppOpsManager.OnOpNotedListener, IndividualSensorPrivacyController.Callback,
+        Dumpable {
 
     // This is the minimum time that we will keep AppOps that are noted on record. If multiple
     // occurrences of the same (op, package, uid) happen in a shorter interval, they will not be
@@ -77,8 +81,8 @@
     private final AppOpsManager mAppOps;
     private final AudioManager mAudioManager;
     private final LocationManager mLocationManager;
-    // TODO ntmyren: remove t
     private final PackageManager mPackageManager;
+    private final IndividualSensorPrivacyController mSensorPrivacyController;
 
     // mLocationProviderPackages are cached and updated only occasionally
     private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
@@ -91,6 +95,7 @@
     private final PermissionFlagsCache mFlagsCache;
     private boolean mListening;
     private boolean mMicMuted;
+    private boolean mCameraDisabled;
 
     @GuardedBy("mActiveItems")
     private final List<AppOpItem> mActiveItems = new ArrayList<>();
@@ -118,6 +123,7 @@
             DumpManager dumpManager,
             PermissionFlagsCache cache,
             AudioManager audioManager,
+            IndividualSensorPrivacyController sensorPrivacyController,
             BroadcastDispatcher dispatcher
     ) {
         mDispatcher = dispatcher;
@@ -129,7 +135,10 @@
             mCallbacksByCode.put(OPS[i], new ArraySet<>());
         }
         mAudioManager = audioManager;
-        mMicMuted = audioManager.isMicrophoneMute();
+        mSensorPrivacyController = sensorPrivacyController;
+        mMicMuted = audioManager.isMicrophoneMute()
+                || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+        mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA);
         mLocationManager = context.getSystemService(LocationManager.class);
         mPackageManager = context.getPackageManager();
         dumpManager.registerDumpable(TAG, this);
@@ -147,6 +156,12 @@
             mAppOps.startWatchingActive(OPS, this);
             mAppOps.startWatchingNoted(OPS, this);
             mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
+            mSensorPrivacyController.addCallback(this);
+
+            mMicMuted = mAudioManager.isMicrophoneMute()
+                    || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+            mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA);
+
             mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
                     mAudioManager.getActiveRecordingConfigurations()));
             mDispatcher.registerReceiverWithHandler(this,
@@ -156,6 +171,7 @@
             mAppOps.stopWatchingActive(this);
             mAppOps.stopWatchingNoted(this);
             mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+            mSensorPrivacyController.removeCallback(this);
 
             mBGHandler.removeCallbacksAndMessages(null); // null removes all
             mDispatcher.unregisterReceiver(this);
@@ -235,11 +251,13 @@
             if (item == null && active) {
                 item = new AppOpItem(code, uid, packageName, System.currentTimeMillis());
                 if (code == AppOpsManager.OP_RECORD_AUDIO) {
-                    item.setSilenced(isAnyRecordingPausedLocked(uid));
+                    item.setDisabled(isAnyRecordingPausedLocked(uid));
+                } else if (code == AppOpsManager.OP_CAMERA) {
+                    item.setDisabled(mCameraDisabled);
                 }
                 mActiveItems.add(item);
                 if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
-                return !item.isSilenced();
+                return !item.isDisabled();
             } else if (item != null && !active) {
                 mActiveItems.remove(item);
                 if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
@@ -409,7 +427,7 @@
                 AppOpItem item = mActiveItems.get(i);
                 if ((userId == UserHandle.USER_ALL
                         || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item) && !item.isSilenced()) {
+                        && isUserVisible(item) && !item.isDisabled()) {
                     list.add(item);
                 }
             }
@@ -512,22 +530,27 @@
         return false;
     }
 
-    private void updateRecordingPausedStatus() {
+    private void updateSensorDisabledStatus() {
         synchronized (mActiveItems) {
             int size = mActiveItems.size();
             for (int i = 0; i < size; i++) {
                 AppOpItem item = mActiveItems.get(i);
+
+                boolean paused = false;
                 if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
-                    boolean paused = isAnyRecordingPausedLocked(item.getUid());
-                    if (item.isSilenced() != paused) {
-                        item.setSilenced(paused);
-                        notifySuscribers(
-                                item.getCode(),
-                                item.getUid(),
-                                item.getPackageName(),
-                                !item.isSilenced()
-                        );
-                    }
+                    paused = isAnyRecordingPausedLocked(item.getUid());
+                } else if (item.getCode() == AppOpsManager.OP_CAMERA) {
+                    paused = mCameraDisabled;
+                }
+
+                if (item.isDisabled() != paused) {
+                    item.setDisabled(paused);
+                    notifySuscribers(
+                            item.getCode(),
+                            item.getUid(),
+                            item.getPackageName(),
+                            !item.isDisabled()
+                    );
                 }
             }
         }
@@ -552,14 +575,27 @@
                     recordings.add(recording);
                 }
             }
-            updateRecordingPausedStatus();
+            updateSensorDisabledStatus();
         }
     };
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        mMicMuted = mAudioManager.isMicrophoneMute();
-        updateRecordingPausedStatus();
+        mMicMuted = mAudioManager.isMicrophoneMute()
+                || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE);
+        updateSensorDisabledStatus();
+    }
+
+    @Override
+    public void onSensorBlockedChanged(int sensor, boolean blocked) {
+        mBGHandler.post(() -> {
+            if (sensor == INDIVIDUAL_SENSOR_CAMERA) {
+                mCameraDisabled = blocked;
+            } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) {
+                mMicMuted = mAudioManager.isMicrophoneMute() || blocked;
+            }
+            updateSensorDisabledStatus();
+        });
     }
 
     protected class H extends Handler {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 055270d..7d06dd6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -23,7 +23,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
-import android.app.IActivityTaskManager;
 import android.app.TaskStackListener;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -137,7 +136,8 @@
                         mActivityTaskManager.getTasks(1);
                 if (!runningTasks.isEmpty()) {
                     final String topPackage = runningTasks.get(0).topActivity.getPackageName();
-                    if (!topPackage.contentEquals(clientPackage)) {
+                    if (!topPackage.contentEquals(clientPackage)
+                            && !Utils.isSystem(mContext, clientPackage)) {
                         Log.w(TAG, "Evicting client due to: " + topPackage);
                         mCurrentDialog.dismissWithoutCallback(true /* animate */);
                         mCurrentDialog = null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
new file mode 100644
index 0000000..2083f2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/HbmCallback.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.biometrics;
+
+import android.annotation.NonNull;
+import android.view.Surface;
+
+/**
+ * Interface for controlling the high-brightness mode (HBM). UdfpsView can use this callback to
+ * enable the HBM while showing the fingerprint illumination, and to disable the HBM after the
+ * illumination is no longer necessary.
+ */
+public interface HbmCallback {
+    /**
+     * UdfpsView will call this to enable the HBM before drawing the illumination dot.
+     *
+     * @param surface A valid surface for which the HBM should be enabled.
+     */
+    void enableHbm(@NonNull Surface surface);
+
+    /**
+     * UdfpsView will call this to disable the HBM when the illumination is not longer needed.
+     *
+     * @param surface A valid surface for which the HBM should be disabled.
+     */
+    void disableHbm(@NonNull Surface surface);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
index 5fc2e53..3ea8140 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java
@@ -47,4 +47,23 @@
                 (int) sensorRect.right - margin,
                 (int) sensorRect.bottom - margin);
     }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mFingerprintDrawable.setAlpha(alpha);
+    }
+
+    /**
+     * @return The amount of padding that's needed on each side of the sensor, in pixels.
+     */
+    public int getPaddingX() {
+        return 0;
+    }
+
+    /**
+     * @return The amount of padding that's needed on each side of the sensor, in pixels.
+     */
+    public int getPaddingY() {
+        return 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
index 1a2a492..5290986 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java
@@ -23,10 +23,12 @@
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.RectF;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
 /**
@@ -35,8 +37,12 @@
 public class UdfpsAnimationEnroll extends UdfpsAnimation {
     private static final String TAG = "UdfpsAnimationEnroll";
 
+    private static final float SHADOW_RADIUS = 5.f;
+    private static final float PROGRESS_BAR_RADIUS = 140.f;
+
     @Nullable private RectF mSensorRect;
     @NonNull private final Paint mSensorPaint;
+    private final int mNotificationShadeColor;
 
     UdfpsAnimationEnroll(@NonNull Context context) {
         super(context);
@@ -44,8 +50,11 @@
         mSensorPaint = new Paint(0 /* flags */);
         mSensorPaint.setAntiAlias(true);
         mSensorPaint.setColor(Color.WHITE);
-        mSensorPaint.setShadowLayer(UdfpsView.SENSOR_SHADOW_RADIUS, 0, 0, Color.BLACK);
+        mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK);
         mSensorPaint.setStyle(Paint.Style.FILL);
+
+        mNotificationShadeColor = Utils.getColorAttr(context,
+                android.R.attr.colorBackgroundFloating).getDefaultColor();
     }
 
     @Override
@@ -72,8 +81,19 @@
     }
 
     @Override
-    public void setAlpha(int alpha) {
+    public int getPaddingX() {
+        return (int) Math.ceil(PROGRESS_BAR_RADIUS);
+    }
 
+    @Override
+    public int getPaddingY() {
+        return (int) Math.ceil(PROGRESS_BAR_RADIUS);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        super.setAlpha(alpha);
+        mSensorPaint.setAlpha(alpha);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
index 7563e73..efc864a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java
@@ -44,11 +44,6 @@
     }
 
     @Override
-    public void setAlpha(int alpha) {
-
-    }
-
-    @Override
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
index 3e46a65..501de9d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java
@@ -99,11 +99,6 @@
     }
 
     @Override
-    public void setAlpha(int alpha) {
-
-    }
-
-    @Override
     public void setColorFilter(@Nullable ColorFilter colorFilter) {
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
new file mode 100644
index 0000000..41ea4d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 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.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+/**
+ * Class that coordinates non-HBM animations (such as enroll, keyguard, BiometricPrompt,
+ * FingerprintManager).
+ */
+public class UdfpsAnimationView extends View implements DozeReceiver,
+        StatusBar.ExpansionChangedListener {
+
+    private static final String TAG = "UdfpsAnimationView";
+
+    @NonNull private UdfpsView mParent;
+    @Nullable private UdfpsAnimation mUdfpsAnimation;
+    @NonNull private RectF mSensorRect;
+    private int mAlpha;
+
+    public UdfpsAnimationView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mSensorRect = new RectF();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (mUdfpsAnimation != null) {
+            final int alpha = mParent.shouldPauseAuth() ? mAlpha : 255;
+            mUdfpsAnimation.setAlpha(alpha);
+            mUdfpsAnimation.draw(canvas);
+        }
+    }
+
+    private int expansionToAlpha(float expansion) {
+        // Fade to 0 opacity when reaching this expansion amount
+        final float maxExpansion = 0.4f;
+
+        if (expansion >= maxExpansion) {
+            return 0; // transparent
+        }
+
+        final float percent = expansion / maxExpansion;
+        return (int) ((1 - percent) * 255);
+    }
+
+    void setParent(@NonNull UdfpsView parent) {
+        mParent = parent;
+    }
+
+    void setAnimation(@Nullable UdfpsAnimation animation) {
+        mUdfpsAnimation = animation;
+    }
+
+    void onSensorRectUpdated(@NonNull RectF sensorRect) {
+        mSensorRect = sensorRect;
+        if (mUdfpsAnimation != null) {
+            mUdfpsAnimation.onSensorRectUpdated(mSensorRect);
+        }
+    }
+
+    void updateColor() {
+        if (mUdfpsAnimation != null) {
+            mUdfpsAnimation.updateColor();
+        }
+    }
+
+    @Override
+    public void dozeTimeTick() {
+        if (mUdfpsAnimation instanceof DozeReceiver) {
+            ((DozeReceiver) mUdfpsAnimation).dozeTimeTick();
+        }
+    }
+
+    @Override
+    public void onExpansionChanged(float expansion, boolean expanded) {
+        mAlpha = expansionToAlpha(expansion);
+        postInvalidate();
+    }
+
+    public int getPaddingX() {
+        if (mUdfpsAnimation == null) {
+            return 0;
+        }
+        return mUdfpsAnimation.getPaddingX();
+    }
+
+    public int getPaddingY() {
+        if (mUdfpsAnimation == null) {
+            return 0;
+        }
+        return mUdfpsAnimation.getPaddingY();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index a2b2bea8..e7b08e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -22,39 +22,31 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.RectF;
-import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.MathUtils;
-import android.util.Spline;
+import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.internal.BrightnessSynchronizer;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.settings.SystemSettings;
-
-import java.io.FileWriter;
-import java.io.IOException;
 
 import javax.inject.Inject;
 
@@ -70,16 +62,14 @@
  * {@code sensorId} parameters.
  */
 @SuppressWarnings("deprecation")
-class UdfpsController implements DozeReceiver {
+@SysUISingleton
+public class UdfpsController implements DozeReceiver, HbmCallback {
     private static final String TAG = "UdfpsController";
-    // Gamma approximation for the sRGB color space.
-    private static final float DISPLAY_GAMMA = 2.2f;
     private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
 
     private final Context mContext;
     private final FingerprintManager mFingerprintManager;
     private final WindowManager mWindowManager;
-    private final SystemSettings mSystemSettings;
     private final DelayableExecutor mFgExecutor;
     private final StatusBarStateController mStatusBarStateController;
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
@@ -87,23 +77,6 @@
     @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
     private final WindowManager.LayoutParams mCoreLayoutParams;
     private final UdfpsView mView;
-    // Debugfs path to control the high-brightness mode.
-    private final String mHbmPath;
-    private final String mHbmEnableCommand;
-    private final String mHbmDisableCommand;
-    private final boolean mHbmSupported;
-    // Brightness in nits in the high-brightness mode.
-    private final float mMaxNits;
-    // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to a
-    // brightness in nits.
-    private final Spline mBacklightToNitsSpline;
-    // A spline mapping from a value in nits to a backlight value of a hypothetical panel whose
-    // maximum backlight value corresponds to our panel's high-brightness mode.
-    // The output is normalized to the range [0, 1.0].
-    private Spline mNitsToHbmBacklightSpline;
-    // Default non-HBM backlight value normalized to the range [0, 1.0]. Used as a fallback when the
-    // actual brightness value cannot be retrieved.
-    private final float mDefaultBrightness;
     // Indicates whether the overlay is currently showing. Even if it has been requested, it might
     // not be showing.
     private boolean mIsOverlayShowing;
@@ -111,6 +84,7 @@
     private boolean mIsOverlayRequested;
     // Reason the overlay has been requested. See IUdfpsOverlayController for definitions.
     private int mRequestReason;
+    @Nullable UdfpsEnrollHelper mEnrollHelper;
 
     // The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when
     // to turn off high brightness mode. To get around this limitation, the state of the AOD
@@ -122,6 +96,12 @@
     public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
         @Override
         public void showUdfpsOverlay(int sensorId, int reason) {
+            if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
+                    || reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
+                mEnrollHelper = new UdfpsEnrollHelper(reason);
+            } else {
+                mEnrollHelper = null;
+            }
             UdfpsController.this.showOverlay(reason);
         }
 
@@ -131,6 +111,16 @@
         }
 
         @Override
+        public void onEnrollmentProgress(int sensorId, int remaining) {
+            mView.onEnrollmentProgress(remaining);
+        }
+
+        @Override
+        public void onEnrollmentHelp(int sensorId) {
+            mView.onEnrollmentHelp();
+        }
+
+        @Override
         public void setDebugMessage(int sensorId, String message) {
             mView.setDebugMessage(message);
         }
@@ -139,7 +129,7 @@
     @SuppressLint("ClickableViewAccessibility")
     private final UdfpsView.OnTouchListener mOnTouchListener = (v, event) -> {
         UdfpsView view = (UdfpsView) v;
-        final boolean isFingerDown = view.isShowScrimAndDot();
+        final boolean isFingerDown = view.isIlluminationRequested();
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_MOVE:
@@ -166,21 +156,19 @@
     };
 
     @Inject
-    UdfpsController(@NonNull Context context,
+    public UdfpsController(@NonNull Context context,
             @Main Resources resources,
             LayoutInflater inflater,
             @Nullable FingerprintManager fingerprintManager,
-            DisplayManager displayManager,
             WindowManager windowManager,
-            SystemSettings systemSettings,
             @NonNull StatusBarStateController statusBarStateController,
-            @Main DelayableExecutor fgExecutor) {
+            @Main DelayableExecutor fgExecutor,
+            @Nullable StatusBar statusBar) {
         mContext = context;
         // The fingerprint manager is queried for UDFPS before this class is constructed, so the
         // fingerprint manager should never be null.
         mFingerprintManager = checkNotNull(fingerprintManager);
         mWindowManager = windowManager;
-        mSystemSettings = systemSettings;
         mFgExecutor = fgExecutor;
         mStatusBarStateController = statusBarStateController;
 
@@ -197,69 +185,17 @@
                 PixelFormat.TRANSLUCENT);
         mCoreLayoutParams.setTitle(TAG);
         mCoreLayoutParams.setFitInsetsTypes(0);
+        mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
         mCoreLayoutParams.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
         mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
         mView.setSensorProperties(mSensorProps);
+        mView.setHbmCallback(this);
 
-        mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
-        mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command);
-        mHbmDisableCommand = resources.getString(R.string.udfps_hbm_disable_command);
-
-        mHbmSupported = !TextUtils.isEmpty(mHbmPath);
-        mView.setHbmSupported(mHbmSupported);
-
-        // This range only consists of the minimum and maximum values, which only cover
-        // non-high-brightness mode.
-        float[] nitsRange = toFloatArray(resources.obtainTypedArray(
-                com.android.internal.R.array.config_screenBrightnessNits));
-        if (nitsRange.length < 2) {
-            throw new IllegalArgumentException(
-                    String.format("nitsRange.length: %d. Must be >= 2", nitsRange.length));
-        }
-
-        // The last value of this range corresponds to the high-brightness mode.
-        float[] nitsAutoBrightnessValues = toFloatArray(resources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNits));
-        if (nitsAutoBrightnessValues.length < 2) {
-            throw new IllegalArgumentException(
-                    String.format("nitsAutoBrightnessValues.length: %d. Must be >= 2",
-                            nitsAutoBrightnessValues.length));
-        }
-
-        mMaxNits = nitsAutoBrightnessValues[nitsAutoBrightnessValues.length - 1];
-        float[] hbmNitsRange = nitsRange.clone();
-        hbmNitsRange[hbmNitsRange.length - 1] = mMaxNits;
-
-        // This range only consists of the minimum and maximum backlight values, which only apply
-        // in non-high-brightness mode.
-        float[] normalizedBacklightRange = normalizeBacklightRange(
-                resources.getIntArray(
-                        com.android.internal.R.array.config_screenBrightnessBacklight));
-        if (normalizedBacklightRange.length < 2) {
-            throw new IllegalArgumentException(
-                    String.format("normalizedBacklightRange.length: %d. Must be >= 2",
-                            normalizedBacklightRange.length));
-        }
-        if (normalizedBacklightRange.length != nitsRange.length) {
-            throw new IllegalArgumentException(
-                    "normalizedBacklightRange.length != nitsRange.length");
-        }
-
-        mBacklightToNitsSpline = Spline.createSpline(normalizedBacklightRange, nitsRange);
-        mNitsToHbmBacklightSpline = Spline.createSpline(hbmNitsRange, normalizedBacklightRange);
-        mDefaultBrightness = obtainDefaultBrightness(mContext);
-
-        // TODO(b/160025856): move to the "dump" method.
-        Log.v(TAG, String.format("ctor | mNitsRange: [%f, %f]", nitsRange[0],
-                nitsRange[nitsRange.length - 1]));
-        Log.v(TAG, String.format("ctor | mHbmNitsRange: [%f, %f]", hbmNitsRange[0],
-                hbmNitsRange[hbmNitsRange.length - 1]));
-        Log.v(TAG, String.format("ctor | mNormalizedBacklightRange: [%f, %f]",
-                normalizedBacklightRange[0],
-                normalizedBacklightRange[normalizedBacklightRange.length - 1]));
+        statusBar.addExpansionChangedListener(mView);
+        statusBarStateController.addCallback(mView);
 
         mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController());
         mIsOverlayShowing = false;
@@ -314,14 +250,39 @@
         }
     }
 
-    private WindowManager.LayoutParams computeLayoutParams() {
+    private WindowManager.LayoutParams computeLayoutParams(@Nullable UdfpsAnimation animation) {
+        final int paddingX = animation != null ? animation.getPaddingX() : 0;
+        final int paddingY = animation != null ? animation.getPaddingY() : 0;
+
+        // Default dimensions assume portrait mode.
+        mCoreLayoutParams.x = mSensorProps.sensorLocationX - mSensorProps.sensorRadius - paddingX;
+        mCoreLayoutParams.y = mSensorProps.sensorLocationY - mSensorProps.sensorRadius - paddingY;
+        mCoreLayoutParams.height = 2 * mSensorProps.sensorRadius + 2 * paddingX;
+        mCoreLayoutParams.width = 2 * mSensorProps.sensorRadius + 2 * paddingY;
+
         Point p = new Point();
         // Gets the size based on the current rotation of the display.
         mContext.getDisplay().getRealSize(p);
-        mCoreLayoutParams.width = p.x;
-        mCoreLayoutParams.x = p.x;
-        mCoreLayoutParams.height = p.y;
-        mCoreLayoutParams.y = p.y;
+
+        // Transform dimensions if the device is in landscape mode.
+        switch (mContext.getDisplay().getRotation()) {
+            case Surface.ROTATION_90:
+                mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius
+                        - paddingX;
+                mCoreLayoutParams.y = p.y - mSensorProps.sensorLocationX - mSensorProps.sensorRadius
+                        - paddingY;
+                break;
+
+            case Surface.ROTATION_270:
+                mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius
+                        - paddingX;
+                mCoreLayoutParams.y = mSensorProps.sensorLocationX - mSensorProps.sensorRadius
+                        - paddingY;
+                break;
+
+            default:
+                // Do nothing to stay in portrait mode.
+        }
         return mCoreLayoutParams;
     }
 
@@ -341,8 +302,9 @@
             if (!mIsOverlayShowing) {
                 try {
                     Log.v(TAG, "showUdfpsOverlay | adding window");
-                    mView.setUdfpsAnimation(getUdfpsAnimationForReason(reason));
-                    mWindowManager.addView(mView, computeLayoutParams());
+                    final UdfpsAnimation animation = getUdfpsAnimationForReason(reason);
+                    mView.setExtras(animation, mEnrollHelper);
+                    mWindowManager.addView(mView, computeLayoutParams(animation));
                     mView.setOnTouchListener(mOnTouchListener);
                     mIsOverlayShowing = true;
                 } catch (RuntimeException e) {
@@ -358,7 +320,8 @@
     private UdfpsAnimation getUdfpsAnimationForReason(int reason) {
         Log.d(TAG, "getUdfpsAnimationForReason: " + reason);
         switch (reason) {
-            case IUdfpsOverlayController.REASON_ENROLL:
+            case IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR:
+            case IUdfpsOverlayController.REASON_ENROLL_ENROLLING:
                 return new UdfpsAnimationEnroll(mContext);
             case IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD:
                 return new UdfpsAnimationKeyguard(mView, mContext, mStatusBarStateController);
@@ -374,7 +337,7 @@
         mFgExecutor.execute(() -> {
             if (mIsOverlayShowing) {
                 Log.v(TAG, "hideUdfpsOverlay | removing window");
-                mView.setUdfpsAnimation(null);
+                mView.setExtras(null, null);
                 mView.setOnTouchListener(null);
                 // Reset the controller back to its starting state.
                 onFingerUp();
@@ -386,36 +349,10 @@
         });
     }
 
-    // Returns a value in the range of [0, 255].
-    private int computeScrimOpacity() {
-        // Backlight setting can be NaN, -1.0f, and [0.0f, 1.0f].
-        float backlightSetting = mSystemSettings.getFloatForUser(
-                Settings.System.SCREEN_BRIGHTNESS_FLOAT, mDefaultBrightness,
-                UserHandle.USER_CURRENT);
-
-        // Constrain the backlight setting to [0.0f, 1.0f].
-        float backlightValue = MathUtils.constrain(backlightSetting,
-                PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX);
-
-        // Interpolate the backlight value to nits.
-        float nits = mBacklightToNitsSpline.interpolate(backlightValue);
-
-        // Interpolate nits to a backlight value for a panel with enabled HBM.
-        float interpolatedHbmBacklightValue = mNitsToHbmBacklightSpline.interpolate(nits);
-
-        float gammaCorrectedHbmBacklightValue = (float) Math.pow(interpolatedHbmBacklightValue,
-                1.0f / DISPLAY_GAMMA);
-        float scrimOpacity = PowerManager.BRIGHTNESS_MAX - gammaCorrectedHbmBacklightValue;
-
-        // Interpolate the opacity value from [0.0f, 1.0f] to [0, 255].
-        return BrightnessSynchronizer.brightnessFloatToInt(scrimOpacity);
-    }
-
     /**
      * Request fingerprint scan.
      *
-     * This is intented to be called in response to a sensor that triggers an AOD interrupt for the
+     * This is intended to be called in response to a sensor that triggers an AOD interrupt for the
      * fingerprint sensor.
      */
     void onAodInterrupt(int screenX, int screenY, float major, float minor) {
@@ -435,7 +372,7 @@
     /**
      * Cancel fingerprint scan.
      *
-     * This is intented to be called after the fingerprint scan triggered by the AOD interrupt
+     * This is intended to be called after the fingerprint scan triggered by the AOD interrupt
      * either succeeds or fails.
      */
     void onCancelAodInterrupt() {
@@ -450,61 +387,27 @@
         onFingerUp();
     }
 
+    // This method can be called from the UI thread.
     private void onFingerDown(int x, int y, float minor, float major) {
-        if (mHbmSupported) {
-            try {
-                FileWriter fw = new FileWriter(mHbmPath);
-                fw.write(mHbmEnableCommand);
-                fw.close();
-            } catch (IOException e) {
-                mView.hideScrimAndDot();
-                Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage());
-            }
-        }
-        mView.setScrimAlpha(computeScrimOpacity());
-        mView.setRunAfterShowingScrimAndDot(() -> {
-            mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major);
-        });
-        mView.showScrimAndDot();
+        mView.startIllumination(() ->
+                mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major));
     }
 
+    // This method can be called from the UI thread.
     private void onFingerUp() {
         mFingerprintManager.onPointerUp(mSensorProps.sensorId);
-        // Hiding the scrim before disabling HBM results in less noticeable flicker.
-        mView.hideScrimAndDot();
-        if (mHbmSupported) {
-            try {
-                FileWriter fw = new FileWriter(mHbmPath);
-                fw.write(mHbmDisableCommand);
-                fw.close();
-            } catch (IOException e) {
-                mView.showScrimAndDot();
-                Log.e(TAG, "onFingerUp | failed to disable HBM: " + e.getMessage());
-            }
-        }
+        mView.stopIllumination();
     }
 
-    private static float obtainDefaultBrightness(Context context) {
-        return MathUtils.constrain(context.getDisplay().getBrightnessDefault(),
-                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+    @Override
+    public void enableHbm(@NonNull Surface surface) {
+        // Do nothing. This method can be implemented for devices that require the high-brightness
+        // mode for fingerprint illumination.
     }
 
-    private static float[] toFloatArray(TypedArray array) {
-        final int n = array.length();
-        float[] vals = new float[n];
-        for (int i = 0; i < n; i++) {
-            vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT);
-        }
-        array.recycle();
-        return vals;
-    }
-
-    private static float[] normalizeBacklightRange(int[] backlight) {
-        final int n = backlight.length;
-        float[] normalizedBacklight = new float[n];
-        for (int i = 0; i < n; i++) {
-            normalizedBacklight[i] = BrightnessSynchronizer.brightnessIntToFloat(backlight[i]);
-        }
-        return normalizedBacklight;
+    @Override
+    public void disableHbm(@NonNull Surface surface) {
+        // Do nothing. This method can be implemented for devices that require the high-brightness
+        // mode for fingerprint illumination.
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
new file mode 100644
index 0000000..2442633
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 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.biometrics;
+
+import android.hardware.fingerprint.IUdfpsOverlayController;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Helps keep track of enrollment state and animates the progress bar accordingly.
+ */
+public class UdfpsEnrollHelper {
+    private static final String TAG = "UdfpsEnrollHelper";
+
+    // IUdfpsOverlayController reason
+    private final int mEnrollReason;
+
+    private int mTotalSteps = -1;
+    private int mCurrentProgress = 0;
+
+    public UdfpsEnrollHelper(int reason) {
+        mEnrollReason = reason;
+    }
+
+    boolean shouldShowProgressBar() {
+        return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
+    }
+
+    void onEnrollmentProgress(int remaining, @NonNull UdfpsProgressBar progressBar) {
+        if (mTotalSteps == -1) {
+            mTotalSteps = remaining;
+        }
+
+        mCurrentProgress = progressBar.getMax() * Math.max(0, mTotalSteps + 1 - remaining)
+                / (mTotalSteps + 1);
+        progressBar.setProgress(mCurrentProgress, true /* animate */);
+    }
+
+    void updateProgress(@NonNull UdfpsProgressBar progressBar) {
+        progressBar.setProgress(mCurrentProgress);
+    }
+
+    void onEnrollmentHelp() {
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java
new file mode 100644
index 0000000..8bea05b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsIlluminator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 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.biometrics;
+
+import android.annotation.Nullable;
+
+/**
+ * Interface that should be implemented by UI's that need to coordinate user touches,
+ * views/animations, and modules that start/stop display illumination.
+ */
+interface UdfpsIlluminator {
+    /**
+     * @param callback Invoked when HBM should be enabled or disabled.
+     */
+    void setHbmCallback(@Nullable HbmCallback callback);
+
+    /**
+     * Invoked when illumination should start.
+     * @param onIlluminatedRunnable Invoked when the display has been illuminated.
+     */
+    void startIllumination(@Nullable Runnable onIlluminatedRunnable);
+
+    /**
+     * Invoked when illumination should end.
+     */
+    void stopIllumination();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsProgressBar.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsProgressBar.java
new file mode 100644
index 0000000..84e2fab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsProgressBar.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.biometrics;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ProgressBar;
+
+import com.android.systemui.R;
+
+/**
+ * A (determinate) progress bar in the form of a ring. The progress bar goes clockwise starting
+ * from the 12 o'clock position. This view maintain equal width and height using a strategy similar
+ * to "centerInside" for ImageView.
+ */
+public class UdfpsProgressBar extends ProgressBar {
+
+    public UdfpsProgressBar(Context context) {
+        this(context, null);
+    }
+
+    public UdfpsProgressBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UdfpsProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, R.style.UdfpsProgressBarStyle);
+    }
+
+    public UdfpsProgressBar(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        final int measuredHeight = getMeasuredHeight();
+        final int measuredWidth = getMeasuredWidth();
+
+        final int length = Math.min(measuredHeight, measuredWidth);
+        setMeasuredDimension(length, length);
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
new file mode 100644
index 0000000..97c215e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsSurfaceView.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 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.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * Under-display fingerprint sensor Surface View. The surface should be used for HBM-specific things
+ * only. All other animations should be done on the other view.
+ */
+public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator {
+    private static final String TAG = "UdfpsSurfaceView";
+
+    /**
+     * This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
+     * several abstract methods that are not used here but require implementation.
+     */
+    private interface SimpleDrawable {
+        void draw(Canvas canvas);
+    }
+
+    @NonNull private final SurfaceHolder mHolder;
+    @NonNull private final Paint mSensorPaint;
+    @NonNull private final SimpleDrawable mIlluminationDotDrawable;
+
+    @NonNull private RectF mSensorRect;
+    @Nullable private HbmCallback mHbmCallback;
+
+    public UdfpsSurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mHolder = getHolder();
+        mHolder.setFormat(PixelFormat.RGBA_8888);
+
+        mSensorRect = new RectF();
+        mSensorPaint = new Paint(0 /* flags */);
+        mSensorPaint.setAntiAlias(true);
+        mSensorPaint.setARGB(255, 255, 255, 255);
+        mSensorPaint.setStyle(Paint.Style.FILL);
+
+        mIlluminationDotDrawable = canvas -> canvas.drawOval(mSensorRect, mSensorPaint);
+    }
+
+    @Override
+    public void setHbmCallback(@Nullable HbmCallback callback) {
+        mHbmCallback = callback;
+    }
+
+    @Override
+    public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
+        if (mHbmCallback != null && mHolder.getSurface().isValid()) {
+            mHbmCallback.enableHbm(mHolder.getSurface());
+        }
+        drawImmediately(mIlluminationDotDrawable);
+
+        if (onIlluminatedRunnable != null) {
+            // No framework API can reliably tell when a frame reaches the panel. A timeout is the
+            // safest solution. The frame should be displayed within 3 refresh cycles, which on a
+            // 60 Hz panel equates to 50 milliseconds.
+            postDelayed(onIlluminatedRunnable, 50 /* delayMillis */);
+        }
+    }
+
+    @Override
+    public void stopIllumination() {
+        if (mHbmCallback != null && mHolder.getSurface().isValid()) {
+            mHbmCallback.disableHbm(mHolder.getSurface());
+        }
+
+        invalidate();
+    }
+
+    void onSensorRectUpdated(@NonNull RectF sensorRect) {
+        mSensorRect = sensorRect;
+    }
+
+    /**
+     * Immediately draws the provided drawable on this SurfaceView's surface.
+     */
+    private void drawImmediately(@NonNull SimpleDrawable drawable) {
+        Canvas canvas = null;
+        try {
+            canvas = mHolder.lockCanvas();
+            drawable.draw(canvas);
+        } finally {
+            // Make sure the surface is never left in a bad state.
+            if (canvas != null) {
+                mHolder.unlockCanvasAndPost(canvas);
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index 4cb8101..00cb28b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.biometrics;
 
+import static com.android.systemui.statusbar.StatusBarState.FULLSCREEN_USER_SWITCHER;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -23,58 +27,46 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.Rect;
 import android.graphics.RectF;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.Surface;
+import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
 
 import com.android.systemui.R;
 import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.StatusBar;
 
 /**
- * A full screen view with a configurable illumination dot and scrim.
+ * A view containing 1) A SurfaceView for HBM, and 2) A normal drawable view for all other
+ * animations.
  */
-public class UdfpsView extends View implements DozeReceiver {
+public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator,
+        StatusBarStateController.StateListener, StatusBar.ExpansionChangedListener {
     private static final String TAG = "UdfpsView";
 
-    // Values in pixels.
-    public static final float SENSOR_SHADOW_RADIUS = 2.0f;
-
     private static final int DEBUG_TEXT_SIZE_PX = 32;
 
-    @NonNull private final Rect mScrimRect;
-    @NonNull private final Paint mScrimPaint;
+    @NonNull private final UdfpsSurfaceView mHbmSurfaceView;
+    @NonNull private final UdfpsAnimationView mAnimationView;
+    @NonNull private final RectF mSensorRect;
     @NonNull private final Paint mDebugTextPaint;
 
-    @NonNull private final RectF mSensorRect;
-    @NonNull private final Paint mSensorPaint;
-    private final float mSensorTouchAreaCoefficient;
-
-
-    // Stores rounded up values from mSensorRect. Necessary for APIs that only take Rect (not RecF).
-    @NonNull private final Rect mTouchableRegion;
-    // mInsetsListener is used to set the touchable region for our window. Our window covers the
-    // whole screen, and by default its touchable region is the whole screen. We use
-    // mInsetsListener to restrict the touchable region and allow the touches outside of the sensor
-    // to propagate to the rest of the UI.
-    @NonNull private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener;
-    @Nullable private UdfpsAnimation mUdfpsAnimation;
+    @Nullable private UdfpsProgressBar mProgressBar;
 
     // Used to obtain the sensor location.
     @NonNull private FingerprintSensorPropertiesInternal mSensorProps;
 
-    private boolean mShowScrimAndDot;
-    private boolean mIsHbmSupported;
+    private final float mSensorTouchAreaCoefficient;
     @Nullable private String mDebugMessage;
-
-    // Runnable that will be run after the illumination dot and scrim are shown.
-    // The runnable is reset to null after it's executed once.
-    @Nullable private Runnable mRunAfterShowingScrimAndDot;
+    private boolean mIlluminationRequested;
+    private int mStatusBarState;
+    private boolean mNotificationShadeExpanded;
+    @Nullable private UdfpsEnrollHelper mEnrollHelper;
 
     public UdfpsView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -92,93 +84,86 @@
             a.recycle();
         }
 
+        // Inflate UdfpsSurfaceView
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        mHbmSurfaceView = (UdfpsSurfaceView) inflater.inflate(R.layout.udfps_surface_view,
+                null, false);
+        addView(mHbmSurfaceView);
+        mHbmSurfaceView.setVisibility(View.INVISIBLE);
 
-        mScrimRect = new Rect();
-        mScrimPaint = new Paint(0 /* flags */);
-        mScrimPaint.setColor(Color.BLACK);
+        // Inflate UdfpsAnimationView
+        mAnimationView = (UdfpsAnimationView) inflater.inflate(R.layout.udfps_animation_view,
+                null, false);
+        mAnimationView.setParent(this);
+        addView(mAnimationView);
 
         mSensorRect = new RectF();
-        mSensorPaint = new Paint(0 /* flags */);
-        mSensorPaint.setAntiAlias(true);
-        mSensorPaint.setColor(Color.WHITE);
-        mSensorPaint.setShadowLayer(SENSOR_SHADOW_RADIUS, 0, 0, Color.BLACK);
-        mSensorPaint.setStyle(Paint.Style.FILL);
 
         mDebugTextPaint = new Paint();
         mDebugTextPaint.setAntiAlias(true);
         mDebugTextPaint.setColor(Color.BLUE);
         mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
 
-        mTouchableRegion = new Rect();
-        // When the device is rotated, it's important that mTouchableRegion is updated before
-        // this listener is called. This listener is usually called shortly after onLayout.
-        mInsetsListener = internalInsetsInfo -> {
-            internalInsetsInfo.setTouchableInsets(
-                    ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-            internalInsetsInfo.touchableRegion.set(mTouchableRegion);
-        };
-
-        mShowScrimAndDot = false;
+        mIlluminationRequested = false;
     }
 
     void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
         mSensorProps = properties;
     }
 
-    void setUdfpsAnimation(@Nullable UdfpsAnimation animation) {
-        mUdfpsAnimation = animation;
+    void setExtras(@Nullable UdfpsAnimation animation, @Nullable UdfpsEnrollHelper enrollHelper) {
+        mAnimationView.setAnimation(animation);
+        mEnrollHelper = enrollHelper;
+
+        if (enrollHelper != null) {
+            mEnrollHelper.updateProgress(mProgressBar);
+            mProgressBar.setVisibility(enrollHelper.shouldShowProgressBar()
+                    ? View.VISIBLE : View.GONE);
+        } else {
+            mProgressBar.setVisibility(View.GONE);
+        }
     }
 
+    @Override
+    public void setHbmCallback(@Nullable HbmCallback callback) {
+        mHbmSurfaceView.setHbmCallback(callback);
+    }
 
     @Override
     public void dozeTimeTick() {
-        if (mUdfpsAnimation instanceof DozeReceiver) {
-            ((DozeReceiver) mUdfpsAnimation).dozeTimeTick();
-        }
+        mAnimationView.dozeTimeTick();
     }
 
-    // The "h" and "w" are the display's height and width relative to its current rotation.
-    protected void updateSensorRect(int h, int w) {
-        // mSensorProps coordinates assume portrait mode.
-        mSensorRect.set(mSensorProps.sensorLocationX - mSensorProps.sensorRadius,
-                mSensorProps.sensorLocationY - mSensorProps.sensorRadius,
-                mSensorProps.sensorLocationX + mSensorProps.sensorRadius,
-                mSensorProps.sensorLocationY + mSensorProps.sensorRadius);
+    @Override
+    public void onExpandedChanged(boolean isExpanded) {
+        mNotificationShadeExpanded = isExpanded;
+    }
 
-        // Transform mSensorRect if the device is in landscape mode.
-        switch (mContext.getDisplay().getRotation()) {
-            case Surface.ROTATION_90:
-                mSensorRect.set(mSensorRect.top, h - mSensorRect.right, mSensorRect.bottom,
-                        h - mSensorRect.left);
-                break;
-            case Surface.ROTATION_270:
-                mSensorRect.set(w - mSensorRect.bottom, mSensorRect.left, w - mSensorRect.top,
-                        mSensorRect.right);
-                break;
-            default:
-                // Do nothing to stay in portrait mode.
-        }
+    @Override
+    public void onStateChanged(int newState) {
+        mStatusBarState = newState;
+    }
 
-        if (mUdfpsAnimation != null) {
-            mUdfpsAnimation.onSensorRectUpdated(new RectF(mSensorRect));
-        }
+    @Override
+    public void onExpansionChanged(float expansion, boolean expanded) {
+        mAnimationView.onExpansionChanged(expansion, expanded);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mProgressBar = findViewById(R.id.progress_bar);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
-        // Always re-compute the layout regardless of whether "changed" is true. It is usually false
-        // when the device goes from landscape to seascape and vice versa, but mSensorRect and
-        // its dependencies need to be recalculated to stay at the same physical location on the
-        // screen.
-        final int w = getLayoutParams().width;
-        final int h = getLayoutParams().height;
-        mScrimRect.set(0 /* left */, 0 /* top */, w, h);
-        updateSensorRect(h, w);
-        // Update mTouchableRegion with the rounded up values from mSensorRect. After "onLayout"
-        // is finished, mTouchableRegion will be used by mInsetsListener to compute the touch
-        // insets.
-        mSensorRect.roundOut(mTouchableRegion);
+        mSensorRect.set(0 + mAnimationView.getPaddingX(),
+                0 + mAnimationView.getPaddingY(),
+                2 * mSensorProps.sensorRadius + mAnimationView.getPaddingX(),
+                2 * mSensorProps.sensorRadius + mAnimationView.getPaddingY());
+
+        mHbmSurfaceView.onSensorRectUpdated(new RectF(mSensorRect));
+        mAnimationView.onSensorRectUpdated(new RectF(mSensorRect));
     }
 
     @Override
@@ -187,69 +172,34 @@
         Log.v(TAG, "onAttachedToWindow");
 
         // Retrieve the colors each time, since it depends on day/night mode
-        updateColor();
-
-        getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
-    }
-
-    private void updateColor() {
-        if (mUdfpsAnimation != null) {
-            mUdfpsAnimation.updateColor();
-        }
+        mAnimationView.updateColor();
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         Log.v(TAG, "onDetachedFromWindow");
-        getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-
-        if (mShowScrimAndDot && mIsHbmSupported) {
-            // Only draw the scrim if HBM is supported.
-            canvas.drawRect(mScrimRect, mScrimPaint);
-        }
-
-        if (!TextUtils.isEmpty(mDebugMessage)) {
-            canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
-        }
-
-        if (mShowScrimAndDot) {
-            // draw dot (white circle)
-            canvas.drawOval(mSensorRect, mSensorPaint);
-        } else {
-            if (mUdfpsAnimation != null) {
-                mUdfpsAnimation.draw(canvas);
+        if (!mIlluminationRequested) {
+            if (!TextUtils.isEmpty(mDebugMessage)) {
+                canvas.drawText(mDebugMessage, 0, 160, mDebugTextPaint);
             }
         }
-
-        if (mShowScrimAndDot && mRunAfterShowingScrimAndDot != null) {
-            post(mRunAfterShowingScrimAndDot);
-            mRunAfterShowingScrimAndDot = null;
-        }
     }
 
     RectF getSensorRect() {
         return new RectF(mSensorRect);
     }
 
-    void setHbmSupported(boolean value) {
-        mIsHbmSupported = value;
-    }
-
     void setDebugMessage(String message) {
         mDebugMessage = message;
         postInvalidate();
     }
 
-    void setRunAfterShowingScrimAndDot(Runnable runnable) {
-        mRunAfterShowingScrimAndDot = runnable;
-    }
-
     boolean isValidTouch(float x, float y, float pressure) {
         // The X and Y coordinates of the sensor's center.
         final float cx = mSensorRect.centerX();
@@ -261,24 +211,49 @@
         return x > (cx - rx * mSensorTouchAreaCoefficient)
                 && x < (cx + rx * mSensorTouchAreaCoefficient)
                 && y > (cy - ry * mSensorTouchAreaCoefficient)
-                && y < (cy + ry * mSensorTouchAreaCoefficient);
+                && y < (cy + ry * mSensorTouchAreaCoefficient)
+                && !shouldPauseAuth();
     }
 
-    void setScrimAlpha(int alpha) {
-        mScrimPaint.setAlpha(alpha);
+    /**
+     * States where UDFPS should temporarily not be authenticating. Instead of completely stopping
+     * authentication which would cause the UDFPS icons to abruptly disappear, do it here by not
+     * sending onFingerDown and smoothly animating away.
+     */
+    boolean shouldPauseAuth() {
+        return (mNotificationShadeExpanded && mStatusBarState != KEYGUARD)
+                || mStatusBarState == SHADE_LOCKED
+                || mStatusBarState == FULLSCREEN_USER_SWITCHER;
     }
 
-    boolean isShowScrimAndDot() {
-        return mShowScrimAndDot;
+    boolean isIlluminationRequested() {
+        return mIlluminationRequested;
     }
 
-    void showScrimAndDot() {
-        mShowScrimAndDot = true;
-        invalidate();
+    /**
+     * @param onIlluminatedRunnable Runs when the first illumination frame reaches the panel.
+     */
+    @Override
+    public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
+        mIlluminationRequested = true;
+        mAnimationView.setVisibility(View.INVISIBLE);
+        mHbmSurfaceView.setVisibility(View.VISIBLE);
+        mHbmSurfaceView.startIllumination(onIlluminatedRunnable);
     }
 
-    void hideScrimAndDot() {
-        mShowScrimAndDot = false;
-        invalidate();
+    @Override
+    public void stopIllumination() {
+        mIlluminationRequested = false;
+        mAnimationView.setVisibility(View.VISIBLE);
+        mHbmSurfaceView.setVisibility(View.INVISIBLE);
+        mHbmSurfaceView.stopIllumination();
+    }
+
+    void onEnrollmentProgress(int remaining) {
+        mEnrollHelper.onEnrollmentProgress(remaining, mProgressBar);
+    }
+
+    void onEnrollmentHelp() {
+
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index fd5e85a..076c7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -16,13 +16,16 @@
 
 package com.android.systemui.biometrics;
 
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorPropertiesInternal;
 import android.os.UserManager;
@@ -116,4 +119,10 @@
 
         return false;
     }
+
+    static boolean isSystem(@NonNull Context context, @Nullable String clientPackage) {
+        final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
+                == PackageManager.PERMISSION_GRANTED;
+        return hasPermission && "android".equals(clientPackage);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
new file mode 100644
index 0000000..8e878cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.controls.ui
+
+import android.app.Dialog
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+
+import com.android.systemui.Interpolators
+import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
+
+/**
+ * Show the controls space inside a dialog, as from the lock screen.
+ */
+class ControlsDialog(
+    thisContext: Context,
+    val broadcastDispatcher: BroadcastDispatcher
+) : Dialog(thisContext, R.style.Theme_SystemUI_Dialog_Control_LockScreen) {
+
+    private val receiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            val action = intent.getAction()
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+                dismiss()
+            }
+        }
+    }
+
+    init {
+        window.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+        window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+                or WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+
+        setContentView(R.layout.controls_in_dialog)
+
+        requireViewById<ViewGroup>(R.id.control_detail_root).apply {
+            setOnClickListener { dismiss() }
+            (getParent() as View).setOnClickListener { dismiss() }
+        }
+    }
+
+    fun show(
+        controller: ControlsUiController
+    ): ControlsDialog {
+        super.show()
+
+        val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls)
+        vg.alpha = 0f
+        controller.show(vg, { /* do nothing */ })
+
+        vg.animate()
+            .alpha(1f)
+            .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+            .setDuration(300)
+
+        val filter = IntentFilter()
+        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
+        broadcastDispatcher.registerReceiver(receiver, filter)
+
+        return this
+    }
+
+    override fun dismiss() {
+        broadcastDispatcher.unregisterReceiver(receiver)
+
+        if (!isShowing()) return
+
+        super.dismiss()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 55359ea..e5c9d10 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -18,7 +18,6 @@
 
 import com.android.systemui.LatencyTester;
 import com.android.systemui.ScreenDecorations;
-import com.android.systemui.SizeCompatModeActivityController;
 import com.android.systemui.SliceBroadcastRelayHandler;
 import com.android.systemui.SystemUI;
 import com.android.systemui.accessibility.SystemActions;
@@ -113,13 +112,6 @@
     @ClassKey(ShortcutKeyDispatcher.class)
     public abstract SystemUI bindsShortcutKeyDispatcher(ShortcutKeyDispatcher sysui);
 
-    /** Inject into SizeCompatModeActivityController. */
-    @Binds
-    @IntoMap
-    @ClassKey(SizeCompatModeActivityController.class)
-    public abstract SystemUI bindsSizeCompatModeActivityController(
-            SizeCompatModeActivityController sysui);
-
     /** Inject into SliceBroadcastRelayHandler. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4d69700..b0067cd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.assist.AssistModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.controls.dagger.ControlsModule;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.dagger.DemoModeModule;
 import com.android.systemui.doze.dagger.DozeComponent;
 import com.android.systemui.dump.DumpManager;
@@ -75,6 +76,7 @@
 import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import dagger.Binds;
 import dagger.BindsOptionalOf;
@@ -153,6 +155,7 @@
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
 
+    // TODO: This should provided by the WM component
     /** Provides Optional of BubbleManager */
     @SysUISingleton
     @Provides
@@ -166,11 +169,12 @@
             ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager,
             NotificationGroupManagerLegacy groupManager, NotificationEntryManager entryManager,
             NotifPipeline notifPipeline, SysUiState sysUiState, FeatureFlags featureFlags,
-            DumpManager dumpManager) {
+            DumpManager dumpManager, @Main Executor sysuiMainExecutor) {
         return Optional.ofNullable(BubblesManager.create(context, bubblesOptional,
                 notificationShadeWindowController, statusBarStateController, shadeController,
                 configurationController, statusBarService, notificationManager,
                 interruptionStateProvider, zenModeController, notifUserManager,
-                groupManager, entryManager, notifPipeline, sysUiState, featureFlags, dumpManager));
+                groupManager, entryManager, notifPipeline, sysUiState, featureFlags, dumpManager,
+                sysuiMainExecutor));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 1b033e9..17f7ccf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -17,7 +17,13 @@
 package com.android.systemui.keyguard;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
 
+import android.app.ActivityTaskManager;
 import android.app.Service;
 import android.content.Intent;
 import android.os.Binder;
@@ -26,8 +32,17 @@
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.util.Log;
+import android.util.Slog;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationDefinition;
+import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -43,6 +58,21 @@
     static final String TAG = "KeyguardService";
     static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
 
+    /**
+     * Run Keyguard animation as remote animation in System UI instead of local animation in
+     * the server process.
+     *
+     * Note: Must be consistent with WindowManagerService.
+     */
+    private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
+            "persist.wm.enable_remote_keyguard_animation";
+
+    /**
+     * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
+     */
+    private static boolean sEnableRemoteKeyguardAnimation =
+            SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
+
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
 
@@ -52,6 +82,21 @@
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
+
+        if (sEnableRemoteKeyguardAnimation) {
+            RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+            final RemoteAnimationAdapter exitAnimationAdapter =
+                    new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
+            definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, exitAnimationAdapter);
+            definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                    exitAnimationAdapter);
+            final RemoteAnimationAdapter occludeAnimationAdapter =
+                    new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0);
+            definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE, occludeAnimationAdapter);
+            definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, occludeAnimationAdapter);
+            ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
+                    DEFAULT_DISPLAY, definition);
+        }
     }
 
     @Override
@@ -76,6 +121,48 @@
         }
     }
 
+    private final IRemoteAnimationRunner.Stub mExitAnimationRunner =
+            new IRemoteAnimationRunner.Stub() {
+        @Override // Binder interface
+        public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
+                RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation");
+            checkPermission();
+            mKeyguardViewMediator.startKeyguardExitAnimation(transit, apps, wallpapers,
+                    null /* nonApps */, finishedCallback);
+            Trace.endSection();
+        }
+
+        @Override // Binder interface
+        public void onAnimationCancelled() {
+        }
+    };
+
+    private final IRemoteAnimationRunner.Stub mOccludeAnimationRunner =
+            new IRemoteAnimationRunner.Stub() {
+        @Override // Binder interface
+        public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                       RemoteAnimationTarget[] apps,
+                       RemoteAnimationTarget[] wallpapers,
+                        RemoteAnimationTarget[] nonApps,
+                        IRemoteAnimationFinishedCallback finishedCallback) {
+            // TODO(bc-unlock): Calls KeyguardViewMediator#setOccluded to update the state and
+            // run animation.
+            try {
+                finishedCallback.onAnimationFinished();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException");
+            }
+        }
+
+        @Override // Binder interface
+        public void onAnimationCancelled() {
+        }
+    };
+
     private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {
 
         @Override // Binder interface
@@ -225,6 +312,11 @@
             mKeyguardViewMediator.onBootCompleted();
         }
 
+        /**
+         * @deprecated When remote animation is enabled, this won't be called anymore. Use
+         * {@code IRemoteAnimationRunner#onAnimationStart} instead.
+         */
+        @Deprecated
         @Override
         public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
             Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation");
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e732669..5a918d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -27,6 +27,9 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
@@ -65,8 +68,13 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseIntArray;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationTarget;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
@@ -85,6 +93,7 @@
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Interpolators;
 import com.android.systemui.SystemUI;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollector;
@@ -1318,6 +1327,7 @@
             if (mHiding && isOccluded) {
                 // We're in the process of going away but WindowManager wants to show a
                 // SHOW_WHEN_LOCKED activity instead.
+                // TODO(bc-unlock): Migrate to remote animation.
                 startKeyguardExitAnimation(0, 0);
             }
 
@@ -1703,7 +1713,9 @@
                     Trace.beginSection(
                             "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
-                    handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
+                    handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration,
+                            params.mApps, params.mWallpapers, params.mNonApps,
+                            params.mFinishedCallback);
                     mFalsingCollector.onSuccessfulUnlock();
                     Trace.endSection();
                     break;
@@ -1990,15 +2002,19 @@
             if (mShowing && !mOccluded) {
                 mKeyguardGoingAwayRunnable.run();
             } else {
+                // TODO(bc-unlock): Fill parameters
                 handleStartKeyguardExitAnimation(
                         SystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
-                        mHideAnimation.getDuration());
+                        mHideAnimation.getDuration(), null /* apps */,  null /* wallpapers */,
+                        null /* nonApps */, null /* finishedCallback */);
             }
         }
         Trace.endSection();
     }
 
-    private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+    private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration,
+            RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+            RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
         Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
         if (DEBUG) Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
                 + " fadeoutDuration=" + fadeoutDuration);
@@ -2031,6 +2047,49 @@
             mWakeAndUnlocking = false;
             mDismissCallbackRegistry.notifyDismissSucceeded();
             mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+
+            // TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
+            // apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
+            // supported, so it's always null.
+            mContext.getMainExecutor().execute(() -> {
+                if (finishedCallback == null) {
+                    return;
+                }
+
+                // TODO(bc-unlock): Sample animation, just to apply alpha animation on the app.
+                final SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier(
+                        mKeyguardViewControllerLazy.get().getViewRootImpl().getView());
+                final RemoteAnimationTarget primary = apps[0];
+                ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+                anim.setDuration(400 /* duration */);
+                anim.setInterpolator(Interpolators.LINEAR);
+                anim.addUpdateListener((ValueAnimator animation) -> {
+                    SurfaceParams params = new SurfaceParams.Builder(primary.leash)
+                            .withAlpha(animation.getAnimatedFraction())
+                            .build();
+                    applier.scheduleApply(params);
+                });
+                anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        try {
+                            finishedCallback.onAnimationFinished();
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "RemoteException");
+                        }
+                    }
+
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        try {
+                            finishedCallback.onAnimationFinished();
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "RemoteException");
+                        }
+                    }
+                });
+                anim.start();
+            });
             resetKeyguardDonePendingLocked();
             mHideAnimationRun = false;
             adjustStatusBarLocked();
@@ -2223,10 +2282,55 @@
         return mKeyguardViewControllerLazy.get();
     }
 
+    /**
+     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * the wallpaper and keyguard flag, and WindowManager has started running keyguard exit
+     * animation.
+     *
+     * @param startTime the start time of the animation in uptime milliseconds. Deprecated.
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds Deprecated.
+     * @deprecated Will be migrate to remote animation soon.
+     */
+    @Deprecated
     public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+        startKeyguardExitAnimation(0, startTime, fadeoutDuration, null, null, null, null);
+    }
+
+    /**
+     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * the wallpaper and keyguard flag, and System UI should start running keyguard exit animation.
+     *
+     * @param apps The list of apps to animate.
+     * @param wallpapers The list of wallpapers to animate.
+     * @param nonApps The list of non-app windows such as Bubbles to animate.
+     * @param finishedCallback The callback to invoke when the animation is finished.
+     */
+    public void startKeyguardExitAnimation(@WindowManager.TransitionOldType int transit,
+            RemoteAnimationTarget[] apps,
+            RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+            IRemoteAnimationFinishedCallback finishedCallback) {
+        startKeyguardExitAnimation(transit, 0, 0, apps, wallpapers, nonApps, finishedCallback);
+    }
+
+    /**
+     * Notifies to System UI that the activity behind has now been drawn and it's safe to remove
+     * the wallpaper and keyguard flag, and start running keyguard exit animation.
+     *
+     * @param startTime the start time of the animation in uptime milliseconds. Deprecated.
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds Deprecated.
+     * @param apps The list of apps to animate.
+     * @param wallpapers The list of wallpapers to animate.
+     * @param nonApps The list of non-app windows such as Bubbles to animate.
+     * @param finishedCallback The callback to invoke when the animation is finished.
+     */
+    private void startKeyguardExitAnimation(@WindowManager.TransitionOldType int transit,
+            long startTime, long fadeoutDuration,
+            RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+            RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
         Trace.beginSection("KeyguardViewMediator#startKeyguardExitAnimation");
         Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM,
-                new StartKeyguardExitAnimParams(startTime, fadeoutDuration));
+                new StartKeyguardExitAnimParams(transit, startTime, fadeoutDuration, apps,
+                        wallpapers, nonApps, finishedCallback));
         mHandler.sendMessage(msg);
         Trace.endSection();
     }
@@ -2300,12 +2404,26 @@
 
     private static class StartKeyguardExitAnimParams {
 
+        @WindowManager.TransitionOldType int mTransit;
         long startTime;
         long fadeoutDuration;
+        RemoteAnimationTarget[] mApps;
+        RemoteAnimationTarget[] mWallpapers;
+        RemoteAnimationTarget[] mNonApps;
+        IRemoteAnimationFinishedCallback mFinishedCallback;
 
-        private StartKeyguardExitAnimParams(long startTime, long fadeoutDuration) {
+        private StartKeyguardExitAnimParams(@WindowManager.TransitionOldType int transit,
+                long startTime, long fadeoutDuration,
+                RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+                RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            this.mTransit = transit;
             this.startTime = startTime;
             this.fadeoutDuration = fadeoutDuration;
+            this.mApps = apps;
+            this.mWallpapers = wallpapers;
+            this.mNonApps = nonApps;
+            this.mFinishedCallback = finishedCallback;
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 34d1f6e..dab4d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -28,6 +28,7 @@
 import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
@@ -544,6 +545,7 @@
         }
         mNavigationBarView.setNavigationIconHints(mNavigationIconHints);
         mNavigationBarView.setWindowVisible(isNavBarWindowVisible());
+        mNavigationBarView.setBehavior(mBehavior);
         mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
         mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener);
 
@@ -664,7 +666,7 @@
     }
 
     private void initSecondaryHomeHandleForRotation() {
-        if (!canShowSecondaryHandle()) {
+        if (mNavBarMode != NAV_BAR_MODE_GESTURAL) {
             return;
         }
 
@@ -918,6 +920,9 @@
         }
         if (mBehavior != behavior) {
             mBehavior = behavior;
+            if (mNavigationBarView != null) {
+                mNavigationBarView.setBehavior(behavior);
+            }
             updateSystemUiStateFlags(-1);
         }
     }
@@ -994,6 +999,8 @@
             return MODE_LIGHTS_OUT_TRANSPARENT;
         } else if ((appearance & APPEARANCE_OPAQUE_NAVIGATION_BARS) != 0) {
             return MODE_OPAQUE;
+        } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS) != 0) {
+            return MODE_SEMI_TRANSPARENT;
         } else {
             return MODE_TRANSPARENT;
         }
@@ -1517,7 +1524,7 @@
     }
 
     private boolean canShowSecondaryHandle() {
-        return mNavBarMode == NAV_BAR_MODE_GESTURAL;
+        return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
     }
 
     private final Consumer<Integer> mRotationWatcher = rotation -> {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index e7f2b222..c07404c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -57,6 +57,7 @@
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
 import android.view.WindowInsets;
+import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -626,6 +627,10 @@
         mRotationButtonController.onNavigationBarWindowVisibilityChange(visible);
     }
 
+    public void setBehavior(@Behavior int behavior) {
+        mRotationButtonController.onBehaviorChanged(behavior);
+    }
+
     @Override
     public void setLayoutDirection(int layoutDirection) {
         reloadNavIcons();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index df9e7a4..33d1807 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -35,6 +35,8 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
+import android.view.WindowInsetsController;
+import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 
@@ -78,6 +80,7 @@
     private Consumer<Integer> mRotWatcherListener;
     private boolean mListenersRegistered = false;
     private boolean mIsNavigationBarShowing;
+    private @Behavior int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
     private boolean mSkipOverrideUserLockPrefsOnce;
     private int mLightIconColor;
     private int mDarkIconColor;
@@ -297,8 +300,8 @@
         }
         mRotationButton.updateIcon(mLightIconColor, mDarkIconColor);
 
-        if (mIsNavigationBarShowing) {
-            // The navbar is visible so show the icon right away
+        if (canShowRotationButton()) {
+            // The navbar is visible / it's in visual immersive mode, so show the icon right away
             showAndLogRotationSuggestion();
         } else {
             // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become
@@ -318,14 +321,28 @@
     void onNavigationBarWindowVisibilityChange(boolean showing) {
         if (mIsNavigationBarShowing != showing) {
             mIsNavigationBarShowing = showing;
-
-            // If the navbar is visible, show the rotate button if there's a pending suggestion
-            if (showing && mPendingRotationSuggestion) {
-                showAndLogRotationSuggestion();
-            }
+            showPendingRotationButtonIfNeeded();
         }
     }
 
+    void onBehaviorChanged(@Behavior int behavior) {
+        if (mBehavior != behavior) {
+            mBehavior = behavior;
+            showPendingRotationButtonIfNeeded();
+        }
+    }
+
+    private void showPendingRotationButtonIfNeeded() {
+        if (canShowRotationButton() && mPendingRotationSuggestion) {
+            showAndLogRotationSuggestion();
+        }
+    }
+
+    /** Return true when either the nav bar is visible or it's in visual immersive mode. */
+    private boolean canShowRotationButton() {
+        return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
+    }
+
     public Context getContext() {
         return mContext;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
index 453e85a..517f8e7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButton.java
@@ -51,7 +51,7 @@
      * Reload the drawable from resource id, should reapply the previous dark intensity.
      */
     public void updateIcon(int lightIconColor, int darkIconColor) {
-        if (getCurrentView() == null || !getCurrentView().isAttachedToWindow() || mIconResId == 0) {
+        if (mIconResId == 0) {
             return;
         }
         final KeyButtonDrawable currentDrawable = getImageDrawable();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index a4e9189..580cbcf 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -24,10 +24,8 @@
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
@@ -36,12 +34,9 @@
 import android.util.Log;
 import android.view.ViewGroup;
 
-import androidx.preference.PreferenceManager;
-
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.systemui.R;
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
 
 import java.util.List;
 
@@ -128,26 +123,17 @@
 
     /** Stores the user selected configuration for {@code mAppWidgetId}. */
     private void storeWidgetConfiguration(PeopleSpaceTile tile) {
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
-        SharedPreferences.Editor editor = sp.edit();
         if (PeopleSpaceUtils.DEBUG) {
             Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
                     + tile.getId() + " for widget ID: "
                     + mAppWidgetId);
         }
-        // Ensure updates to app widget can be retrieved from both appWidget Id and tile ID.
-        editor.putString(String.valueOf(mAppWidgetId), tile.getId());
-        editor.putInt(tile.getId(), mAppWidgetId);
-        editor.apply();
-        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
-        Bundle options = new Bundle();
-        options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, tile);
-        appWidgetManager.updateAppWidgetOptions(mAppWidgetId, options);
-        int[] widgetIds = appWidgetManager.getAppWidgetIds(
-                new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
+
+        PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId);
+        int[] widgetIds = new int[mAppWidgetId];
         // TODO: Populate new widget with existing conversation notification, if there is any.
         PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager,
-                mNotificationManager);
+                mPeopleManager);
         finishActivity();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 91e7968..994dc6d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -18,6 +18,7 @@
 
 import static android.app.Notification.EXTRA_MESSAGES;
 
+import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -70,11 +71,13 @@
 import java.util.Comparator;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -89,6 +92,13 @@
     private static final int MIN_HOUR = 1;
     private static final int ONE_DAY = 1;
     public static final String OPTIONS_PEOPLE_SPACE_TILE = "options_people_space_tile";
+    public static final String PACKAGE_NAME = "package_name";
+    public static final String USER_ID = "user_id";
+    public static final String SHORTCUT_ID = "shortcut_id";
+
+    public static final String EMPTY_STRING = "";
+    public static final int INVALID_WIDGET_ID = -1;
+    public static final int INVALID_USER_ID = -1;
 
     private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");
     private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");
@@ -171,70 +181,174 @@
      * notification being posted or removed.
      */
     public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds,
-            AppWidgetManager appWidgetManager, INotificationManager notificationManager) {
-        IPeopleManager peopleManager = IPeopleManager.Stub.asInterface(
-                ServiceManager.getService(Context.PEOPLE_SERVICE));
-        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+            AppWidgetManager appWidgetManager, IPeopleManager peopleManager) {
         Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>();
-        try {
-            List<PeopleSpaceTile> tiles =
-                    PeopleSpaceUtils.getTiles(context, notificationManager,
-                            peopleManager, launcherApps);
-            for (int appWidgetId : appWidgetIds) {
-                String shortcutId = sp.getString(String.valueOf(appWidgetId), null);
-                if (DEBUG) {
-                    Log.d(TAG, "Widget ID: " + appWidgetId + " Shortcut ID: " + shortcutId);
-                }
-
-                Optional<PeopleSpaceTile> entry = tiles.stream().filter(
-                        e -> e.getId().equals(shortcutId)).findFirst();
-
-                if (!entry.isPresent() || shortcutId == null) {
-                    if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID");
-                    //TODO: Delete app widget id when crash is fixed (b/175486868)
-                    continue;
-                }
-                // Augment current tile based on stored fields.
-                PeopleSpaceTile tile = augmentTileFromStorage(entry.get(), appWidgetManager,
-                        appWidgetId);
-
-                RemoteViews views = createRemoteViews(context, tile, appWidgetId);
-
-                // Tell the AppWidgetManager to perform an update on the current app widget.
-                appWidgetManager.updateAppWidget(appWidgetId, views);
-
-                widgetIdToTile.put(appWidgetId, tile);
+        for (int appWidgetId : appWidgetIds) {
+            PeopleSpaceTile tile = getPeopleSpaceTile(peopleManager, appWidgetManager, context,
+                    appWidgetId);
+            if (tile == null) {
+                if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID");
+                //TODO: Delete app widget id when crash is fixed (b/172932636)
+                continue;
             }
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to retrieve conversations to set tiles: " + e);
+
+            if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName());
+            RemoteViews views = createRemoteViews(context, tile, appWidgetId);
+
+            // Tell the AppWidgetManager to perform an update on the current app widget.
+            appWidgetManager.updateAppWidget(appWidgetId, views);
+
+            widgetIdToTile.put(appWidgetId, tile);
         }
         getBirthdaysOnBackgroundThread(context, appWidgetManager, widgetIdToTile, appWidgetIds);
     }
 
-    /** Augment {@link PeopleSpaceTile} with fields from stored tile. */
-    @VisibleForTesting
-    static PeopleSpaceTile augmentTileFromStorage(PeopleSpaceTile tile,
-            AppWidgetManager appWidgetManager, int appWidgetId) {
-        Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
-        PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE);
-        if (storedTile == null) {
-            return tile;
+    @Nullable
+    private static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager,
+            AppWidgetManager appWidgetManager,
+            Context context, int appWidgetId) {
+        try {
+            // Migrate storage for existing users.
+            SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId),
+                    Context.MODE_PRIVATE);
+            String pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING);
+            int userId = widgetSp.getInt(USER_ID, INVALID_USER_ID);
+            String shortcutId = widgetSp.getString(SHORTCUT_ID, EMPTY_STRING);
+            if (pkg.isEmpty() || shortcutId.isEmpty() || userId == INVALID_WIDGET_ID) {
+                SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+                shortcutId = sp.getString(String.valueOf(appWidgetId), null);
+                if (shortcutId == null) {
+                    Log.e(TAG, "Cannot restore widget");
+                    return null;
+                }
+                migrateExistingUsersToNewStorage(context, shortcutId, appWidgetId);
+                pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING);
+                userId = widgetSp.getInt(USER_ID, INVALID_USER_ID);
+            }
+
+            // Check if tile is cached.
+            Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
+            PeopleSpaceTile tile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE);
+            if (tile != null) {
+                return tile;
+            }
+
+            // If tile is null, we need to retrieve from persisted storage.
+            if (DEBUG) Log.d(TAG, "Retrieving from storage after reboots");
+            LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+            ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId);
+            if (channel == null) {
+                Log.d(TAG, "Could not retrieve conversation from storage");
+                return null;
+            }
+            return new PeopleSpaceTile.Builder(channel, launcherApps).build();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to retrieve conversation for tile: " + e);
+            return null;
         }
-        return tile.toBuilder()
-                .setBirthdayText(storedTile.getBirthdayText())
-                .setNotificationKey(storedTile.getNotificationKey())
-                .setNotificationContent(storedTile.getNotificationContent())
-                .setNotificationDataUri(storedTile.getNotificationDataUri())
-                .build();
     }
 
-    /** If incoming notification changed tile, store the changes in the tile options. */
-    public static void storeNotificationChange(StatusBarNotification sbn,
+    /** Best-effort attempts to migrate existing users to the new storage format. */
+    // TODO: Remove after sufficient time. Temporary migration storage for existing users.
+    private static void migrateExistingUsersToNewStorage(Context context, String shortcutId,
+            int appWidgetId) {
+        try {
+            List<PeopleSpaceTile> tiles =
+                    PeopleSpaceUtils.getTiles(context, INotificationManager.Stub.asInterface(
+                            ServiceManager.getService(Context.NOTIFICATION_SERVICE)),
+                            IPeopleManager.Stub.asInterface(
+                                    ServiceManager.getService(Context.PEOPLE_SERVICE)),
+                            context.getSystemService(LauncherApps.class));
+            Optional<PeopleSpaceTile> entry = tiles.stream().filter(
+                    e -> e.getId().equals(shortcutId)).findFirst();
+            if (entry.isPresent()) {
+                if (DEBUG) Log.d(TAG, "Migrate storage for " + entry.get().getUserName());
+                setStorageForTile(context, entry.get(), appWidgetId);
+            } else {
+                Log.e(TAG, "Could not migrate user");
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Could not query conversations");
+        }
+    }
+
+    /** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */
+    public static void setStorageForTile(Context context, PeopleSpaceTile tile, int appWidgetId) {
+        // Write relevant persisted storage.
+        SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId),
+                Context.MODE_PRIVATE);
+        SharedPreferences.Editor widgetEditor = widgetSp.edit();
+        widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName());
+        widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId());
+        int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier();
+        widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId);
+        widgetEditor.apply();
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putString(String.valueOf(appWidgetId), tile.getId());
+        String key = PeopleSpaceUtils.getKey(tile.getId(), tile.getPackageName(), userId);
+        // Don't overwrite existing widgets with the same key.
+        Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
+        storedWidgetIds.add(String.valueOf(appWidgetId));
+        editor.putStringSet(key, storedWidgetIds);
+        editor.apply();
+
+        // Write cached storage.
+        updateAppWidgetOptionsAndView(AppWidgetManager.getInstance(context), context, appWidgetId,
+                tile);
+    }
+
+    /** Removes stored data when tile is deleted. */
+    public static void removeStorageForTile(Context context, int widgetId) {
+        SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId),
+                Context.MODE_PRIVATE);
+        String packageName = widgetSp.getString(PACKAGE_NAME, null);
+        String shortcutId = widgetSp.getString(SHORTCUT_ID, null);
+        int userId = widgetSp.getInt(USER_ID, -1);
+
+        // Delete widgetId mapping to key.
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+        SharedPreferences.Editor editor = sp.edit();
+        String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId);
+        Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
+        storedWidgetIds.remove(String.valueOf(widgetId));
+        editor.putStringSet(key, storedWidgetIds);
+        editor.remove(String.valueOf(widgetId));
+        editor.apply();
+
+        // Delete all data specifically mapped to widgetId.
+        SharedPreferences.Editor widgetEditor = widgetSp.edit();
+        widgetEditor.remove(PACKAGE_NAME);
+        widgetEditor.remove(USER_ID);
+        widgetEditor.remove(SHORTCUT_ID);
+        widgetEditor.apply();
+    }
+
+    /**
+     * Returns whether the data mapped to app widget specified by {@code appWidgetId} matches the
+     * requested update data.
+     */
+    public static boolean isCorrectAppWidget(Context context, int appWidgetId, String shortcutId,
+            String packageName, int userId) {
+        SharedPreferences sp = context.getSharedPreferences(String.valueOf(appWidgetId),
+                Context.MODE_PRIVATE);
+        String storedPackage = sp.getString(PACKAGE_NAME, EMPTY_STRING);
+        int storedUserId = sp.getInt(USER_ID, INVALID_USER_ID);
+        String storedShortcutId = sp.getString(SHORTCUT_ID, EMPTY_STRING);
+        return storedPackage.equals(packageName) && storedShortcutId.equals(shortcutId)
+                && storedUserId == userId;
+    }
+
+    /**
+     * If incoming notification changed tile, store the changes in the tile options.
+     */
+    public static void updateWidgetWithNotificationChanged(IPeopleManager peopleManager,
+            Context context,
+            StatusBarNotification sbn,
             NotificationAction notificationAction, AppWidgetManager appWidgetManager,
             int appWidgetId) {
-        Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
-        PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE);
+        PeopleSpaceTile storedTile = getPeopleSpaceTile(peopleManager, appWidgetManager, context,
+                appWidgetId);
         if (storedTile == null) {
             if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to");
             return;
@@ -263,7 +377,7 @@
                     .setNotificationDataUri(null)
                     .build();
         }
-        updateAppWidgetOptions(appWidgetManager, appWidgetId, storedTile);
+        updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile);
     }
 
     private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId,
@@ -677,4 +791,23 @@
         }
         return lookupKeysWithBirthdaysToday;
     }
+
+    /**
+     * Returns the uniquely identifying key for the conversation.
+     *
+     * <p>{@code userId} will always be a number, so we put user ID as the
+     * delimiter between the app-provided strings of shortcut ID and package name.
+     *
+     * <p>There aren't restrictions on shortcut ID characters, but there are restrictions requiring
+     * a {@code packageName} to always start with a letter. This restriction means we are
+     * guaranteed to avoid cases like "a/b/0/0/package.name" having two potential keys, as the first
+     * case is impossible given the package name restrictions:
+     * <ul>
+     *     <li>"a/b" + "/" + 0 + "/" + "0/packageName"</li>
+     *     <li>"a/b/0" + "/" + 0 + "/" + "packageName"</li>
+     * </ul>
+     */
+    public static String getKey(String shortcutId, String packageName, int userId) {
+        return shortcutId + "/" + userId + "/" + packageName;
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index ad6046b..bee54e4 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.people.widget;
 
-import android.app.INotificationManager;
 import android.app.NotificationChannel;
+import android.app.people.IPeopleManager;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,6 +29,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
+import android.widget.RemoteViews;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.appwidget.IAppWidgetService;
@@ -37,7 +38,8 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 
-import java.util.Objects;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -51,7 +53,7 @@
     private final Context mContext;
     private IAppWidgetService mAppWidgetService;
     private AppWidgetManager mAppWidgetManager;
-    private INotificationManager mNotificationManager;
+    private IPeopleManager mPeopleManager;
 
     @Inject
     public PeopleSpaceWidgetManager(Context context, IAppWidgetService appWidgetService) {
@@ -59,11 +61,13 @@
         mContext = context;
         mAppWidgetService = appWidgetService;
         mAppWidgetManager = AppWidgetManager.getInstance(context);
-        mNotificationManager = INotificationManager.Stub.asInterface(
-                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        mPeopleManager = IPeopleManager.Stub.asInterface(
+                ServiceManager.getService(Context.PEOPLE_SERVICE));
     }
 
-    /** Constructor used for testing. */
+    /**
+     * Constructor used for testing.
+     */
     @VisibleForTesting
     protected PeopleSpaceWidgetManager(Context context) {
         if (DEBUG) Log.d(TAG, "constructor");
@@ -72,16 +76,20 @@
                 ServiceManager.getService(Context.APPWIDGET_SERVICE));
     }
 
-    /** AppWidgetManager setter used for testing. */
+    /**
+     * AppWidgetManager setter used for testing.
+     */
     @VisibleForTesting
     protected void setAppWidgetManager(IAppWidgetService appWidgetService,
-            AppWidgetManager appWidgetManager, INotificationManager notificationManager) {
+            AppWidgetManager appWidgetManager, IPeopleManager peopleManager) {
         mAppWidgetService = appWidgetService;
         mAppWidgetManager = appWidgetManager;
-        mNotificationManager = notificationManager;
+        mPeopleManager = peopleManager;
     }
 
-    /** Updates People Space widgets. */
+    /**
+     * Updates People Space widgets.
+     */
     public void updateWidgets() {
         try {
             if (DEBUG) Log.d(TAG, "updateWidgets called");
@@ -99,7 +107,7 @@
 
             if (showSingleConversation) {
                 PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds,
-                        mAppWidgetManager, mNotificationManager);
+                        mAppWidgetManager, mPeopleManager);
             } else {
                 mAppWidgetService
                         .notifyAppWidgetViewDataChanged(mContext.getOpPackageName(), widgetIds,
@@ -114,30 +122,38 @@
      * Check if any existing People tiles match the incoming notification change, and store the
      * change in the tile if so.
      */
-    public void storeNotificationChange(StatusBarNotification sbn,
+    public void updateWidgetWithNotificationChanged(StatusBarNotification sbn,
             PeopleSpaceUtils.NotificationAction notificationAction) {
-        if (DEBUG) Log.d(TAG, "storeNotificationChange called");
+        RemoteViews views = new RemoteViews(
+                mContext.getPackageName(), R.layout.people_space_small_avatar_tile);
+        if (DEBUG) Log.d(TAG, "updateWidgetWithNotificationChanged called");
         boolean showSingleConversation = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0;
         if (!showSingleConversation) {
             return;
         }
         try {
+            String sbnShortcutId = sbn.getShortcutId();
+            if (sbnShortcutId == null) {
+                return;
+            }
             int[] widgetIds = mAppWidgetService.getAppWidgetIds(
                     new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
             );
             if (widgetIds.length == 0) {
                 return;
             }
-
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
-            for (int widgetId : widgetIds) {
-                String shortcutId = sp.getString(String.valueOf(widgetId), null);
-                if (!Objects.equals(sbn.getShortcutId(), shortcutId)) {
-                    continue;
-                }
+            int userId = UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier();
+            String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId);
+            Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
+            if (storedWidgetIds.isEmpty()) {
+                return;
+            }
+            for (String widgetIdString : storedWidgetIds) {
+                int widgetId = Integer.parseInt(widgetIdString);
                 if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey());
-                PeopleSpaceUtils.storeNotificationChange(
+                PeopleSpaceUtils.updateWidgetWithNotificationChanged(mPeopleManager, mContext,
                         sbn, notificationAction, mAppWidgetManager, widgetId);
             }
         } catch (Exception e) {
@@ -159,8 +175,7 @@
         public void onNotificationPosted(
                 StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) {
             if (DEBUG) Log.d(TAG, "onNotificationPosted");
-            storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.POSTED);
-            updateWidgets();
+            updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED);
         }
 
         @Override
@@ -169,8 +184,7 @@
                 NotificationListenerService.RankingMap rankingMap
         ) {
             if (DEBUG) Log.d(TAG, "onNotificationRemoved");
-            storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.REMOVED);
-            updateWidgets();
+            updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED);
         }
 
         @Override
@@ -179,8 +193,7 @@
                 NotificationListenerService.RankingMap rankingMap,
                 int reason) {
             if (DEBUG) Log.d(TAG, "onNotificationRemoved with reason " + reason);
-            storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.REMOVED);
-            updateWidgets();
+            updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED);
         }
 
         @Override
@@ -207,4 +220,4 @@
         }
     };
 
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index 7f204cc..f5577d3 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.people.widget;
 
-import android.app.INotificationManager;
 import android.app.PendingIntent;
+import android.app.people.IPeopleManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProvider;
 import android.content.Context;
@@ -53,8 +53,8 @@
                 Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0;
         if (showSingleConversation) {
             PeopleSpaceUtils.updateSingleConversationWidgets(context, appWidgetIds,
-                    appWidgetManager, INotificationManager.Stub.asInterface(
-                            ServiceManager.getService(Context.NOTIFICATION_SERVICE)));
+                    appWidgetManager, IPeopleManager.Stub.asInterface(
+                            ServiceManager.getService(Context.PEOPLE_SERVICE)));
             return;
         }
         // Perform this loop procedure for each App Widget that belongs to this provider
@@ -91,6 +91,7 @@
         for (int widgetId : appWidgetIds) {
             if (DEBUG) Log.d(TAG, "Widget removed");
             mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED);
+            PeopleSpaceUtils.removeStorageForTile(context, widgetId);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java
index 95029c0..7f01d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInitializerImpl.java
@@ -15,6 +15,7 @@
 package com.android.systemui.plugins;
 
 import android.content.Context;
+import android.os.Build;
 import android.os.Looper;
 import android.util.Log;
 
@@ -67,4 +68,9 @@
             });
         }
     }
+
+    @Override
+    public boolean isDebuggable() {
+        return Build.IS_DEBUGGABLE;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index 66c535f..3f79be8 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -24,7 +24,6 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.view.WindowInsets
 import android.widget.ImageView
@@ -66,9 +65,8 @@
         super.onCreate(savedInstanceState)
         window?.apply {
             attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
-            setLayout(MATCH_PARENT, WRAP_CONTENT)
+            setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT)
             setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
-            setBackgroundDrawable(null)
         }
 
         setContentView(R.layout.privacy_dialog)
@@ -125,12 +123,12 @@
         val finalText = element.attribution?.let {
             TextUtils.concat(
                     firstLine,
-                    "\n",
+                    " ",
                     context.getString(R.string.ongoing_privacy_dialog_attribution_text, it)
             )
         } ?: firstLine
         newView.requireViewById<TextView>(R.id.text).text = finalText
-        newView.requireViewById<View>(R.id.link).apply {
+        newView.apply {
             tag = element.type.permGroupName
             setOnClickListener(clickListener)
         }
@@ -154,9 +152,7 @@
     }
 
     private val clickListener = View.OnClickListener { v ->
-        if (v.id == R.id.link) {
-            v.tag?.let { activityStarter(it as String) }
-        }
+        v.tag?.let { activityStarter(it as String) }
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index 99e9bfc..8b6c04f 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -227,21 +227,20 @@
     /**
      * Filters the list of elements to show.
      *
-     * * Return at most one element per [PrivacyType], sorted by the natural order of the
-     * [PrivacyType].
-     * * If there are no active usages for a type, return the most recent
-     * * If there are multiple active usages for a type, return the most active recent.
+     * For each privacy type, it'll return all active elements. If there are no active elements,
+     * it'll return the most recent access
      */
     private fun filterAndSelect(
         list: List<PrivacyDialog.PrivacyElement>
     ): List<PrivacyDialog.PrivacyElement> {
-        return list.groupBy { it.type }.toSortedMap().mapNotNull { entry ->
-            if (entry.value.isEmpty()) {
-                null
+        return list.groupBy { it.type }.toSortedMap().flatMap { (_, elements) ->
+            val actives = elements.filter { it.active }
+            if (actives.isNotEmpty()) {
+                actives.sortedByDescending { it.lastActiveTimestamp }
             } else {
-                val actives = entry.value.filter { it.active }
-                val out = if (actives.isNotEmpty()) actives else entry.value
-                out.maxByOrNull { it.lastActiveTimestamp }
+                elements.maxByOrNull { it.lastActiveTimestamp }?.let {
+                    listOf(it)
+                } ?: emptyList()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index e9207f1..d248ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -29,6 +29,8 @@
 import com.android.systemui.qs.TouchAnimator.Builder;
 import com.android.systemui.qs.TouchAnimator.Listener;
 import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.qs.tileimpl.QSTileBaseView;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -87,11 +89,13 @@
     private final Executor mExecutor;
     private final TunerService mTunerService;
     private boolean mShowCollapsedOnKeyguard;
+    private final FeatureFlags mFeatureFlags;
 
     @Inject
     public QSAnimator(QS qs, QuickQSPanel quickPanel, QSPanelController qsPanelController,
             QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
-            QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService) {
+            QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
+            FeatureFlags featureFlags) {
         mQs = qs;
         mQuickQsPanel = quickPanel;
         mQsPanelController = qsPanelController;
@@ -100,6 +104,7 @@
         mHost = qsTileHost;
         mExecutor = executor;
         mTunerService = tunerService;
+        mFeatureFlags = featureFlags;
         mHost.addCallback(this);
         mQsPanelController.addOnAttachStateChangeListener(this);
         qs.getView().addOnLayoutChangeListener(this);
@@ -228,6 +233,7 @@
                 // Quick tiles.
                 QSTileView quickTileView = mQuickQSPanelController.getTileView(tile);
                 if (quickTileView == null) continue;
+                View qqsBgCircle = ((QSTileBaseView) quickTileView).getBgCircle();
 
                 getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view);
                 getRelativePosition(loc2, tileIcon, view);
@@ -249,6 +255,11 @@
                     translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0);
                     translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0);
 
+                    if (mFeatureFlags.isQSLabelsEnabled()) {
+                        firstPageBuilder.addFloat(qqsBgCircle, "alpha", 1, 1, 0);
+                        mAllViews.add(qqsBgCircle);
+                    }
+
                 } else { // These tiles disappear when expanding
                     firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0);
                     translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index d0601f0..91ae571 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -777,10 +777,6 @@
         updatePadding();
     }
 
-    boolean useSideLabels() {
-        return mSideLabels;
-    }
-
     private class H extends Handler {
         private static final int SHOW_DETAIL = 1;
         private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 30774be..f56a890 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -19,7 +19,6 @@
 import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
 import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
 import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_SIDE_LABELS;
 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
 import android.annotation.NonNull;
@@ -93,8 +92,7 @@
             DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
             BrightnessSlider.Factory brightnessSliderFactory,
-            @Named(QS_LABELS_FLAG) boolean qsLabelsFlag,
-            @Named(QS_SIDE_LABELS) boolean useSideLabels) {
+            @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
         super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                 metricsLogger, uiEventLogger, qsLogger, dumpManager);
         mQsSecurityFooter = qsSecurityFooter;
@@ -110,7 +108,7 @@
         mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider);
 
         mQsLabelsFlag = qsLabelsFlag;
-        mSideLabels = useSideLabels;
+        mSideLabels = qsLabelsFlag;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index e2d7d20..a0db200 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.qs;
 
 import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
+import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
 import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER;
 
 import com.android.internal.logging.MetricsLogger;
@@ -40,6 +41,8 @@
 @QSScope
 public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> {
 
+    private boolean mUseSideLabels;
+
     private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
             newConfig -> {
                 int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
@@ -54,9 +57,10 @@
             @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
             @Named(QUICK_QS_PANEL) MediaHost mediaHost,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
-            DumpManager dumpManager) {
+            DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag) {
         super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
                 uiEventLogger, qsLogger, dumpManager);
+        mUseSideLabels = qsLabelsFlag;
     }
 
     @Override
@@ -97,7 +101,7 @@
                 break;
             }
         }
-        if (mView.useSideLabels()) {
+        if (mUseSideLabels) {
             List<QSTile> newTiles = new ArrayList<>();
             for (int i = 0; i < tiles.size(); i += 2) {
                 newTiles.add(tiles.get(i));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 39b92dc..7d8d86f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -20,6 +20,7 @@
 import android.content.res.ColorStateList;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.FeatureFlagUtils;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -65,10 +66,13 @@
         super.onFinishInflate();
         mDualToneHandler = new DualToneHandler(getContext());
         mMobileGroup = findViewById(R.id.mobile_combo);
+        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+            mMobileRoaming = findViewById(R.id.mobile_roaming_large);
+        } else {
+            mMobileRoaming = findViewById(R.id.mobile_roaming);
+        }
         mMobileSignal = findViewById(R.id.mobile_signal);
-        mMobileRoaming = findViewById(R.id.mobile_roaming);
         mCarrierText = findViewById(R.id.qs_carrier_text);
-
         mMobileSignal.setImageDrawable(new SignalDrawable(mContext));
 
         int colorForeground = Utils.getColorAttrDefaultColor(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index ebdcc00..77200cc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -71,7 +71,7 @@
                         boolean activityIn, boolean activityOut,
                         CharSequence typeContentDescription,
                         CharSequence typeContentDescriptionHtml, CharSequence description,
-                        boolean isWide, int subId, boolean roaming) {
+                        boolean isWide, int subId, boolean roaming, boolean showTriangle) {
                     int slotIndex = getSlotIndex(subId);
                     if (slotIndex >= SIM_SLOTS) {
                         Log.w(TAG, "setMobileDataIndicators - slot: " + slotIndex);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
index ad4c8bb..9ab2d73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
@@ -18,7 +18,6 @@
 
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.util.settings.SecureSettings;
 
 import javax.inject.Named;
 
@@ -28,7 +27,6 @@
 @Module
 public interface QSFlagsModule {
     String QS_LABELS_FLAG = "qs_labels_flag";
-    String QS_SIDE_LABELS = "qs_side_labels";
 
     @Provides
     @SysUISingleton
@@ -36,12 +34,4 @@
     static boolean provideQSFlag(FeatureFlags featureFlags) {
         return featureFlags.isQSLabelsEnabled();
     }
-
-    @Provides
-    @SysUISingleton
-    @Named(QS_SIDE_LABELS)
-    static boolean provideSideLabels(SecureSettings secureSettings,
-            @Named(QS_LABELS_FLAG) boolean qsLabels) {
-        return qsLabels && secureSettings.getInt("sysui_side_labels", 0) != 0;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 6e28cd8..56d06eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -315,7 +315,8 @@
         }
         try {
             if (DEBUG) Log.d(TAG, "Adding token");
-            mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG, DEFAULT_DISPLAY);
+            mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG, DEFAULT_DISPLAY,
+                    null /* options */);
             mIsTokenGranted = true;
         } catch (RemoteException e) {
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 9e582dd..11e6330 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -14,7 +14,7 @@
 
 package com.android.systemui.qs.tileimpl;
 
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_SIDE_LABELS;
+import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
 
 import android.content.Context;
 import android.os.Build;
@@ -98,7 +98,7 @@
     @Inject
     public QSFactoryImpl(
             Lazy<QSHost> qsHostLazy,
-            @Named(QS_SIDE_LABELS) boolean useSideLabels,
+            @Named(QS_LABELS_FLAG) boolean useSideLabels,
             Provider<CustomTile.Builder> customTileBuilderProvider,
             Provider<WifiTile> wifiTileProvider,
             Provider<InternetTile> internetTileProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
index 2dbd2cf..a699e2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java
@@ -36,7 +36,7 @@
 
 /** View that represents a standard quick settings tile. **/
 public class QSTileView extends QSTileBaseView {
-    private static final int MAX_LABEL_LINES = 2;
+    protected int mMaxLabelLines = 2;
     private View mDivider;
     protected TextView mLabel;
     protected TextView mSecondLine;
@@ -109,10 +109,17 @@
 
         // Remeasure view if the primary label requires more then 2 lines or the secondary label
         // text will be cut off.
-        if (mLabel.getLineCount() > MAX_LABEL_LINES || !TextUtils.isEmpty(mSecondLine.getText())
+        if (mLabel.getLineCount() > mMaxLabelLines || !TextUtils.isEmpty(mSecondLine.getText())
                         && mSecondLine.getLineHeight() > mSecondLine.getHeight()) {
-            mLabel.setSingleLine();
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            if (!mLabel.isSingleLine()) {
+                mLabel.setSingleLine();
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
+        } else {
+            if (mLabel.isSingleLine()) {
+                mLabel.setSingleLine(false);
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
index 2ef78c2..dc81b702 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt
@@ -24,7 +24,6 @@
 import android.graphics.drawable.RippleDrawable
 import android.service.quicksettings.Tile.STATE_ACTIVE
 import android.view.Gravity
-import android.view.LayoutInflater
 import android.view.View
 import android.widget.LinearLayout
 import com.android.systemui.R
@@ -42,38 +41,23 @@
 
     init {
         orientation = HORIZONTAL
-        mDualTargetAllowed = true
+        mDualTargetAllowed = false
         mBg.setImageDrawable(null)
-        createDivider()
         mColorLabelActive = ColorStateList.valueOf(getColorForState(getContext(), STATE_ACTIVE))
+        mMaxLabelLines = 3
     }
 
     override fun createLabel() {
         super.createLabel()
         findViewById<LinearLayout>(R.id.label_group)?.gravity = Gravity.START
         mLabel.gravity = Gravity.START
+        mLabel.textDirection = TEXT_DIRECTION_LOCALE
         mSecondLine.gravity = Gravity.START
+        mSecondLine.textDirection = TEXT_DIRECTION_LOCALE
         val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding)
-        mLabelContainer.setPadding(padding, padding, padding, padding)
-        (mLabelContainer.layoutParams as LayoutParams).gravity = Gravity.CENTER_VERTICAL
-    }
-
-    fun createDivider() {
-        divider = LayoutInflater.from(context).inflate(R.layout.qs_tile_label_divider, this, false)
-        val position = indexOfChild(mLabelContainer)
-        addView(divider, position)
-    }
-
-    override fun init(
-        click: OnClickListener?,
-        secondaryClick: OnClickListener?,
-        longClick: OnLongClickListener?
-    ) {
-        super.init(click, secondaryClick, longClick)
-        mLabelContainer.setOnClickListener {
-            longClick?.onLongClick(it)
-        }
-        mLabelContainer.isClickable = false
+        mLabelContainer.setPaddingRelative(0, padding, padding, padding)
+        (mLabelContainer.layoutParams as LayoutParams).gravity =
+                Gravity.CENTER_VERTICAL or Gravity.START
     }
 
     override fun updateRippleSize() {
@@ -83,7 +67,7 @@
         val d = super.newTileBackground()
         if (paintDrawable == null) {
             paintDrawable = PaintDrawable(Color.WHITE).apply {
-                setCornerRadius(30f)
+                setCornerRadius(50f)
             }
         }
         if (d is RippleDrawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index b6e07b1..3841dac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
@@ -49,9 +50,10 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            IndividualSensorPrivacyController sensorPrivacyController) {
+            IndividualSensorPrivacyController sensorPrivacyController,
+            KeyguardStateController keyguardStateController) {
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
-                activityStarter, qsLogger, sensorPrivacyController);
+                activityStarter, qsLogger, sensorPrivacyController, keyguardStateController);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index f742752..720c5dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -268,7 +268,7 @@
                 int qsType, boolean activityIn, boolean activityOut,
                 CharSequence typeContentDescription,
                 CharSequence typeContentDescriptionHtml, CharSequence description,
-                boolean isWide, int subId, boolean roaming) {
+                boolean isWide, int subId, boolean roaming, boolean showTriangle) {
             if (qsIcon == null) {
                 // Not data sim, don't display.
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 1523278..191b85b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -34,6 +34,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.graph.SignalDrawable;
+import com.android.settingslib.mobile.TelephonyIcons;
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -53,6 +54,9 @@
 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
 import com.android.systemui.statusbar.policy.WifiIcons;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 import javax.inject.Inject;
 
 /** Quick settings tile: Internet **/
@@ -64,7 +68,7 @@
     protected final NetworkController mController;
     private final DataUsageController mDataController;
     private final QSTile.SignalState mStateBeforeClick = newTileState();
-    // The last updated tile state, 0: mobile, 1: wifi
+    // The last updated tile state, 0: mobile, 1: wifi, 2: ethernet.
     private int mLastTileState = -1;
 
     protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback();
@@ -139,6 +143,21 @@
         return string;
     }
 
+    private static final class EthernetCallbackInfo {
+        boolean mConnected;
+        int mEthernetSignalIconId;
+        String mEthernetContentDescription;
+
+        @Override
+        public String toString() {
+            return new StringBuilder("EthernetCallbackInfo[")
+                    .append("mConnected=").append(mConnected)
+                    .append(",mEthernetSignalIconId=").append(mEthernetSignalIconId)
+                    .append(",mEthernetContentDescription=").append(mEthernetContentDescription)
+                    .append(']').toString();
+        }
+    }
+
     private static final class WifiCallbackInfo {
         boolean mAirplaneModeEnabled;
         boolean mEnabled;
@@ -178,6 +197,7 @@
         CharSequence mDataSubscriptionName;
         CharSequence mDataContentDescription;
         int mMobileSignalIconId;
+        int mQsTypeIcon;
         boolean mActivityIn;
         boolean mActivityOut;
         boolean mNoSim;
@@ -194,6 +214,7 @@
                 .append(",mDataSubscriptionName=").append(mDataSubscriptionName)
                 .append(",mDataContentDescription=").append(mDataContentDescription)
                 .append(",mMobileSignalIconId=").append(mMobileSignalIconId)
+                .append(",mQsTypeIcon=").append(mQsTypeIcon)
                 .append(",mActivityIn=").append(mActivityIn)
                 .append(",mActivityOut=").append(mActivityOut)
                 .append(",mNoSim=").append(mNoSim)
@@ -209,6 +230,8 @@
     protected final class InternetSignalCallback implements SignalCallback {
         final WifiCallbackInfo mWifiInfo = new WifiCallbackInfo();
         final CellularCallbackInfo mCellularInfo = new CellularCallbackInfo();
+        final EthernetCallbackInfo mEthernetInfo = new EthernetCallbackInfo();
+
 
         @Override
         public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
@@ -227,14 +250,12 @@
             }
             // When airplane mode is enabled, we need to refresh the Internet Tile even if the WiFi
             // is not the default network.
-            if (qsIcon == null && !mWifiInfo.mAirplaneModeEnabled) {
+            if (qsIcon == null) {
                 return;
             }
-            if (qsIcon != null) {
-                mWifiInfo.mConnected = qsIcon.visible;
-                mWifiInfo.mWifiSignalIconId = qsIcon.icon;
-                mWifiInfo.mWifiSignalContentDescription = qsIcon.contentDescription;
-            }
+            mWifiInfo.mConnected = qsIcon.visible;
+            mWifiInfo.mWifiSignalIconId = qsIcon.icon;
+            mWifiInfo.mWifiSignalContentDescription = qsIcon.contentDescription;
             mWifiInfo.mEnabled = enabled;
             mWifiInfo.mSsid = description;
             mWifiInfo.mActivityIn = activityIn;
@@ -249,7 +270,7 @@
                 int qsType, boolean activityIn, boolean activityOut,
                 CharSequence typeContentDescription,
                 CharSequence typeContentDescriptionHtml, CharSequence description,
-                boolean isWide, int subId, boolean roaming) {
+                boolean isWide, int subId, boolean roaming, boolean showTriangle) {
             if (DEBUG) {
                 Log.d(TAG, "setMobileDataIndicators: "
                         + "statusIcon = " + (statusIcon == null ? "" :  statusIcon.toString()) + ","
@@ -263,7 +284,8 @@
                         + "description = " + description + ","
                         + "isWide = " + isWide + ","
                         + "subId = " + subId + ","
-                        + "roaming = " + roaming);
+                        + "roaming = " + roaming + ","
+                        + "showTriangle = " + showTriangle);
             }
             if (qsIcon == null) {
                 // Not data sim, don't display.
@@ -274,6 +296,7 @@
             mCellularInfo.mDataContentDescription =
                     (description != null) ? typeContentDescriptionHtml : null;
             mCellularInfo.mMobileSignalIconId = qsIcon.icon;
+            mCellularInfo.mQsTypeIcon = qsType;
             mCellularInfo.mActivityIn = activityIn;
             mCellularInfo.mActivityOut = activityOut;
             mCellularInfo.mRoaming = roaming;
@@ -282,6 +305,20 @@
         }
 
         @Override
+        public void setEthernetIndicators(IconState icon) {
+            if (DEBUG) {
+                Log.d(TAG, "setEthernetIndicators: "
+                        + "icon = " + (icon == null ? "" :  icon.toString()));
+            }
+            mEthernetInfo.mConnected = icon.visible;
+            mEthernetInfo.mEthernetSignalIconId = icon.icon;
+            mEthernetInfo.mEthernetContentDescription = icon.contentDescription;
+            if (icon.visible) {
+                refreshState(mEthernetInfo);
+            }
+        }
+
+        @Override
         public void setNoSims(boolean show, boolean simDetected) {
             if (DEBUG) {
                 Log.d(TAG, "setNoSims: "
@@ -292,8 +329,8 @@
             if (mCellularInfo.mNoSim) {
                 // Make sure signal gets cleared out when no sims.
                 mCellularInfo.mMobileSignalIconId = 0;
+                mCellularInfo.mQsTypeIcon = 0;
             }
-            refreshState(mCellularInfo);
         }
 
         @Override
@@ -304,7 +341,9 @@
             }
             mCellularInfo.mAirplaneModeEnabled = icon.visible;
             mWifiInfo.mAirplaneModeEnabled = icon.visible;
-            refreshState(mCellularInfo);
+            if (!mSignalCallback.mEthernetInfo.mConnected) {
+                refreshState(mCellularInfo);
+            }
         }
 
         @Override
@@ -324,6 +363,15 @@
             mWifiInfo.mNoNetworksAvailable = noNetworksAvailable;
             refreshState(mWifiInfo);
         }
+
+        @Override
+        public String toString() {
+            return new StringBuilder("InternetSignalCallback[")
+                .append("mWifiInfo=").append(mWifiInfo)
+                .append(",mCellularInfo=").append(mCellularInfo)
+                .append(",mEthernetInfo=").append(mEthernetInfo)
+                .append(']').toString();
+        }
     }
 
     @Override
@@ -334,6 +382,9 @@
         } else if (arg instanceof WifiCallbackInfo) {
             mLastTileState = 1;
             handleUpdateWifiState(state, arg);
+        } else if (arg instanceof EthernetCallbackInfo) {
+            mLastTileState = 2;
+            handleUpdateEthernetState(state, arg);
         } else {
             // handleUpdateState will be triggered when user expands the QuickSetting panel with
             // arg = null, in this case the last updated CellularCallbackInfo or WifiCallbackInfo
@@ -342,6 +393,8 @@
                 handleUpdateCellularState(state, mSignalCallback.mCellularInfo);
             } else if (mLastTileState == 1) {
                 handleUpdateWifiState(state, mSignalCallback.mWifiInfo);
+            } else if (mLastTileState == 2) {
+                handleUpdateEthernetState(state, mSignalCallback.mEthernetInfo);
             }
         }
     }
@@ -374,6 +427,7 @@
         state.label = r.getString(R.string.quick_settings_internet_label);
         if (cb.mAirplaneModeEnabled) {
             if (!state.value) {
+                state.state = Tile.STATE_INACTIVE;
                 state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_airplane);
                 state.secondaryLabel = r.getString(R.string.status_bar_airplane);
             } else if (!wifiConnected) {
@@ -433,7 +487,6 @@
             Log.d(TAG, "handleUpdateCellularState: " + "CellularCallbackInfo = " + cb.toString());
         }
         final Resources r = mContext.getResources();
-        // TODO(b/174753536): Use the new "Internet" string as state.label once available.
         state.label = r.getString(R.string.quick_settings_internet_label);
         state.state = Tile.STATE_ACTIVE;
         boolean mobileDataEnabled = mDataController.isMobileDataSupported()
@@ -443,7 +496,8 @@
         state.activityOut = mobileDataEnabled && cb.mActivityOut;
         state.expandedAccessibilityClassName = Switch.class.getName();
 
-        if (cb.mAirplaneModeEnabled && cb.mNoDefaultNetwork) {
+        if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
+            state.state = Tile.STATE_INACTIVE;
             state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_airplane);
             state.secondaryLabel = r.getString(R.string.status_bar_airplane);
         } else if (cb.mNoDefaultNetwork && cb.mNoNetworksAvailable) {
@@ -470,6 +524,18 @@
         }
     }
 
+    private void handleUpdateEthernetState(SignalState state, Object arg) {
+        EthernetCallbackInfo cb = (EthernetCallbackInfo) arg;
+        if (DEBUG) {
+            Log.d(TAG, "handleUpdateEthernetState: " + "EthernetCallbackInfo = " + cb.toString());
+        }
+        final Resources r = mContext.getResources();
+        state.label = r.getString(R.string.quick_settings_internet_label);
+        state.state = Tile.STATE_ACTIVE;
+        state.icon = ResourceIcon.get(cb.mEthernetSignalIconId);
+        state.secondaryLabel = cb.mEthernetContentDescription;
+    }
+
     private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) {
         if (TextUtils.isEmpty(dataType)) {
             return Html.fromHtml((current == null ? "" : current.toString()), 0);
@@ -511,4 +577,15 @@
             return d;
         }
     }
+
+    /**
+     * Dumps the state of this tile along with its name.
+     */
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(this.getClass().getSimpleName() + ":");
+        pw.print("    "); pw.println(getState().toString());
+        pw.print("    "); pw.println("mLastTileState=" + mLastTileState);
+        pw.print("    "); pw.println("mSignalCallback=" + mSignalCallback.toString());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index 9cc6f09..2f0071a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -36,6 +36,7 @@
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
@@ -49,9 +50,10 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            IndividualSensorPrivacyController sensorPrivacyController) {
+            IndividualSensorPrivacyController sensorPrivacyController,
+            KeyguardStateController keyguardStateController) {
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
-                activityStarter, qsLogger, sensorPrivacyController);
+                activityStarter, qsLogger, sensorPrivacyController, keyguardStateController);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index fb281169..63e27796 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -47,6 +47,7 @@
 /** Quick settings tile: Enable/Disable NFC **/
 public class NfcTile extends QSTileImpl<BooleanState> {
 
+    private static final String NFC = "nfc";
     private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_nfc);
 
     private NfcAdapter mAdapter;
@@ -89,7 +90,13 @@
 
     @Override
     public boolean isAvailable() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC);
+        String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
+        // For the restore from backup case
+        // Return false when "nfc" is not listed in quick_settings_tiles_stock.
+        if (stockTiles.contains(NFC)) {
+            return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC);
+        }
+        return false;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 12205d6..00703e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -35,6 +35,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 /**
  * Superclass to toggle individual sensor privacy via quick settings tiles
@@ -42,6 +43,7 @@
 public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanState> implements
         IndividualSensorPrivacyController.Callback {
 
+    private final KeyguardStateController mKeyguard;
     private IndividualSensorPrivacyController mSensorPrivacyController;
 
     /**
@@ -61,10 +63,12 @@
             StatusBarStateController statusBarStateController,
             ActivityStarter activityStarter,
             QSLogger qsLogger,
-            IndividualSensorPrivacyController sensorPrivacyController) {
+            IndividualSensorPrivacyController sensorPrivacyController,
+            KeyguardStateController keyguardStateController) {
         super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
                 activityStarter, qsLogger);
         mSensorPrivacyController = sensorPrivacyController;
+        mKeyguard = keyguardStateController;
         mSensorPrivacyController.observe(getLifecycle(), this);
     }
 
@@ -75,6 +79,13 @@
 
     @Override
     protected void handleClick() {
+        if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) {
+            mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+                mSensorPrivacyController.setSensorBlocked(getSensorId(),
+                        !mSensorPrivacyController.isSensorBlocked(getSensorId()));
+            });
+            return;
+        }
         mSensorPrivacyController.setSensorBlocked(getSensorId(),
                 !mSensorPrivacyController.isSensorBlocked(getSensorId()));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 760ebad..a9f76f6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -35,6 +35,7 @@
 
 import android.annotation.FloatRange;
 import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -83,6 +84,7 @@
 import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
+import com.android.systemui.shared.recents.ISplitScreenListener;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -98,6 +100,7 @@
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.FileDescriptor;
@@ -133,7 +136,8 @@
     private final Context mContext;
     private final Optional<Pip> mPipOptional;
     private final Optional<Lazy<StatusBar>> mStatusBarOptionalLazy;
-    private final Optional<LegacySplitScreen> mSplitScreenOptional;
+    private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+    private final Optional<SplitScreen> mSplitScreenOptional;
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final Lazy<NavigationBarController> mNavBarControllerLazy;
@@ -263,7 +267,7 @@
             }
             final long token = Binder.clearCallingIdentity();
             try {
-                return mSplitScreenOptional.map(splitScreen ->
+                return mLegacySplitScreenOptional.map(splitScreen ->
                         splitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds())
                         .orElse(null);
             } finally {
@@ -401,7 +405,7 @@
 
         @Override
         public void setSplitScreenMinimized(boolean minimized) {
-            mSplitScreenOptional.ifPresent(
+            mLegacySplitScreenOptional.ifPresent(
                     splitScreen -> splitScreen.setMinimized(minimized));
         }
 
@@ -559,6 +563,105 @@
             }
         }
 
+        @Override
+        public void registerSplitScreenListener(ISplitScreenListener listener) {
+            if (!verifyCaller("registerSplitScreenListener")) {
+                return;
+            }
+            mISplitScreenListener = listener;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(
+                        s -> s.registerSplitScreenListener(mSplitScreenListener));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void unregisterSplitScreenListener(ISplitScreenListener listener) {
+            if (!verifyCaller("unregisterSplitScreenListener")) {
+                return;
+            }
+            mISplitScreenListener = null;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(
+                        s -> s.unregisterSplitScreenListener(mSplitScreenListener));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setSideStageVisibility(boolean visible) {
+            if (!verifyCaller("setSideStageVisibility")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s -> s.setSideStageVisibility(visible));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void exitSplitScreen() {
+            if (!verifyCaller("exitSplitScreen")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s -> s.exitSplitScreen());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void startTask(int taskId, int stage, int position, Bundle options) {
+            if (!verifyCaller("startTask")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(
+                        s -> s.startTask(taskId, stage, position, options));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void startShortcut(String packageName, String shortcutId, int stage, int position,
+                Bundle options, UserHandle user) {
+            if (!verifyCaller("startShortcut")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s ->
+                        s.startShortcut(packageName, shortcutId, stage, position, options, user));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void startIntent(PendingIntent intent, int stage, int position, Bundle options) {
+            if (!verifyCaller("startIntent")) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mSplitScreenOptional.ifPresent(s ->
+                        s.startIntent(intent, stage, position, options));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -658,6 +761,32 @@
     private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
             = this::cleanupAfterDeath;
 
+    private ISplitScreenListener mISplitScreenListener;
+    private final SplitScreen.SplitScreenListener mSplitScreenListener =
+            new SplitScreen.SplitScreenListener() {
+        @Override
+        public void onStagePositionChanged(int stage, int position) {
+            try {
+                if (mISplitScreenListener != null) {
+                    mISplitScreenListener.onStagePositionChanged(stage, position);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG_OPS, "onStagePositionChanged", e);
+            }
+        }
+
+        @Override
+        public void onTaskStageChanged(int taskId, int stage) {
+            try {
+                if (mISplitScreenListener != null) {
+                    mISplitScreenListener.onTaskStageChanged(taskId, stage);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG_OPS, "onTaskStageChanged", e);
+            }
+        }
+    };
+
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
     public OverviewProxyService(Context context, CommandQueue commandQueue,
@@ -665,7 +794,8 @@
             NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
             Optional<Pip> pipOptional,
-            Optional<LegacySplitScreen> splitScreenOptional,
+            Optional<LegacySplitScreen> legacySplitScreenOptional,
+            Optional<SplitScreen> splitScreenOptional,
             Optional<Lazy<StatusBar>> statusBarOptionalLazy,
             Optional<OneHanded> oneHandedOptional,
             BroadcastDispatcher broadcastDispatcher,
@@ -718,9 +848,10 @@
         });
         mCommandQueue = commandQueue;
 
-        splitScreenOptional.ifPresent(splitScreen ->
-                splitScreen.registerBoundsChangeListener(mSplitScreenBoundsChangeListener));
         mSplitScreenOptional = splitScreenOptional;
+        legacySplitScreenOptional.ifPresent(splitScreen ->
+                splitScreen.registerBoundsChangeListener(mSplitScreenBoundsChangeListener));
+        mLegacySplitScreenOptional = legacySplitScreenOptional;
 
         // Listen for user setup
         startTracking();
@@ -835,7 +966,7 @@
         startConnectionToCurrentUser();
 
         // Clean up the minimized state if launcher dies
-        mSplitScreenOptional.ifPresent(
+        mLegacySplitScreenOptional.ifPresent(
                 splitScreen -> splitScreen.setMinimized(false));
 
         // Clean up any registered remote transitions
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 9037192..5438743 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -187,7 +187,7 @@
      * @param refreshRate Desired refresh rate
      * @return array with supported width, height, and refresh rate
      */
-    private int[] getSupportedSize(int screenWidth, int screenHeight, int refreshRate) {
+    private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate) {
         double maxScale = 0;
 
         MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
@@ -207,25 +207,33 @@
                     int width = vc.getSupportedWidths().getUpper();
                     int height = vc.getSupportedHeights().getUpper();
 
-                    if (width >= screenWidth && height >= screenHeight
-                            && vc.isSizeSupported(screenWidth, screenHeight)) {
+                    int screenWidthAligned = screenWidth;
+                    if (screenWidthAligned % vc.getWidthAlignment() != 0) {
+                        screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment());
+                    }
+                    int screenHeightAligned = screenHeight;
+                    if (screenHeightAligned % vc.getHeightAlignment() != 0) {
+                        screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment());
+                    }
 
+                    if (width >= screenWidthAligned && height >= screenHeightAligned
+                            && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) {
                         // Desired size is supported, now get the rate
-                        int maxRate = vc.getSupportedFrameRatesFor(screenWidth, screenHeight)
-                                .getUpper().intValue();
+                        int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned,
+                                screenHeightAligned).getUpper().intValue();
 
                         if (maxRate < refreshRate) {
                             refreshRate = maxRate;
                         }
                         Log.d(TAG, "Screen size supported at rate " + refreshRate);
-                        return new int[]{screenWidth, screenHeight, refreshRate};
+                        return new int[]{screenWidthAligned, screenHeightAligned, refreshRate};
                     }
 
                     // Otherwise, continue searching
                     double scale = Math.min(((double) width / screenWidth),
                             ((double) height / screenHeight));
                     if (scale > maxScale) {
-                        maxScale = scale;
+                        maxScale = Math.min(1, scale);
                         maxInfo = vc;
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
new file mode 100644
index 0000000..c8afd0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2021 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.screenshot;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * CropView has top and bottom draggable crop handles, with a scrim to darken the areas being
+ * cropped out.
+ */
+public class CropView extends View {
+    public enum CropBoundary {
+        NONE, TOP, BOTTOM
+    }
+
+    private final float mCropTouchMargin;
+    private final Paint mShadePaint;
+    private final Paint mHandlePaint;
+
+    // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the
+    // view, respectively.
+    private float mTopCrop = 0f;
+    private float mBottomCrop = 1f;
+
+    // When the user is dragging a handle, these variables store the distance between the top/bottom
+    // crop values and
+    private float mTopDelta = 0f;
+    private float mBottomDelta = 0f;
+
+    private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE;
+    private float mStartingY;  // y coordinate of ACTION_DOWN
+    private CropInteractionListener mCropInteractionListener;
+
+    public CropView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        TypedArray t = context.getTheme().obtainStyledAttributes(
+                attrs, R.styleable.CropView, 0, 0);
+        mShadePaint = new Paint();
+        mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT));
+        mHandlePaint = new Paint();
+        mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK));
+        mHandlePaint.setStrokeWidth(
+                t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20));
+        t.recycle();
+        // 48 dp touchable region around each handle.
+        mCropTouchMargin = 24 * getResources().getDisplayMetrics().density;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        float top = mTopCrop + mTopDelta;
+        float bottom = mBottomCrop + mBottomDelta;
+        drawShade(canvas, 0, top);
+        drawShade(canvas, bottom, 1f);
+        drawHandle(canvas, top);
+        drawHandle(canvas, bottom);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int topPx = fractionToPixels(mTopCrop);
+        int bottomPx = fractionToPixels(mBottomCrop);
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx);
+                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
+                    mStartingY = event.getY();
+                    updateListener(event);
+                }
+                return true;
+            case MotionEvent.ACTION_MOVE:
+                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
+                    float delta = event.getY() - mStartingY;
+                    if (mCurrentDraggingBoundary == CropBoundary.TOP) {
+                        mTopDelta = pixelsToFraction((int) MathUtils.constrain(delta, -topPx,
+                                bottomPx - 2 * mCropTouchMargin - topPx));
+                    } else {  // Bottom
+                        mBottomDelta = pixelsToFraction((int) MathUtils.constrain(delta,
+                                topPx + 2 * mCropTouchMargin - bottomPx,
+                                getMeasuredHeight() - bottomPx));
+                    }
+                    updateListener(event);
+                    invalidate();
+                    return true;
+                }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                if (mCurrentDraggingBoundary != CropBoundary.NONE) {
+                    // Commit the delta to the stored crop values.
+                    mTopCrop += mTopDelta;
+                    mBottomCrop += mBottomDelta;
+                    mTopDelta = 0;
+                    mBottomDelta = 0;
+                    updateListener(event);
+                }
+        }
+        return super.onTouchEvent(event);
+    }
+
+    /**
+     * @return value [0,1] representing the position of the top crop boundary. Does not reflect
+     * changes from any in-progress touch input.
+     */
+    public float getTopBoundary() {
+        return mTopCrop;
+    }
+
+    /**
+     * @return value [0,1] representing the position of the bottom crop boundary. Does not reflect
+     * changes from any in-progress touch input.
+     */
+    public float getBottomBoundary() {
+        return mBottomCrop;
+    }
+
+    public void setCropInteractionListener(CropInteractionListener listener) {
+        mCropInteractionListener = listener;
+    }
+
+    private void updateListener(MotionEvent event) {
+        if (mCropInteractionListener != null) {
+            float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP)
+                    ? mTopCrop + mTopDelta : mBottomCrop + mBottomDelta;
+            mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary,
+                    boundaryPosition, fractionToPixels(boundaryPosition));
+        }
+    }
+
+    private void drawShade(Canvas canvas, float fracStart, float fracEnd) {
+        canvas.drawRect(0, fractionToPixels(fracStart), getMeasuredWidth(),
+                fractionToPixels(fracEnd), mShadePaint);
+    }
+
+    private void drawHandle(Canvas canvas, float frac) {
+        int y = fractionToPixels(frac);
+        canvas.drawLine(0, y, getMeasuredWidth(), y, mHandlePaint);
+    }
+
+    private int fractionToPixels(float frac) {
+        return (int) (frac * getMeasuredHeight());
+    }
+
+    private float pixelsToFraction(int px) {
+        return px / (float) getMeasuredHeight();
+    }
+
+    private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) {
+        if (Math.abs(event.getY() - topPx) < mCropTouchMargin) {
+            return CropBoundary.TOP;
+        }
+        if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) {
+            return CropBoundary.BOTTOM;
+        }
+        return CropBoundary.NONE;
+    }
+
+    /**
+     * Listen for crop motion events and state.
+     */
+    public interface CropInteractionListener {
+        /**
+         * Called whenever CropView has a MotionEvent that can impact the position of the crop
+         * boundaries.
+         */
+        void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition,
+                int boundaryPositionPx);
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
index 143121a..212e6c8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTile.java
@@ -19,6 +19,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.NonNull;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
 import android.graphics.RecordingCanvas;
@@ -50,14 +51,13 @@
      * @param image an image containing a hardware buffer
      * @param location the captured area represented by image tile (virtual coordinates)
      */
-    ImageTile(Image image, Rect location) {
+    ImageTile(@NonNull Image image, @NonNull Rect location) {
         mImage = requireNonNull(image, "image");
-        mLocation = location;
-
+        mLocation = requireNonNull(location);
         requireNonNull(mImage.getHardwareBuffer(), "image must be a hardware image");
     }
 
-    RenderNode getDisplayList() {
+    synchronized RenderNode getDisplayList() {
         if (mNode == null) {
             mNode = new RenderNode("Tile{" + Integer.toHexString(mImage.hashCode()) + "}");
         }
@@ -69,7 +69,6 @@
         mNode.setPosition(0, 0, w, h);
 
         RecordingCanvas canvas = mNode.beginRecording(w, h);
-        Rect rect = new Rect(0, 0, w, h);
         canvas.save();
         canvas.clipRect(0, 0, mLocation.right, mLocation.bottom);
         canvas.drawBitmap(Bitmap.wrapHardwareBuffer(mImage.getHardwareBuffer(), COLOR_SPACE),
@@ -100,9 +99,11 @@
     }
 
     @Override
-    public void close() {
+    public synchronized void close() {
         mImage.close();
-        mNode.discardDisplayList();
+        if (mNode != null) {
+            mNode.discardDisplayList();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
index 8ff66f5..20f8451 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -108,6 +108,15 @@
     }
 
     Bitmap toBitmap() {
+        return toBitmap(new Rect(0, 0, getWidth(), getHeight()));
+    }
+
+    /**
+     * @param bounds Selected portion of the tile set's bounds (equivalent to tile bounds coord
+     *               space). For example, to get the whole doc, use Rect(0, 0, getWidth(),
+     *               getHeight()).
+     */
+    Bitmap toBitmap(Rect bounds) {
         if (mTiles.isEmpty()) {
             return null;
         }
@@ -115,6 +124,9 @@
         output.setPosition(0, 0, getWidth(), getHeight());
         RecordingCanvas canvas = output.beginRecording();
         canvas.translate(-getLeft(), -getTop());
+        // Additional translation to account for the requested bounds
+        canvas.translate(-bounds.left, -bounds.top);
+        canvas.clipRect(bounds);
         for (ImageTile tile : mTiles) {
             canvas.save();
             canvas.translate(tile.getLeft(), tile.getTop());
@@ -122,7 +134,7 @@
             canvas.restore();
         }
         output.endRecording();
-        return HardwareRenderer.createHardwareBitmap(output, getWidth(), getHeight());
+        return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
     }
 
     int getLeft() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
new file mode 100644
index 0000000..f887151
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 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.screenshot;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * MagnifierView shows a full-res cropped circular display of a given ImageTileSet, contents and
+ * positioning dereived from events from a CropView to which it listens.
+ *
+ * Not meant to be a general-purpose magnifier!
+ */
+public class MagnifierView extends View implements CropView.CropInteractionListener {
+    private Drawable mDrawable;
+
+    private final Paint mShadePaint;
+    private final Paint mHandlePaint;
+
+    private Path mOuterCircle;
+    private Path mInnerCircle;
+
+    private Path mCheckerboard;
+    private Paint mCheckerboardPaint;
+    private final float mBorderPx;
+    private final int mBorderColor;
+    private float mCheckerboardBoxSize = 40;
+
+    private float mLastCropPosition;
+    private CropView.CropBoundary mCropBoundary;
+
+    public MagnifierView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public MagnifierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        TypedArray t = context.getTheme().obtainStyledAttributes(
+                attrs, R.styleable.MagnifierView, 0, 0);
+        mShadePaint = new Paint();
+        mShadePaint.setColor(t.getColor(R.styleable.MagnifierView_scrimColor, Color.TRANSPARENT));
+        mHandlePaint = new Paint();
+        mHandlePaint.setColor(t.getColor(R.styleable.MagnifierView_handleColor, Color.BLACK));
+        mHandlePaint.setStrokeWidth(
+                t.getDimensionPixelSize(R.styleable.MagnifierView_handleThickness, 20));
+        mBorderPx = t.getDimensionPixelSize(R.styleable.MagnifierView_borderThickness, 0);
+        mBorderColor = t.getColor(R.styleable.MagnifierView_borderColor, Color.WHITE);
+        t.recycle();
+        mCheckerboardPaint = new Paint();
+        mCheckerboardPaint.setColor(Color.GRAY);
+    }
+
+    public void setImageTileset(ImageTileSet tiles) {
+        if (tiles != null) {
+            mDrawable = tiles.getDrawable();
+            mDrawable.setBounds(0, 0, tiles.getWidth(), tiles.getHeight());
+        } else {
+            mDrawable = null;
+        }
+        invalidate();
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        int radius = getWidth() / 2;
+        mOuterCircle = new Path();
+        mOuterCircle.addCircle(radius, radius, radius, Path.Direction.CW);
+        mInnerCircle = new Path();
+        mInnerCircle.addCircle(radius, radius, radius - mBorderPx, Path.Direction.CW);
+        mCheckerboard = generateCheckerboard();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // TODO: just draw a circle at the end instead of clipping like this?
+        canvas.clipPath(mOuterCircle);
+        canvas.drawColor(mBorderColor);
+        canvas.clipPath(mInnerCircle);
+
+        // Draw a checkerboard pattern for out of bounds.
+        canvas.drawPath(mCheckerboard, mCheckerboardPaint);
+
+        if (mDrawable != null) {
+            canvas.save();
+            // Translate such that the center of this view represents the center of the crop
+            // boundary.
+            canvas.translate(-mDrawable.getBounds().width() / 2 + getWidth() / 2,
+                    -mDrawable.getBounds().height() * mLastCropPosition + getHeight() / 2);
+            mDrawable.draw(canvas);
+            canvas.restore();
+        }
+
+        Rect scrimRect = new Rect(0, 0, getWidth(), getHeight() / 2);
+        if (mCropBoundary == CropView.CropBoundary.BOTTOM) {
+            scrimRect.offset(0, getHeight() / 2);
+        }
+        canvas.drawRect(scrimRect, mShadePaint);
+
+        canvas.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2, mHandlePaint);
+    }
+
+    @Override
+    public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary,
+            float cropPosition, int cropPositionPx) {
+        mCropBoundary = boundary;
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                mLastCropPosition = cropPosition;
+                setTranslationY(cropPositionPx - getHeight() / 2);
+                setPivotX(getWidth() / 2);
+                setPivotY(getHeight() / 2);
+                setScaleX(0.2f);
+                setScaleY(0.2f);
+                setAlpha(0f);
+                setTranslationX((getParentWidth() - getWidth()) / 2);
+                setVisibility(View.VISIBLE);
+                animate().alpha(1f).translationX(0).scaleX(1f).scaleY(1f).start();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                mLastCropPosition = cropPosition;
+                setTranslationY(cropPositionPx - getHeight() / 2);
+                invalidate();
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                animate().alpha(0).translationX((getParentWidth() - getWidth()) / 2).scaleX(0.2f)
+                        .scaleY(0.2f).withEndAction(() -> setVisibility(View.INVISIBLE)).start();
+                break;
+        }
+    }
+
+    private Path generateCheckerboard() {
+        Path path = new Path();
+        int checkerWidth = (int) Math.ceil(getWidth() / mCheckerboardBoxSize);
+        int checkerHeight = (int) Math.ceil(getHeight() / mCheckerboardBoxSize);
+
+        for (int row = 0; row < checkerHeight; row++) {
+            // Alternate starting on the first and second column;
+            int colStart = (row % 2 == 0) ? 0 : 1;
+            for (int col = colStart; col < checkerWidth; col += 2) {
+                path.addRect(col * mCheckerboardBoxSize,
+                        row * mCheckerboardBoxSize,
+                        (col + 1) * mCheckerboardBoxSize,
+                        (row + 1) * mCheckerboardBoxSize,
+                        Path.Direction.CW);
+            }
+        }
+        return path;
+    }
+
+    private int getParentWidth() {
+        return ((View) getParent()).getWidth();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b71036c..953b40b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -49,7 +49,6 @@
 import android.hardware.display.DisplayManager;
 import android.media.MediaActionSound;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -67,6 +66,7 @@
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -80,13 +80,15 @@
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.applications.InterestingConfigChanges;
 import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
 import com.android.systemui.util.DeviceConfigProxy;
 
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
@@ -173,7 +175,7 @@
     private final UiEventLogger mUiEventLogger;
     private final ImageExporter mImageExporter;
     private final Executor mMainExecutor;
-    private final Executor mBgExecutor;
+    private final ExecutorService mBgExecutor;
 
     private final WindowManager mWindowManager;
     private final WindowManager.LayoutParams mWindowLayoutParams;
@@ -182,17 +184,14 @@
     private final ScrollCaptureClient mScrollCaptureClient;
     private final DeviceConfigProxy mConfigProxy;
     private final PhoneWindow mWindow;
-    private final View mDecorView;
     private final DisplayManager mDisplayManager;
 
-    private final Binder mWindowToken;
     private ScreenshotView mScreenshotView;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
 
     private Animator mScreenshotAnimation;
-
-    private Runnable mOnCompleteRunnable;
+    private RequestCallback mCurrentRequestCallback;
 
     private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
         @Override
@@ -229,15 +228,14 @@
             UiEventLogger uiEventLogger,
             DeviceConfigProxy configProxy,
             ImageExporter imageExporter,
-            @Main Executor mainExecutor,
-            @Background Executor bgExecutor) {
+            @Main Executor mainExecutor) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
         mScrollCaptureClient = scrollCaptureClient;
         mUiEventLogger = uiEventLogger;
         mImageExporter = imageExporter;
         mMainExecutor = mainExecutor;
-        mBgExecutor = bgExecutor;
+        mBgExecutor = Executors.newSingleThreadExecutor();
 
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
@@ -247,9 +245,6 @@
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
         mConfigProxy = configProxy;
 
-        mWindowToken = new Binder("ScreenshotController");
-        mScrollCaptureClient.setHostWindowToken(mWindowToken);
-
         // Setup the window that we are going to use
         mWindowLayoutParams = new WindowManager.LayoutParams(
                 MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT,
@@ -263,7 +258,6 @@
         mWindowLayoutParams.setTitle("ScreenshotAnimation");
         mWindowLayoutParams.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-        mWindowLayoutParams.token = mWindowToken;
         // This is needed to let touches pass through outside the touchable areas
         mWindowLayoutParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
@@ -272,8 +266,8 @@
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
         mWindow.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
         mWindow.setBackgroundDrawableResource(android.R.color.transparent);
-        mDecorView = mWindow.getDecorView();
 
+        mConfigChanges.applyNewConfig(context.getResources());
         reloadAssets();
 
         // Setup the Camera shutter sound
@@ -281,9 +275,8 @@
         mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
     }
 
-    void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) {
-        mOnCompleteRunnable = onComplete;
-
+    void takeScreenshotFullscreen(Consumer<Uri> finisher, RequestCallback requestCallback) {
+        mCurrentRequestCallback = requestCallback;
         DisplayMetrics displayMetrics = new DisplayMetrics();
         getDefaultDisplay().getRealMetrics(displayMetrics);
         takeScreenshotInternal(
@@ -293,16 +286,14 @@
 
     void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
             Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
-            Consumer<Uri> finisher, Runnable onComplete) {
+            Consumer<Uri> finisher, RequestCallback requestCallback) {
         // TODO: use task Id, userId, topComponent for smart handler
-        mOnCompleteRunnable = onComplete;
 
         if (screenshot == null) {
             Log.e(TAG, "Got null bitmap from screenshot message");
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_capture_text);
-            finisher.accept(null);
-            mOnCompleteRunnable.run();
+            requestCallback.reportError();
             return;
         }
 
@@ -312,17 +303,19 @@
             visibleInsets = Insets.NONE;
             screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
         }
+        mCurrentRequestCallback = requestCallback;
         saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, showFlash);
     }
 
     /**
      * Displays a screenshot selector
      */
-    void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
-        dismissScreenshot(true);
-        mOnCompleteRunnable = onComplete;
+    void takeScreenshotPartial(final Consumer<Uri> finisher, RequestCallback requestCallback) {
+        mScreenshotView.reset();
+        mCurrentRequestCallback = requestCallback;
 
-        mWindowManager.addView(mScreenshotView, mWindowLayoutParams);
+        attachWindow();
+        mWindow.setContentView(mScreenshotView);
 
         mScreenshotView.takePartialScreenshot(
                 rect -> takeScreenshotInternal(finisher, rect));
@@ -343,9 +336,9 @@
             }
             return;
         }
-        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+        cancelTimeout();
         if (immediate) {
-            resetScreenshotView();
+            finishDismiss();
         } else {
             mScreenshotView.animateDismissal();
         }
@@ -360,6 +353,8 @@
      */
     void releaseContext() {
         mContext.release();
+        mCameraSound.release();
+        mBgExecutor.shutdownNow();
     }
 
     /**
@@ -369,12 +364,8 @@
         if (DEBUG_UI) {
             Log.d(TAG, "reloadAssets()");
         }
-        boolean wasAttached = mDecorView.isAttachedToWindow();
-        if (wasAttached) {
-            if (DEBUG_WINDOW) {
-                Log.d(TAG, "Removing screenshot window");
-            }
-            mWindowManager.removeView(mDecorView);
+        if (mScreenshotView != null && mScreenshotView.isAttachedToWindow()) {
+            mWindow.clearContentView(); // Is there a simpler way to say "remove screenshotView?"
         }
 
         // respect the display cutout in landscape (since we'd otherwise overlap) but not portrait
@@ -382,12 +373,6 @@
         mWindowLayoutParams.setFitInsetsTypes(
                 orientation == ORIENTATION_PORTRAIT ? 0 : WindowInsets.Type.displayCutout());
 
-        // ignore system bar insets for the purpose of window layout
-        mDecorView.setOnApplyWindowInsetsListener((v, insets) -> v.onApplyWindowInsets(
-                new WindowInsets.Builder(insets)
-                        .setInsets(WindowInsets.Type.all(), Insets.NONE)
-                        .build()));
-
         // Inflate the screenshot layout
         mScreenshotView = (ScreenshotView)
                 LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
@@ -399,12 +384,12 @@
 
             @Override
             public void onDismiss() {
-                resetScreenshotView();
+                finishDismiss();
             }
         });
 
         // TODO(159460485): Remove this when focus is handled properly in the system
-        mDecorView.setOnTouchListener((v, event) -> {
+        mScreenshotView.setOnTouchListener((v, event) -> {
             if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onTouch: ACTION_OUTSIDE");
@@ -426,8 +411,10 @@
             return false;
         });
 
-        // view is added to window manager in startAnimation
-        mWindow.setContentView(mScreenshotView, mWindowLayoutParams);
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "adding OnComputeInternalInsetsListener");
+        }
+        mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
     }
 
     /**
@@ -464,14 +451,9 @@
             Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null");
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_capture_text);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "Supplying null to Consumer<Uri>");
+            if (mCurrentRequestCallback != null) {
+                mCurrentRequestCallback.reportError();
             }
-            finisher.accept(null);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "Calling mOnCompleteRunnable.run()");
-            }
-            mOnCompleteRunnable.run();
             return;
         }
 
@@ -488,6 +470,13 @@
             mAccessibilityManager.sendAccessibilityEvent(event);
         }
 
+        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+            if (DEBUG_UI) {
+                Log.d(TAG, "saveScreenshot: reloading assets");
+            }
+            reloadAssets();
+        }
+
         if (mScreenshotView.isAttachedToWindow()) {
             // if we didn't already dismiss for another reason
             if (!mScreenshotView.isDismissing()) {
@@ -514,33 +503,115 @@
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
 
-        if (mConfigChanges.applyNewConfig(mContext.getResources())) {
-            if (DEBUG_UI) {
-                Log.d(TAG, "saveScreenshot: reloading assets");
-            }
-            reloadAssets();
-        }
+        saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady);
 
         // The window is focusable by default
         setWindowFocusable(true);
 
-        // Start the post-screenshot animation
-        startAnimation(finisher, screenRect, screenInsets, showFlash);
-
         if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) {
-            mScrollCaptureClient.request(DEFAULT_DISPLAY, (connection) ->
-                    mScreenshotView.showScrollChip(() ->
-                            runScrollCapture(connection,
-                                    () -> mScreenshotHandler.post(
-                                            () -> dismissScreenshot(false)))));
+            View decorView = mWindow.getDecorView();
+
+            // Wait until this window is attached to request because it is
+            // the reference used to locate the target window (below).
+            withWindowAttached(() -> {
+                mScrollCaptureClient.setHostWindowToken(decorView.getWindowToken());
+                mScrollCaptureClient.request(DEFAULT_DISPLAY,
+                        /* onConnection */
+                        (connection) -> mScreenshotHandler.post(() ->
+                                mScreenshotView.showScrollChip(() ->
+                                        /* onClick */
+                                        runScrollCapture(connection))));
+            });
+        }
+
+
+        attachWindow();
+        mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    @Override
+                    public boolean onPreDraw() {
+                        if (DEBUG_WINDOW) {
+                            Log.d(TAG, "onPreDraw: startAnimation");
+                        }
+                        mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
+                        startAnimation(screenRect, showFlash);
+                        return true;
+                    }
+                });
+        mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "setContentView: " + mScreenshotView);
+        }
+        setContentView(mScreenshotView);
+        // ignore system bar insets for the purpose of window layout
+        mWindow.getDecorView().setOnApplyWindowInsetsListener(
+                (v, insets) -> WindowInsets.CONSUMED);
+        cancelTimeout(); // restarted after animation
+    }
+
+    private void withWindowAttached(Runnable action) {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            action.run();
+        } else {
+            decorView.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                        @Override
+                        public void onWindowAttached() {
+                            decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+                            action.run();
+                        }
+
+                        @Override
+                        public void onWindowDetached() { }
+                    });
+
         }
     }
 
-    private void runScrollCapture(ScrollCaptureClient.Connection connection, Runnable andThen) {
+    private void setContentView(View contentView) {
+        mWindow.setContentView(contentView);
+    }
+
+    private void attachWindow() {
+        View decorView = mWindow.getDecorView();
+        if (decorView.isAttachedToWindow()) {
+            return;
+        }
+        if (DEBUG_WINDOW) {
+            Log.d(TAG, "attachWindow");
+        }
+        mWindowManager.addView(decorView, mWindowLayoutParams);
+        decorView.requestApplyInsets();
+    }
+
+    void removeWindow() {
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            if (DEBUG_WINDOW) {
+                Log.d(TAG, "Removing screenshot window");
+            }
+            mWindowManager.removeViewImmediate(decorView);
+        }
+    }
+
+    private void runScrollCapture(ScrollCaptureClient.Connection connection) {
+        cancelTimeout();
         ScrollCaptureController controller = new ScrollCaptureController(mContext, connection,
-                mMainExecutor, mBgExecutor, mImageExporter);
-        controller.run(andThen);
+                mMainExecutor, mBgExecutor, mImageExporter, mUiEventLogger);
+        controller.attach(mWindow);
+        controller.start(new TakeScreenshotService.RequestCallback() {
+            @Override
+            public void reportError() {
+            }
+
+            @Override
+            public void onFinish() {
+                Log.d(TAG, "onFinish from ScrollCaptureController");
+                finishDismiss();
+            }
+        });
     }
 
     /**
@@ -549,11 +620,11 @@
      */
     private void saveScreenshotAndToast(Consumer<Uri> finisher) {
         // Play the shutter sound to notify that we've taken a screenshot
-        mScreenshotHandler.post(() -> {
-            mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
-        });
+        mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
 
-        saveScreenshotInWorkerThread(finisher, imageData -> {
+        saveScreenshotInWorkerThread(
+                /* onComplete */ finisher,
+                /* actionsReadyListener */ imageData -> {
             if (DEBUG_CALLBACK) {
                 Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri);
             }
@@ -573,55 +644,35 @@
     /**
      * Starts the animation after taking the screenshot
      */
-    private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
-            boolean showFlash) {
-        mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
-        mScreenshotHandler.post(() -> {
-            if (!mScreenshotView.isAttachedToWindow()) {
-                if (DEBUG_WINDOW) {
-                    Log.d(TAG, "Adding screenshot window");
-                }
-                mWindowManager.addView(mWindow.getDecorView(), mWindowLayoutParams);
-            }
+    private void startAnimation(Rect screenRect, boolean showFlash) {
+        if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
+            mScreenshotAnimation.cancel();
+        }
 
-            mScreenshotView.prepareForAnimation(mScreenBitmap, screenInsets);
+        mScreenshotAnimation =
+                mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
 
-            mScreenshotHandler.post(() -> {
-                if (DEBUG_WINDOW) {
-                    Log.d(TAG, "adding OnComputeInternalInsetsListener");
-                }
-                mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(
-                        mScreenshotView);
+        // Play the shutter sound to notify that we've taken a screenshot
+        mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
 
-                mScreenshotAnimation =
-                        mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
-
-                saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady);
-
-                // Play the shutter sound to notify that we've taken a screenshot
-                mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
-
-                if (DEBUG_ANIM) {
-                    Log.d(TAG, "starting post-screenshot animation");
-                }
-                mScreenshotAnimation.start();
-            });
-        });
+        if (DEBUG_ANIM) {
+            Log.d(TAG, "starting post-screenshot animation");
+        }
+        mScreenshotAnimation.start();
     }
 
     /** Reset screenshot view and then call onCompleteRunnable */
-    private void resetScreenshotView() {
+    private void finishDismiss() {
         if (DEBUG_UI) {
-            Log.d(TAG, "resetScreenshotView");
+            Log.d(TAG, "finishDismiss");
         }
-        if (mScreenshotView.isAttachedToWindow()) {
-            if (DEBUG_WINDOW) {
-                Log.d(TAG, "Removing screenshot window");
-            }
-            mWindowManager.removeView(mDecorView);
-        }
+        cancelTimeout();
+        removeWindow();
         mScreenshotView.reset();
-        mOnCompleteRunnable.run();
+        if (mCurrentRequestCallback != null) {
+            mCurrentRequestCallback.onFinish();
+            mCurrentRequestCallback = null;
+        }
     }
 
     /**
@@ -645,8 +696,12 @@
         mSaveInBgTask.execute();
     }
 
-    private void resetTimeout() {
+    private void cancelTimeout() {
         mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+    }
+
+    private void resetTimeout() {
+        cancelTimeout();
 
         AccessibilityManager accessibilityManager = (AccessibilityManager)
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -705,7 +760,7 @@
 
                 @Override
                 public void hideSharedElements() {
-                    resetScreenshotView();
+                    finishDismiss();
                 }
 
                 @Override
@@ -753,13 +808,21 @@
         if (DEBUG_WINDOW) {
             Log.d(TAG, "setWindowFocusable: " + focusable);
         }
+        int flags = mWindowLayoutParams.flags;
         if (focusable) {
             mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
         } else {
             mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
         }
-        if (mDecorView.isAttachedToWindow()) {
-            mWindowManager.updateViewLayout(mDecorView, mWindowLayoutParams);
+        if (mWindowLayoutParams.flags == flags) {
+            if (DEBUG_WINDOW) {
+                Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
+            }
+            return;
+        }
+        final View decorView = mWindow.peekDecorView();
+        if (decorView != null && decorView.isAttachedToWindow()) {
+            mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index f1fb688..5cf0188 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -63,7 +63,15 @@
     @UiEvent(doc = "screenshot swiped to dismiss")
     SCREENSHOT_SWIPE_DISMISSED(656),
     @UiEvent(doc = "screenshot reentered for new screenshot")
-    SCREENSHOT_REENTERED(640);
+    SCREENSHOT_REENTERED(640),
+    @UiEvent(doc = "Long screenshot button was shown to the user")
+    SCREENSHOT_LONG_SCREENSHOT_IMPRESSION(687),
+    @UiEvent(doc = "User has requested a long screenshot")
+    SCREENSHOT_LONG_SCREENSHOT_REQUESTED(688),
+    @UiEvent(doc = "User has shared a long screenshot")
+    SCREENSHOT_LONG_SCREENSHOT_SHARE(689),
+    @UiEvent(doc = "User has sent a long screenshot to the editor")
+    SCREENSHOT_LONG_SCREENSHOT_EDIT(690);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 211f507..5c650ad 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -192,14 +192,14 @@
         if (DEBUG_SCROLL) {
             Log.d(TAG, "Showing Scroll option");
         }
+        mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION);
         mScrollChip.setVisibility(VISIBLE);
         mScrollChip.setOnClickListener((v) -> {
             if (DEBUG_INPUT) {
                 Log.d(TAG, "scroll chip tapped");
             }
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED);
             onClick.run();
-            // TODO Logging, store event consumer to a field
-            //onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
         });
     }
 
@@ -316,21 +316,19 @@
         mScreenshotSelectorView.requestFocus();
     }
 
-    void prepareForAnimation(Bitmap bitmap, Insets screenInsets) {
+    void setScreenshot(Bitmap bitmap, Insets screenInsets) {
         mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
-        // make static preview invisible (from gone) so we can query its location on screen
-        mScreenshotPreview.setVisibility(View.INVISIBLE);
     }
 
     AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
-        mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        mScreenshotPreview.buildLayer();
+        if (DEBUG_ANIM) {
+            Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash);
+        }
 
-        Rect previewBounds = new Rect();
-        mScreenshotPreview.getBoundsOnScreen(previewBounds);
-        int[] previewLocation = new int[2];
-        mScreenshotPreview.getLocationInWindow(previewLocation);
+        Rect targetPosition = new Rect();
+        mScreenshotPreview.getHitRect(targetPosition);
 
+        // ratio of preview width, end vs. start size
         float cornerScale =
                 mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
         final float currentScale = 1 / cornerScale;
@@ -358,8 +356,13 @@
 
         // animate from the current location, to the static preview location
         final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
-        final PointF finalPos = new PointF(previewLocation[0] + previewBounds.width() / 2f,
-                previewLocation[1] + previewBounds.height() / 2f);
+        final PointF finalPos = new PointF(targetPosition.exactCenterX(),
+                targetPosition.exactCenterY());
+
+        if (DEBUG_ANIM) {
+            Log.d(TAG, "toCorner: startPos=" + startPos);
+            Log.d(TAG, "toCorner: finalPos=" + finalPos);
+        }
 
         ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
         toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
@@ -427,7 +430,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (DEBUG_ANIM) {
-                    Log.d(TAG, "drop-in animation completed");
+                    Log.d(TAG, "drop-in animation ended");
                 }
                 mDismissButton.setOnClickListener(view -> {
                     if (DEBUG_INPUT) {
@@ -653,13 +656,12 @@
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
         // Clear any references to the bitmap
         mScreenshotPreview.setImageDrawable(null);
+        mScreenshotPreview.setVisibility(View.INVISIBLE);
         mPendingSharedTransition = false;
         mActionsContainerBackground.setVisibility(View.GONE);
         mActionsContainer.setVisibility(View.GONE);
         mBackgroundProtection.setAlpha(0f);
         mDismissButton.setVisibility(View.GONE);
-        mScreenshotPreview.setVisibility(View.GONE);
-        mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
         mScreenshotStatic.setTranslationX(0);
         mScreenshotPreview.setTranslationY(0);
         mScreenshotPreview.setContentDescription(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 54b1b2c..25438a6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,16 +16,27 @@
 
 package com.android.systemui.screenshot;
 
+import android.annotation.IdRes;
+import android.annotation.UiThread;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.net.Uri;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
-import android.widget.Toast;
+import android.view.View;
+import android.view.ViewTreeObserver.InternalInsetsInfo;
+import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.Window;
+import android.widget.ImageView;
 
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
 import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
 import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -38,10 +49,15 @@
 /**
  * Interaction controller between the UI and ScrollCaptureClient.
  */
-public class ScrollCaptureController {
+public class ScrollCaptureController implements OnComputeInternalInsetsListener {
     private static final String TAG = "ScrollCaptureController";
 
-    private static final boolean USE_TILED_IMAGE = false;
+    // TODO: Support saving without additional action.
+    private enum PendingAction {
+        SHARE,
+        EDIT,
+        SAVE
+    }
 
     public static final int MAX_PAGES = 5;
     public static final int MAX_HEIGHT = 12000;
@@ -53,32 +69,171 @@
     private final Executor mBgExecutor;
     private final ImageExporter mImageExporter;
     private final ImageTileSet mImageTileSet;
+    private final UiEventLogger mUiEventLogger;
 
     private ZonedDateTime mCaptureTime;
     private UUID mRequestId;
+    private RequestCallback mCallback;
+    private Window mWindow;
+    private ImageView mPreview;
+    private View mSave;
+    private View mCancel;
+    private View mEdit;
+    private View mShare;
+    private CropView mCropView;
+    private MagnifierView mMagnifierView;
 
     public ScrollCaptureController(Context context, Connection connection, Executor uiExecutor,
-            Executor bgExecutor, ImageExporter exporter) {
+            Executor bgExecutor, ImageExporter exporter, UiEventLogger uiEventLogger) {
         mContext = context;
         mConnection = connection;
         mUiExecutor = uiExecutor;
         mBgExecutor = bgExecutor;
         mImageExporter = exporter;
+        mUiEventLogger = uiEventLogger;
         mImageTileSet = new ImageTileSet();
     }
 
     /**
+     * @param window the window to display the preview
+     */
+    public void attach(Window window) {
+        mWindow = window;
+    }
+
+    /**
      * Run scroll capture!
      *
-     * @param after action to take after the flow is complete
+     * @param callback request callback to report back to the service
      */
-    public void run(final Runnable after) {
+    public void start(RequestCallback callback) {
         mCaptureTime = ZonedDateTime.now();
         mRequestId = UUID.randomUUID();
-        mConnection.start((session) -> startCapture(session, after));
+        mCallback = callback;
+
+        setContentView(R.layout.long_screenshot);
+        mWindow.getDecorView().getViewTreeObserver()
+                .addOnComputeInternalInsetsListener(this);
+        mPreview = findViewById(R.id.preview);
+
+        mSave = findViewById(R.id.save);
+        mCancel = findViewById(R.id.cancel);
+        mEdit = findViewById(R.id.edit);
+        mShare = findViewById(R.id.share);
+        mCropView = findViewById(R.id.crop_view);
+        mMagnifierView = findViewById(R.id.magnifier);
+        mCropView.setCropInteractionListener(mMagnifierView);
+
+        mSave.setOnClickListener(this::onClicked);
+        mCancel.setOnClickListener(this::onClicked);
+        mEdit.setOnClickListener(this::onClicked);
+        mShare.setOnClickListener(this::onClicked);
+
+        mConnection.start(this::startCapture);
     }
 
-    private void startCapture(Session session, final Runnable onDismiss) {
+
+    /** Ensure the entire window is touchable */
+    public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
+        inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+    }
+
+    void disableButtons() {
+        mSave.setEnabled(false);
+        mCancel.setEnabled(false);
+        mEdit.setEnabled(false);
+        mShare.setEnabled(false);
+    }
+
+    private void onClicked(View v) {
+        Log.d(TAG, "button clicked!");
+
+        int id = v.getId();
+        v.setPressed(true);
+        disableButtons();
+        if (id == R.id.save) {
+            startExport(PendingAction.SAVE);
+        } else if (id == R.id.cancel) {
+            doFinish();
+        } else if (id == R.id.edit) {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_EDIT);
+            startExport(PendingAction.EDIT);
+        } else if (id == R.id.share) {
+            mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_SHARE);
+            startExport(PendingAction.SHARE);
+        }
+    }
+
+    private void doFinish() {
+        mPreview.setImageDrawable(null);
+        mMagnifierView.setImageTileset(null);
+        mImageTileSet.clear();
+        mCallback.onFinish();
+        mWindow.getDecorView().getViewTreeObserver()
+                .removeOnComputeInternalInsetsListener(this);
+    }
+
+    private void startExport(PendingAction action) {
+        Rect croppedPortion = new Rect(
+                0,
+                (int) (mImageTileSet.getHeight() * mCropView.getTopBoundary()),
+                mImageTileSet.getWidth(),
+                (int) (mImageTileSet.getHeight() * mCropView.getBottomBoundary()));
+        ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
+                mBgExecutor, mRequestId, mImageTileSet.toBitmap(croppedPortion), mCaptureTime);
+        exportFuture.addListener(() -> {
+            try {
+                ImageExporter.Result result = exportFuture.get();
+                if (action == PendingAction.EDIT) {
+                    doEdit(result.uri);
+                } else if (action == PendingAction.SHARE) {
+                    doShare(result.uri);
+                }
+                doFinish();
+            } catch (InterruptedException | ExecutionException e) {
+                Log.e(TAG, "failed to export", e);
+                mCallback.onFinish();
+            }
+        }, mUiExecutor);
+    }
+
+    private void doEdit(Uri uri) {
+        String editorPackage = mContext.getString(R.string.config_screenshotEditor);
+        Intent intent = new Intent(Intent.ACTION_EDIT);
+        if (!TextUtils.isEmpty(editorPackage)) {
+            intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+        }
+        intent.setType("image/png");
+        intent.setData(uri);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+    }
+
+    private void doShare(Uri uri) {
+        Intent intent = new Intent(Intent.ACTION_SEND);
+        intent.setType("image/png");
+        intent.setData(uri);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        Intent sharingChooserIntent = Intent.createChooser(intent, null)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        mContext.startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+    }
+
+    private void setContentView(@IdRes int id) {
+        mWindow.setContentView(id);
+    }
+
+    <T extends View> T findViewById(@IdRes int res) {
+        return mWindow.findViewById(res);
+    }
+
+    private void startCapture(Session session) {
+        Log.d(TAG, "startCapture");
         Consumer<ScrollCaptureClient.CaptureResult> consumer =
                 new Consumer<ScrollCaptureClient.CaptureResult>() {
 
@@ -91,17 +246,17 @@
 
                         boolean emptyFrame = result.captured.height() == 0;
                         if (!emptyFrame) {
-                            mImageTileSet.addTile(new ImageTile(result.image, result.captured));
+                            ImageTile tile = new ImageTile(result.image, result.captured);
+                            Log.d(TAG, "Adding tile: " + tile);
+                            mImageTileSet.addTile(tile);
+                            Log.d(TAG, "New dimens: w=" + mImageTileSet.getWidth() + ", "
+                                    + "h=" + mImageTileSet.getHeight());
                         }
 
                         if (emptyFrame || mFrameCount >= MAX_PAGES
                                 || mTop + session.getTileHeight() > MAX_HEIGHT) {
-                            if (!mImageTileSet.isEmpty()) {
-                                exportToFile(mImageTileSet.toBitmap(), session, onDismiss);
-                                mImageTileSet.clear();
-                            } else {
-                                session.end(onDismiss);
-                            }
+
+                            mUiExecutor.execute(() -> afterCaptureComplete(session));
                             return;
                         }
                         mTop += result.captured.height();
@@ -113,29 +268,15 @@
         session.requestTile(0, consumer);
     };
 
-    void exportToFile(Bitmap bitmap, Session session, Runnable afterEnd) {
-        mImageExporter.setFormat(Bitmap.CompressFormat.PNG);
-        mImageExporter.setQuality(6);
-        ListenableFuture<ImageExporter.Result> future =
-                mImageExporter.export(mBgExecutor, mRequestId, bitmap, mCaptureTime);
-        future.addListener(() -> {
-            try {
-                ImageExporter.Result result = future.get();
-                launchViewer(result.uri);
-            } catch (InterruptedException | ExecutionException e) {
-                Toast.makeText(mContext, "Failed to write image", Toast.LENGTH_SHORT).show();
-                Log.e(TAG, "Error storing screenshot to media store", e.getCause());
-            }
-            session.end(afterEnd); // end session, close connection, afterEnd.run()
-        }, mUiExecutor);
-    }
+    @UiThread
+    void afterCaptureComplete(Session session) {
+        Log.d(TAG, "afterCaptureComplete");
 
-    void launchViewer(Uri uri) {
-        Intent editIntent = new Intent(Intent.ACTION_VIEW);
-        editIntent.setType("image/png");
-        editIntent.setData(uri);
-        editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        mContext.startActivityAsUser(editIntent, UserHandle.CURRENT);
+        if (mImageTileSet.isEmpty()) {
+            session.end(mCallback::onFinish);
+        } else {
+            mPreview.setImageDrawable(mImageTileSet.getDrawable());
+            mMagnifierView.setImageTileset(mImageTileSet);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 144ad39..daa9d09 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -25,6 +25,7 @@
 import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE;
 import static com.android.systemui.screenshot.LogConfig.logTag;
 
+import android.annotation.MainThread;
 import android.app.Service;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -59,7 +60,8 @@
 public class TakeScreenshotService extends Service {
     private static final String TAG = logTag(TakeScreenshotService.class);
 
-    private final ScreenshotController mScreenshot;
+    private ScreenshotController mScreenshot;
+
     private final UserManager mUserManager;
     private final UiEventLogger mUiEventLogger;
     private final ScreenshotNotificationsController mNotificationsController;
@@ -79,6 +81,15 @@
         }
     };
 
+    /** Informs about coarse grained state of the Controller. */
+    interface RequestCallback {
+        /** Respond to the current request indicating the screenshot request failed.*/
+        void reportError();
+
+        /** The controller has completed handling this request UI has been removed */
+        void onFinish();
+    }
+
     @Inject
     public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager,
             UiEventLogger uiEventLogger,
@@ -116,7 +127,8 @@
             Log.d(TAG, "onUnbind");
         }
         if (mScreenshot != null) {
-            mScreenshot.dismissScreenshot(true);
+            mScreenshot.removeWindow();
+            mScreenshot = null;
         }
         unregisterReceiver(mCloseSystemDialogs);
         return false;
@@ -126,18 +138,39 @@
     public void onDestroy() {
         super.onDestroy();
         if (mScreenshot != null) {
+            mScreenshot.removeWindow();
             mScreenshot.releaseContext();
+            mScreenshot = null;
         }
         if (DEBUG_SERVICE) {
             Log.d(TAG, "onDestroy");
         }
     }
 
+    static class RequestCallbackImpl implements RequestCallback {
+        private final Messenger mReplyTo;
+
+        RequestCallbackImpl(Messenger replyTo) {
+            mReplyTo = replyTo;
+        }
+
+        public void reportError() {
+            reportUri(mReplyTo, null);
+            sendComplete(mReplyTo);
+        }
+
+        @Override
+        public void onFinish() {
+            sendComplete(mReplyTo);
+        }
+    }
+
     /** Respond to incoming Message via Binder (Messenger) */
+    @MainThread
     private boolean handleMessage(Message msg) {
         final Messenger replyTo = msg.replyTo;
-        final Runnable onComplete = () -> sendComplete(replyTo);
         final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri);
+        RequestCallback requestCallback = new RequestCallbackImpl(replyTo);
 
         // If the storage for this user is locked, we have no place to store
         // the screenshot, so skip taking it instead of showing a misleading
@@ -146,14 +179,7 @@
             Log.w(TAG, "Skipping screenshot because storage is locked!");
             mNotificationsController.notifyScreenshotError(
                     R.string.screenshot_failed_to_save_user_locked_text);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "handleMessage: calling uriConsumer.accept(null)");
-            }
-            uriConsumer.accept(null);
-            if (DEBUG_CALLBACK) {
-                Log.d(TAG, "handleMessage: calling onComplete.run()");
-            }
-            onComplete.run();
+            requestCallback.reportError();
             return true;
         }
 
@@ -167,13 +193,13 @@
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN");
                 }
-                mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete);
+                mScreenshot.takeScreenshotFullscreen(uriConsumer, requestCallback);
                 break;
             case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
                 if (DEBUG_SERVICE) {
                     Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION");
                 }
-                mScreenshot.takeScreenshotPartial(uriConsumer, onComplete);
+                mScreenshot.takeScreenshotPartial(uriConsumer, requestCallback);
                 break;
             case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
                 if (DEBUG_SERVICE) {
@@ -186,8 +212,16 @@
                 int taskId = screenshotRequest.getTaskId();
                 int userId = screenshotRequest.getUserId();
                 ComponentName topComponent = screenshotRequest.getTopComponent();
-                mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
-                        taskId, userId, topComponent, uriConsumer, onComplete);
+
+                if (screenshot == null) {
+                    Log.e(TAG, "Got null bitmap from screenshot message");
+                    mNotificationsController.notifyScreenshotError(
+                            R.string.screenshot_failed_to_capture_text);
+                    requestCallback.reportError();
+                } else {
+                    mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets,
+                            taskId, userId, topComponent, uriConsumer, requestCallback);
+                }
                 break;
             default:
                 Log.w(TAG, "Invalid screenshot option: " + msg.what);
@@ -196,7 +230,7 @@
         return true;
     };
 
-    private void sendComplete(Messenger target) {
+    private static void sendComplete(Messenger target) {
         try {
             if (DEBUG_CALLBACK) {
                 Log.d(TAG, "sendComplete: " + target);
@@ -207,7 +241,7 @@
         }
     }
 
-    private void reportUri(Messenger target, Uri uri) {
+    private static void reportUri(Messenger target, Uri uri) {
         try {
             if (DEBUG_CALLBACK) {
                 Log.d(TAG, "reportUri: " + target + " -> " + uri);
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index c1161f1..d707bca0 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -17,14 +17,19 @@
 package com.android.systemui.sensorprivacy
 
 import android.app.AppOpsManager
+import android.app.KeyguardManager
+import android.app.KeyguardManager.KeyguardDismissCallback
 import android.content.DialogInterface
 import android.content.Intent.EXTRA_PACKAGE_NAME
 import android.content.pm.PackageManager
 import android.content.res.Resources
 import android.hardware.SensorPrivacyManager
-import android.hardware.SensorPrivacyManager.*
+import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
+import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA
+import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE
 import android.os.Bundle
 import android.text.Html
+import android.util.Log
 import com.android.internal.app.AlertActivity
 import com.android.systemui.R
 
@@ -35,18 +40,29 @@
  * <p>The dialog is started for the user the app is running for which might be a secondary users.
  */
 class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListener {
+
+    companion object {
+        private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
+    }
+
     private var sensor = -1
     private lateinit var sensorUsePackageName: String
 
     private lateinit var sensorPrivacyManager: SensorPrivacyManager
     private lateinit var appOpsManager: AppOpsManager
+    private lateinit var keyguardManager: KeyguardManager
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        setShowWhenLocked(true)
+
+        setFinishOnTouchOutside(false)
+
         setResult(RESULT_CANCELED)
         sensorPrivacyManager = getSystemService(SensorPrivacyManager::class.java)!!
         appOpsManager = getSystemService(AppOpsManager::class.java)!!
+        keyguardManager = getSystemService(KeyguardManager::class.java)!!
 
         sensorUsePackageName = intent.getStringExtra(EXTRA_PACKAGE_NAME) ?: return
         sensor = intent.getIntExtra(EXTRA_SENSOR, -1).also {
@@ -107,8 +123,23 @@
     override fun onClick(dialog: DialogInterface?, which: Int) {
         when (which) {
             BUTTON_POSITIVE -> {
-                sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
-                setResult(RESULT_OK)
+                if (keyguardManager.isDeviceLocked) {
+                    keyguardManager
+                            .requestDismissKeyguard(this, object : KeyguardDismissCallback() {
+                        override fun onDismissError() {
+                            Log.e(LOG_TAG, "Cannot dismiss keyguard")
+                        }
+
+                        override fun onDismissSucceeded() {
+                            sensorPrivacyManager
+                                    .setIndividualSensorPrivacyForProfileGroup(sensor, false)
+                            setResult(RESULT_OK)
+                        }
+                    })
+                } else {
+                    sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false)
+                    setResult(RESULT_OK)
+                }
             }
         }
 
@@ -120,4 +151,8 @@
 
         sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false)
     }
+
+    override fun onBackPressed() {
+        // do not allow backing out
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java
index 11ee94c..3c7d78c 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessControllerSettings.java
@@ -17,7 +17,7 @@
 package com.android.systemui.settings.brightness;
 
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.statusbar.FeatureFlags;
 
 import javax.inject.Inject;
 
@@ -28,24 +28,22 @@
 public class BrightnessControllerSettings {
 
     private static final String THICK_BRIGHTNESS_SLIDER = "sysui_thick_brightness";
-    private final boolean mUseThickSlider;
-    private final boolean mUseMirrorOnThickSlider;
+    private final FeatureFlags mFeatureFlags;
 
     @Inject
-    public BrightnessControllerSettings(SecureSettings settings) {
-        mUseThickSlider = settings.getInt(THICK_BRIGHTNESS_SLIDER, 0) != 0;
-        mUseMirrorOnThickSlider = settings.getInt(THICK_BRIGHTNESS_SLIDER, 0) != 2;
+    public BrightnessControllerSettings(FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
     }
 
     // Changing this setting between zero and non-zero may crash systemui down the line. Better to
     // restart systemui after changing it.
     /** */
     boolean useThickSlider() {
-        return mUseThickSlider;
+        return mFeatureFlags.useNewBrightnessSlider();
     }
 
     /** */
     boolean useMirrorOnThickSlider() {
-        return mUseMirrorOnThickSlider;
+        return !useThickSlider();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
index 53ff1df..a6aec3b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSlider.java
@@ -17,7 +17,6 @@
 package com.android.systemui.settings.brightness;
 
 import android.content.Context;
-import android.graphics.drawable.ClipDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.view.LayoutInflater;
@@ -33,6 +32,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.util.RoundedCornerProgressDrawable;
 import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
@@ -292,8 +292,8 @@
             if (b.getProgressDrawable() instanceof LayerDrawable) {
                 Drawable progress = ((LayerDrawable) b.getProgressDrawable())
                         .findDrawableByLayerId(com.android.internal.R.id.progress);
-                if (progress instanceof ClipDrawable) {
-                    Drawable inner = ((ClipDrawable) progress).getDrawable();
+                if (progress instanceof RoundedCornerProgressDrawable) {
+                    Drawable inner = ((RoundedCornerProgressDrawable) progress).getDrawable();
                     if (inner instanceof LayerDrawable) {
                         return (LayerDrawable) inner;
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 30b3158..e7b60c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -61,4 +61,13 @@
     public boolean isKeyguardLayoutEnabled() {
         return mFlagReader.isEnabled(R.bool.flag_keyguard_layout);
     }
+
+    /** b/178485354 */
+    public boolean useNewBrightnessSlider() {
+        return mFlagReader.isEnabled(R.bool.flag_brightness_slider);
+    }
+
+    public boolean isPeopleTileEnabled() {
+        return mFlagReader.isEnabled(R.bool.flag_conversations);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index fdb793e..924eb26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -131,6 +131,7 @@
      */
     public void removeRemoteInput(NotificationEntry entry, Object token) {
         Objects.requireNonNull(entry);
+        if (entry.mRemoteEditImeVisible) return;
 
         pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index 7f30009..6023b7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -26,25 +26,41 @@
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.AttributeSet;
 import android.view.View;
 
 import androidx.core.graphics.ColorUtils;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.drawable.ScrimDrawable;
 
+import java.util.concurrent.Executor;
+
+
 /**
- * A view which can draw a scrim
+ * A view which can draw a scrim.  This view maybe be used in multiple windows running on different
+ * threads, but is controlled by {@link com.android.systemui.statusbar.phone.ScrimController} so we
+ * need to be careful to synchronize when necessary.
  */
 public class ScrimView extends View {
+    private final Object mColorLock = new Object();
+
+    @GuardedBy("mColorLock")
     private final ColorExtractor.GradientColors mColors;
+    // Used only for returning the colors
+    private final ColorExtractor.GradientColors mTmpColors = new ColorExtractor.GradientColors();
     private float mViewAlpha = 1.0f;
     private Drawable mDrawable;
     private PorterDuffColorFilter mColorFilter;
     private int mTintColor;
     private Runnable mChangeRunnable;
+    private Executor mChangeRunnableExecutor;
+    private Executor mExecutor;
+    private Looper mExecutorLooper;
 
     public ScrimView(Context context) {
         this(context, null);
@@ -64,7 +80,16 @@
         mDrawable = new ScrimDrawable();
         mDrawable.setCallback(this);
         mColors = new ColorExtractor.GradientColors();
-        updateColorWithTint(false);
+        mExecutorLooper = Looper.myLooper();
+        mExecutor = Runnable::run;
+        executeOnExecutor(() -> {
+            updateColorWithTint(false);
+        });
+    }
+
+    public void setExecutor(Executor executor, Looper looper) {
+        mExecutor = executor;
+        mExecutorLooper = looper;
     }
 
     @Override
@@ -75,11 +100,13 @@
     }
 
     public void setDrawable(Drawable drawable) {
-        mDrawable = drawable;
-        mDrawable.setCallback(this);
-        mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom());
-        mDrawable.setAlpha((int) (255 * mViewAlpha));
-        invalidate();
+        executeOnExecutor(() -> {
+            mDrawable = drawable;
+            mDrawable.setCallback(this);
+            mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom());
+            mDrawable.setAlpha((int) (255 * mViewAlpha));
+            invalidate();
+        });
     }
 
     @Override
@@ -99,6 +126,13 @@
         }
     }
 
+    @Override
+    public void setClickable(boolean clickable) {
+        executeOnExecutor(() -> {
+            super.setClickable(clickable);
+        });
+    }
+
     public void setColors(@NonNull ColorExtractor.GradientColors colors) {
         setColors(colors, false);
     }
@@ -107,11 +141,15 @@
         if (colors == null) {
             throw new IllegalArgumentException("Colors cannot be null");
         }
-        if (mColors.equals(colors)) {
-            return;
-        }
-        mColors.set(colors);
-        updateColorWithTint(animated);
+        executeOnExecutor(() -> {
+            synchronized(mColorLock) {
+                if (mColors.equals(colors)) {
+                    return;
+                }
+                mColors.set(colors);
+            }
+            updateColorWithTint(animated);
+        });
     }
 
     @VisibleForTesting
@@ -120,7 +158,10 @@
     }
 
     public ColorExtractor.GradientColors getColors() {
-        return mColors;
+        synchronized(mColorLock) {
+            mTmpColors.set(mColors);
+        }
+        return mTmpColors;
     }
 
     public void setTint(int color) {
@@ -128,11 +169,13 @@
     }
 
     public void setTint(int color, boolean animated) {
-        if (mTintColor == color) {
-            return;
-        }
-        mTintColor = color;
-        updateColorWithTint(animated);
+        executeOnExecutor(() -> {
+            if (mTintColor == color) {
+                return;
+            }
+            mTintColor = color;
+            updateColorWithTint(animated);
+        });
     }
 
     private void updateColorWithTint(boolean animated) {
@@ -160,7 +203,7 @@
         }
 
         if (mChangeRunnable != null) {
-            mChangeRunnable.run();
+            mChangeRunnableExecutor.execute(mChangeRunnable);
         }
     }
 
@@ -184,26 +227,37 @@
         if (isNaN(alpha)) {
             throw new IllegalArgumentException("alpha cannot be NaN: " + alpha);
         }
-        if (alpha != mViewAlpha) {
-            mViewAlpha = alpha;
+        executeOnExecutor(() -> {
+            if (alpha != mViewAlpha) {
+                mViewAlpha = alpha;
 
-            mDrawable.setAlpha((int) (255 * alpha));
-            if (mChangeRunnable != null) {
-                mChangeRunnable.run();
+                mDrawable.setAlpha((int) (255 * alpha));
+                if (mChangeRunnable != null) {
+                    mChangeRunnableExecutor.execute(mChangeRunnable);
+                }
             }
-        }
+        });
     }
 
     public float getViewAlpha() {
         return mViewAlpha;
     }
 
-    public void setChangeRunnable(Runnable changeRunnable) {
+    public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) {
         mChangeRunnable = changeRunnable;
+        mChangeRunnableExecutor = changeRunnableExecutor;
     }
 
     @Override
     protected boolean canReceivePointerEvents() {
         return false;
     }
+
+    private void executeOnExecutor(Runnable r) {
+        if (mExecutor == null || Looper.myLooper() == mExecutorLooper) {
+            r.run();
+        } else {
+            mExecutor.execute(r);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 239addd..d562726 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -27,6 +27,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.FeatureFlagUtils;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -64,7 +65,6 @@
         LayoutInflater inflater = LayoutInflater.from(context);
         StatusBarMobileView v = (StatusBarMobileView)
                 inflater.inflate(R.layout.status_bar_mobile_signal_group, null);
-
         v.setSlot(slot);
         v.init();
         v.setVisibleState(STATE_ICON);
@@ -104,7 +104,11 @@
         mMobileGroup = findViewById(R.id.mobile_group);
         mMobile = findViewById(R.id.mobile_signal);
         mMobileType = findViewById(R.id.mobile_type);
-        mMobileRoaming = findViewById(R.id.mobile_roaming);
+        if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) {
+            mMobileRoaming = findViewById(R.id.mobile_roaming_large);
+        } else {
+            mMobileRoaming = findViewById(R.id.mobile_roaming);
+        }
         mMobileRoamingSpace = findViewById(R.id.mobile_roaming_space);
         mIn = findViewById(R.id.mobile_in);
         mOut = findViewById(R.id.mobile_out);
@@ -160,7 +164,7 @@
         } else {
             mMobileType.setVisibility(View.GONE);
         }
-
+        mMobile.setVisibility(mState.showTriangle ? View.VISIBLE : View.GONE);
         mMobileRoaming.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
         mMobileRoamingSpace.setVisibility(mState.roaming ? View.VISIBLE : View.GONE);
         mIn.setVisibility(mState.activityIn ? View.VISIBLE : View.GONE);
@@ -191,6 +195,7 @@
             }
         }
 
+        mMobile.setVisibility(state.showTriangle ? View.VISIBLE : View.GONE);
         mMobileRoaming.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
         mMobileRoamingSpace.setVisibility(state.roaming ? View.VISIBLE : View.GONE);
         mIn.setVisibility(state.activityIn ? View.VISIBLE : View.GONE);
@@ -200,7 +205,8 @@
 
         needsLayout |= state.roaming != mState.roaming
                 || state.activityIn != mState.activityIn
-                || state.activityOut != mState.activityOut;
+                || state.activityOut != mState.activityOut
+                || state.showTriangle != mState.showTriangle;
 
         mState = state;
         return needsLayout;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
index 1ec043c..e4ae560 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SuperStatusBarViewFactory.java
@@ -23,8 +23,6 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
-import com.android.systemui.statusbar.phone.LockIcon;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
@@ -41,7 +39,6 @@
 
     private final Context mContext;
     private final InjectionInflationController mInjectionInflationController;
-    private final LockscreenLockIconController mLockIconController;
     private final NotificationShelfComponent.Builder mNotificationShelfComponentBuilder;
 
     private NotificationShadeWindowView mNotificationShadeWindowView;
@@ -51,11 +48,9 @@
     @Inject
     public SuperStatusBarViewFactory(Context context,
             InjectionInflationController injectionInflationController,
-            NotificationShelfComponent.Builder notificationShelfComponentBuilder,
-            LockscreenLockIconController lockIconController) {
+            NotificationShelfComponent.Builder notificationShelfComponentBuilder) {
         mContext = context;
         mInjectionInflationController = injectionInflationController;
-        mLockIconController = lockIconController;
         mNotificationShelfComponentBuilder = notificationShelfComponentBuilder;
     }
 
@@ -77,10 +72,6 @@
             throw new IllegalStateException(
                     "R.layout.super_notification_shade could not be properly inflated");
         }
-        LockIcon lockIcon = mNotificationShadeWindowView.findViewById(R.id.lock_icon);
-        if (lockIcon != null) {
-            mLockIconController.attach(lockIcon);
-        }
 
         return mNotificationShadeWindowView;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 88b9c6c..d08f973 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -33,6 +33,7 @@
 import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.View;
+import android.view.WindowManager;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.policy.ScreenDecorationsUtils;
@@ -159,8 +160,10 @@
         }
 
         @Override
-        public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets,
+        public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                RemoteAnimationTarget[] remoteAnimationTargets,
                 RemoteAnimationTarget[] remoteAnimationWallpaperTargets,
+                RemoteAnimationTarget[] remoteAnimationNonAppTargets,
                 IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
                     throws RemoteException {
             mMainExecutor.execute(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index bafa4a25..8a22b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -179,6 +179,8 @@
     private boolean mShelfIconVisible;
     private boolean mIsAlerting;
 
+    public boolean mRemoteEditImeVisible;
+
     /**
      * @param sbn the StatusBarNotification from system server
      * @param ranking also from system server
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 4fde118..db49e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -21,7 +21,6 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -50,7 +49,6 @@
     private final ShadeListBuilder mListBuilder;
     private final NotifCoordinators mNotifPluggableCoordinators;
     private final NotifInflaterImpl mNotifInflater;
-    private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
     private final DumpManager mDumpManager;
     private final ShadeViewManagerFactory mShadeViewManagerFactory;
     private final FeatureFlags mFeatureFlags;
@@ -64,7 +62,6 @@
             ShadeListBuilder listBuilder,
             NotifCoordinators notifCoordinators,
             NotifInflaterImpl notifInflater,
-            PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             DumpManager dumpManager,
             ShadeViewManagerFactory shadeViewManagerFactory,
             FeatureFlags featureFlags) {
@@ -75,7 +72,6 @@
         mNotifPluggableCoordinators = notifCoordinators;
         mDumpManager = dumpManager;
         mNotifInflater = notifInflater;
-        mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
         mShadeViewManagerFactory = shadeViewManagerFactory;
         mFeatureFlags = featureFlags;
     }
@@ -103,7 +99,6 @@
         mListBuilder.attach(mNotifCollection);
         mNotifCollection.attach(mGroupCoalescer);
         mGroupCoalescer.attach(notificationService);
-        mPeopleSpaceWidgetManager.attach(notificationService);
 
         Log.d(TAG, "Notif pipeline initialized");
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 93156d8..0957f78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -69,6 +69,8 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
 import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.leak.LeakDetector;
@@ -89,6 +91,10 @@
  */
 @Module(includes = { NotificationSectionHeadersModule.class })
 public interface NotificationsModule {
+    @Binds
+    StackScrollAlgorithm.SectionProvider bindSectionProvider(
+            NotificationSectionsManager impl);
+
     /** Provides an instance of {@link NotificationEntryManager} */
     @SysUISingleton
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 54ce4ed..0ad6507 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -18,6 +18,7 @@
 
 import android.service.notification.StatusBarNotification
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationListener
@@ -73,7 +74,8 @@
     private val headsUpController: HeadsUpController,
     private val headsUpViewBinder: HeadsUpViewBinder,
     private val clickerBuilder: NotificationClicker.Builder,
-    private val animatedImageNotificationManager: AnimatedImageNotificationManager
+    private val animatedImageNotificationManager: AnimatedImageNotificationManager,
+    private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager
 ) : NotificationsController {
 
     override fun initialize(
@@ -126,6 +128,10 @@
 
             entryManager.attach(notificationListener)
         }
+
+        if (featureFlags.isPeopleTileEnabled) {
+            peopleSpaceWidgetManager.attach(notificationListener)
+        }
     }
 
     override fun dump(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 596aea0..2b12119 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -302,8 +302,12 @@
         } else {
             mMenuContainer = new FrameLayout(mContext);
         }
-        final boolean newFlowHideShelf = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* on by default */) == 1;
+        // The setting can win (which is needed for tests) but if not set, then use the flag
+        final int showDismissSetting =  Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SHOW_NEW_NOTIF_DISMISS, -1);
+        final boolean newFlowHideShelf = showDismissSetting == -1
+                ? mContext.getResources().getBoolean(R.bool.flag_notif_updates)
+                : showDismissSetting == 1;
         if (newFlowHideShelf) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index a12179c..756fe6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -22,6 +22,7 @@
 import android.util.MathUtils;
 
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -30,9 +31,12 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider;
 
+import javax.inject.Inject;
+
 /**
  * A global state to track all input states for the algorithm.
  */
+@SysUISingleton
 public class AmbientState {
 
     private static final float MAX_PULSE_HEIGHT = 100000f;
@@ -83,6 +87,7 @@
     /** Tracks the state from AlertingNotificationManager#hasNotifications() */
     private boolean mHasAlertEntries;
 
+    @Inject
     public AmbientState(
             Context context,
             @NonNull SectionProvider sectionProvider) {
@@ -98,7 +103,7 @@
         mBaseZHeight = getBaseHeight(mZDistanceBetweenElements);
     }
 
-    void setIsShadeOpening(boolean isOpening) {
+    public void setIsShadeOpening(boolean isOpening) {
         mIsShadeOpening = isOpening;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 079cf77..45f5b31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import android.content.res.Resources;
 import android.util.MathUtils;
 
+import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -45,11 +47,6 @@
     private ExpandableNotificationRow mTrackedHeadsUp;
     private float mAppearFraction;
 
-    // Radius for notification corners WITH adjacent notifications
-    // as percent of radius WITHOUT adjacent notifications.
-    // TODO(b/175710408) pull from dimens and hide from beta builds.
-    static final float SMALL_CORNER_RADIUS = 4f/28;
-
     @Inject
     NotificationRoundnessManager(
             KeyguardBypassController keyguardBypassController,
@@ -128,7 +125,9 @@
         if (view.showingPulsing() && !mBypassController.getBypassEnabled()) {
             return 1.0f;
         }
-        return SMALL_CORNER_RADIUS;
+        final Resources resources = view.getResources();
+        return resources.getDimension(R.dimen.notification_corner_radius_small)
+                / resources.getDimension(R.dimen.notification_corner_radius);
     }
 
     public void setExpanded(float expandedHeight, float appearFraction) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f07d874..2c7c5cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -41,8 +41,6 @@
 import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.PointF;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.UserHandle;
@@ -493,7 +491,8 @@
             NotificationSectionsManager notificationSectionsManager,
             GroupMembershipManager groupMembershipManager,
             GroupExpansionManager groupExpansionManager,
-            SysuiStatusBarStateController statusbarStateController
+            SysuiStatusBarStateController statusbarStateController,
+            AmbientState ambientState
     ) {
         super(context, attrs, 0, 0);
         Resources res = getResources();
@@ -502,8 +501,9 @@
         mSectionsManager.initialize(this, LayoutInflater.from(context));
         mSections = mSectionsManager.createSectionsForBuckets();
 
-        mAmbientState = new AmbientState(context, mSectionsManager);
-        mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground).getDefaultColor();
+        mAmbientState = ambientState;
+        mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating)
+                .getDefaultColor();
         int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
         int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
         mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
@@ -550,10 +550,6 @@
         }
     }
 
-    void setIsShadeOpening(boolean isOpening) {
-        mAmbientState.setIsShadeOpening(isOpening);
-    }
-
     void setSectionPadding(float margin) {
         mAmbientState.setSectionPadding(margin);
         requestChildrenUpdate();
@@ -623,7 +619,8 @@
     }
 
     void updateBgColor() {
-        mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground).getDefaultColor();
+        mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating)
+                .getDefaultColor();
         updateBackgroundDimming();
         mShelf.onUiModeChanged();
     }
@@ -2282,7 +2279,8 @@
             if (child.getVisibility() != View.GONE
                     && !(child instanceof StackScrollerDecorView)
                     && child != mShelf
-                    && mSwipeHelper.getSwipedView() != child) {
+                    && (mSwipeHelper.getSwipedView() != child
+                        || !child.getResources().getBoolean(R.bool.flag_notif_updates))) {
                 children.add(child);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 879dad8..a516742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -45,7 +45,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
-import android.widget.FrameLayout;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
@@ -271,10 +270,6 @@
         }
     };
 
-    public void setIsShadeOpening(boolean isOpening) {
-        mView.setIsShadeOpening(isOpening);
-    }
-
     public void setSectionPadding(float padding) {
         mView.setSectionPadding(padding);
     }
@@ -851,11 +846,14 @@
         return mView.getChildAtRawPosition(x, y);
     }
 
-    public FrameLayout.LayoutParams getLayoutParams() {
-        return (FrameLayout.LayoutParams) mView.getLayoutParams();
+    public ViewGroup.LayoutParams getLayoutParams() {
+        return mView.getLayoutParams();
     }
 
-    public void setLayoutParams(FrameLayout.LayoutParams lp) {
+    /**
+     * Updates layout parameters on the root view
+     */
+    public void setLayoutParams(ViewGroup.LayoutParams lp) {
         mView.setLayoutParams(lp);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e6efba7..5d2203b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -286,8 +286,7 @@
         float currentYPosition = -algorithmState.scrollY;
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
-            currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition,
-                    false /* reverse */);
+            currentYPosition = updateChild(i, algorithmState, ambientState, currentYPosition);
         }
     }
 
@@ -301,10 +300,6 @@
      * @param currentYPosition The Y position of the current pass of the algorithm.  For a forward
      *                         pass, this should be the top of the child; for a reverse pass, the
      *                         bottom of the child.
-     * @param reverse          Whether we're laying out children in the reverse direction (Y
-     *                         positions
-     *                         decreasing) instead of the forward direction (Y positions
-     *                         increasing).
      * @return The Y position after laying out the child.  This will be the {@code currentYPosition}
      * for the next call to this method, after adjusting for any gaps between children.
      */
@@ -312,8 +307,7 @@
             int i,
             StackScrollAlgorithmState algorithmState,
             AmbientState ambientState,
-            float currentYPosition,
-            boolean reverse) {
+            float currentYPosition) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
         final boolean applyGapHeight =
@@ -323,20 +317,12 @@
         ExpandableViewState childViewState = child.getViewState();
         childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
 
-        if (applyGapHeight && !reverse) {
+        if (applyGapHeight) {
             currentYPosition += mGapHeight;
         }
-
         int childHeight = getMaxAllowedChildHeight(child);
-        if (reverse) {
-            childViewState.yTranslation = currentYPosition
-                    - (childHeight + mPaddingBetweenElements);
-            if (currentYPosition <= 0) {
-                childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
-            }
-        } else {
-            childViewState.yTranslation = currentYPosition;
-        }
+        childViewState.yTranslation = currentYPosition;
+
         boolean isFooterView = child instanceof FooterView;
         boolean isEmptyShadeView = child instanceof EmptyShadeView;
 
@@ -362,16 +348,9 @@
             clampPositionToShelf(child, childViewState, ambientState);
         }
 
-        if (reverse) {
-            currentYPosition = childViewState.yTranslation;
-            if (applyGapHeight) {
-                currentYPosition -= mGapHeight;
-            }
-        } else {
-            currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
-            if (currentYPosition <= 0) {
-                childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
-            }
+        currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
+        if (currentYPosition <= 0) {
+            childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
         }
         if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
             Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index e1eaf3c..f289b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -71,7 +71,7 @@
             "persist.sysui.wake_performs_auth", true);
     private boolean mDozingRequested;
     private boolean mPulsing;
-    private WakefulnessLifecycle mWakefulnessLifecycle;
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
@@ -86,9 +86,8 @@
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
     private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
-    private final LockscreenLockIconController mLockscreenLockIconController;
     private final AuthController mAuthController;
-    private NotificationIconAreaController mNotificationIconAreaController;
+    private final NotificationIconAreaController mNotificationIconAreaController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private NotificationPanelViewController mNotificationPanel;
     private View mAmbientIndicationContainer;
@@ -109,7 +108,6 @@
             PulseExpansionHandler pulseExpansionHandler,
             NotificationShadeWindowController notificationShadeWindowController,
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
-            LockscreenLockIconController lockscreenLockIconController,
             AuthController authController,
             NotificationIconAreaController notificationIconAreaController) {
         super();
@@ -129,7 +127,6 @@
         mPulseExpansionHandler = pulseExpansionHandler;
         mNotificationShadeWindowController = notificationShadeWindowController;
         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
-        mLockscreenLockIconController = lockscreenLockIconController;
         mAuthController = authController;
         mNotificationIconAreaController = notificationIconAreaController;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 0a366c9..dd1419f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -48,6 +48,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.MediaStore;
+import android.provider.Settings;
 import android.service.media.CameraPrewarmService;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
@@ -60,6 +61,7 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -71,6 +73,10 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.controls.dagger.ControlsComponent;
+import com.android.systemui.controls.ui.ControlsDialog;
+import com.android.systemui.controls.ui.ControlsUiController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.IntentButtonProvider;
 import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
@@ -117,11 +123,13 @@
     private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
     private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
 
+    // TODO(b/179494051): May no longer be needed
     private final boolean mShowLeftAffordance;
     private final boolean mShowCameraAffordance;
 
     private KeyguardAffordanceView mRightAffordanceView;
     private KeyguardAffordanceView mLeftAffordanceView;
+    private ImageView mAltLeftButton;
     private ViewGroup mIndicationArea;
     private TextView mEnterpriseDisclosure;
     private TextView mIndicationText;
@@ -171,6 +179,11 @@
     private int mBurnInYOffset;
     private ActivityIntentHelper mActivityIntentHelper;
 
+    private ControlsDialog mControlsDialog;
+    private ControlsComponent mControlsComponent;
+    private int mLockScreenMode;
+    private BroadcastDispatcher mBroadcastDispatcher;
+
     public KeyguardBottomAreaView(Context context) {
         this(context, null);
     }
@@ -236,6 +249,7 @@
         mOverlayContainer = findViewById(R.id.overlay_container);
         mRightAffordanceView = findViewById(R.id.camera_button);
         mLeftAffordanceView = findViewById(R.id.left_button);
+        mAltLeftButton = findViewById(R.id.alt_left_button);
         mIndicationArea = findViewById(R.id.keyguard_indication_area);
         mEnterpriseDisclosure = findViewById(
                 R.id.keyguard_indication_enterprise_disclosure);
@@ -334,6 +348,11 @@
         lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
         mLeftAffordanceView.setLayoutParams(lp);
         updateLeftAffordanceIcon();
+
+        lp = mAltLeftButton.getLayoutParams();
+        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
+        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
+        mAltLeftButton.setLayoutParams(lp);
     }
 
     private void updateRightAffordanceIcon() {
@@ -392,10 +411,17 @@
     }
 
     private void updateLeftAffordanceIcon() {
+        if (mDozing) {
+            mAltLeftButton.setVisibility(GONE);
+        } else if (mAltLeftButton.getDrawable() != null) {
+            mAltLeftButton.setVisibility(VISIBLE);
+        }
+
         if (!mShowLeftAffordance || mDozing) {
             mLeftAffordanceView.setVisibility(GONE);
             return;
         }
+
         IconState state = mLeftButton.getIcon();
         mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
         if (state.drawable != mLeftAffordanceView.getDrawable()
@@ -669,6 +695,9 @@
 
     public void startFinishDozeAnimation() {
         long delay = 0;
+        if (mAltLeftButton.getVisibility() == View.VISIBLE) {
+            startFinishDozeAnimationElement(mAltLeftButton, delay);
+        }
         if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
             startFinishDozeAnimationElement(mLeftAffordanceView, delay);
             delay += DOZE_ANIMATION_STAGGER_DELAY;
@@ -744,6 +773,10 @@
 
         if (dozing) {
             mOverlayContainer.setVisibility(INVISIBLE);
+            if (mControlsDialog != null) {
+                mControlsDialog.dismiss();
+                mControlsDialog = null;
+            }
         } else {
             mOverlayContainer.setVisibility(VISIBLE);
             if (animate) {
@@ -773,6 +806,7 @@
         mLeftAffordanceView.setAlpha(alpha);
         mRightAffordanceView.setAlpha(alpha);
         mIndicationArea.setAlpha(alpha);
+        mAltLeftButton.setAlpha(alpha);
     }
 
     private class DefaultLeftButton implements IntentButton {
@@ -844,4 +878,54 @@
         }
         return insets;
     }
+
+    /**
+     * Show or hide controls, depending on the lock screen mode and controls
+     * availability.
+     */
+    public void setupControls(ControlsComponent component, BroadcastDispatcher dispatcher) {
+        mControlsComponent = component;
+        mBroadcastDispatcher = dispatcher;
+        setupControls();
+    }
+
+    private void setupControls() {
+        if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+            mAltLeftButton.setVisibility(View.GONE);
+            mAltLeftButton.setOnClickListener(null);
+            return;
+        }
+
+        if (Settings.Global.getInt(mContext.getContentResolver(), "controls_lockscreen", 0) == 0) {
+            return;
+        }
+
+        if (mControlsComponent.getControlsListingController().isPresent()) {
+            mControlsComponent.getControlsListingController().get()
+                    .addCallback(list -> {
+                        if (!list.isEmpty()) {
+                            mAltLeftButton.setImageDrawable(list.get(0).loadIcon());
+                            mAltLeftButton.setVisibility(View.VISIBLE);
+                            mAltLeftButton.setOnClickListener((v) -> {
+                                ControlsUiController ui = mControlsComponent
+                                        .getControlsUiController().get();
+                                mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher)
+                                        .show(ui);
+                            });
+
+                        } else {
+                            mAltLeftButton.setVisibility(View.GONE);
+                            mAltLeftButton.setOnClickListener(null);
+                        }
+                    });
+        }
+    }
+
+    /**
+     * Optionally add controls when in the new lockscreen mode
+     */
+    public void onLockScreenModeChanged(int mode) {
+        mLockScreenMode = mode;
+        setupControls();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index eceac32..7011eee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -37,12 +37,10 @@
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -52,18 +50,20 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ViewController;
 
 import java.util.Optional;
 
 import javax.inject.Inject;
 
-/** Controls the {@link LockIcon} in the lockscreen. */
-@SysUISingleton
-public class LockscreenLockIconController {
+/** Controls the {@link LockIcon} on the lockscreen. */
+@StatusBarComponent.StatusBarScope
+public class LockscreenLockIconController extends ViewController<LockIcon> {
 
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -79,7 +79,6 @@
     private final KeyguardStateController mKeyguardStateController;
     private final Resources mResources;
     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
-    private final KeyguardSecurityModel mKeyguardSecurityModel;
     private boolean mKeyguardShowing;
     private boolean mKeyguardJustShown;
     private boolean mBlockUpdates;
@@ -92,50 +91,308 @@
     private boolean mBouncerShowingScrimmed;
     private boolean mFingerprintUnlock;
     private int mStatusBarState = StatusBarState.SHADE;
-    private LockIcon mLockIcon;
+    private int mLastState;
+    private boolean mDozing;
 
-    private View.OnAttachStateChangeListener mOnAttachStateChangeListener =
-            new View.OnAttachStateChangeListener() {
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            mStatusBarStateController.addCallback(mSBStateListener);
-            mConfigurationController.addCallback(mConfigurationListener);
-            mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
-            mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
-            mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+    @Inject
+    public LockscreenLockIconController(
+            @Nullable LockIcon view,
+            LockscreenGestureLogger lockscreenGestureLogger,
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            LockPatternUtils lockPatternUtils,
+            ShadeController shadeController,
+            AccessibilityController accessibilityController,
+            KeyguardIndicationController keyguardIndicationController,
+            StatusBarStateController statusBarStateController,
+            ConfigurationController configurationController,
+            NotificationWakeUpCoordinator notificationWakeUpCoordinator,
+            KeyguardBypassController keyguardBypassController,
+            @Nullable DockManager dockManager,
+            KeyguardStateController keyguardStateController,
+            @Main Resources resources,
+            HeadsUpManagerPhone headsUpManagerPhone) {
+        super(view);
+        mLockscreenGestureLogger = lockscreenGestureLogger;
+        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mLockPatternUtils = lockPatternUtils;
+        mShadeController = shadeController;
+        mAccessibilityController = accessibilityController;
+        mKeyguardIndicationController = keyguardIndicationController;
+        mStatusBarStateController = statusBarStateController;
+        mConfigurationController = configurationController;
+        mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
+        mKeyguardBypassController = keyguardBypassController;
+        mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
+        mKeyguardStateController = keyguardStateController;
+        mResources = resources;
+        mHeadsUpManagerPhone = headsUpManagerPhone;
 
-            mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
+        if (view != null) {
+            mKeyguardIndicationController.setLockIconController(this);
+        }
+    }
 
-            mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
-            mConfigurationListener.onThemeChanged();
+    @Override
+    protected void onInit() {
+        if (mView == null) {
+            return;
+        }
+        mView.setOnClickListener(this::handleClick);
+        mView.setOnLongClickListener(this::handleLongClick);
+        mView.setAccessibilityDelegate(mAccessibilityDelegate);
+    }
 
-            updateColor();
+    @Override
+    protected void onViewAttached() {
+        setStatusBarState(mStatusBarStateController.getState());
+        mDozing = mStatusBarStateController.isDozing();
+        mStatusBarStateController.addCallback(mSBStateListener);
+        mConfigurationController.addCallback(mConfigurationListener);
+        mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
+        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+
+        mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
+
+        mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
+        mConfigurationListener.onThemeChanged();
+
+        updateColor();
+        update();
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mStatusBarStateController.removeCallback(mSBStateListener);
+        mConfigurationController.removeCallback(mConfigurationListener);
+        mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
+        mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+        mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
+
+        mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
+    }
+
+    /**
+     * Called whenever the scrims become opaque, transparent or semi-transparent.
+     */
+    public void onScrimVisibilityChanged(Integer scrimsVisible) {
+        if (mWakeAndUnlockRunning
+                && scrimsVisible == ScrimController.TRANSPARENT) {
+            mWakeAndUnlockRunning = false;
             update();
         }
+    }
 
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            mStatusBarStateController.removeCallback(mSBStateListener);
-            mConfigurationController.removeCallback(mConfigurationListener);
-            mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
-            mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
-            mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
-
-            mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
+    /**
+     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
+     * icon on top of the black front scrim.
+     * We also want to halt padlock the animation when we're in face bypass mode or dismissing the
+     * keyguard with fingerprint.
+     * @param wakeAndUnlock are we wake and unlocking
+     * @param isUnlock are we currently unlocking
+     */
+    public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
+            BiometricSourceType type) {
+        if (wakeAndUnlock) {
+            mWakeAndUnlockRunning = true;
         }
-    };
+        mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
+        if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
+                && canBlockUpdates()) {
+            // We don't want the icon to change while we are unlocking
+            mBlockUpdates = true;
+        }
+        update();
+    }
+
+    /**
+     * When we're launching an affordance, like double pressing power to open camera.
+     */
+    public void onShowingLaunchAffordanceChanged(Boolean showing) {
+        mShowingLaunchAffordance = showing;
+        update();
+    }
+
+    /** Sets whether the bouncer is showing. */
+    public void setBouncerShowingScrimmed(boolean showing, boolean scrimmed) {
+        mBouncerShowingScrimmed = scrimmed;
+        update();
+    }
+
+    /**
+     * Sets how hidden the bouncer is, where 0f is fully visible and 1f is fully hidden
+     * See {@link KeyguardBouncer#EXPANSION_VISIBLE} and {@link KeyguardBouncer#EXPANSION_HIDDEN}.
+     */
+    public void setBouncerHideAmount(float hideAmount) {
+        mBouncerHiddenAmount = hideAmount;
+        updateColor();
+    }
+
+    private void updateColor() {
+        if (mView == null) {
+            return;
+        }
+        int iconColor = -1;
+        if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_VISIBLE) {
+            TypedArray typedArray = mView.getContext().getTheme().obtainStyledAttributes(
+                    null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
+            iconColor = typedArray.getColor(0, Color.WHITE);
+            typedArray.recycle();
+        } else if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_HIDDEN) {
+            iconColor = Utils.getColorAttrDefaultColor(
+                    mView.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
+        } else {
+            // bouncer is transitioning
+            TypedArray typedArray = mView.getContext().getTheme().obtainStyledAttributes(
+                    null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
+            int bouncerIconColor = typedArray.getColor(0, Color.WHITE);
+            typedArray.recycle();
+            int keyguardIconColor = Utils.getColorAttrDefaultColor(
+                    mView.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
+            iconColor = (int) new ArgbEvaluator().evaluate(
+                    mBouncerHiddenAmount, bouncerIconColor, keyguardIconColor);
+        }
+        mView.updateColor(iconColor);
+    }
+
+    /**
+     * Animate padlock opening when bouncer challenge is solved.
+     */
+    public void onBouncerPreHideAnimation() {
+        update();
+    }
+
+    /**
+     * If we're currently presenting an authentication error message.
+     */
+    public void setTransientBiometricsError(boolean transientBiometricsError) {
+        mTransientBiometricsError = transientBiometricsError;
+        update();
+    }
+
+    private boolean handleLongClick(View view) {
+        mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
+                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+        mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
+        mKeyguardIndicationController.showTransientIndication(
+                R.string.keyguard_indication_trust_disabled);
+        mKeyguardUpdateMonitor.onLockIconPressed();
+        mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+
+        return true;
+    }
+
+
+    private void handleClick(View view) {
+        if (!mAccessibilityController.isAccessibilityEnabled()) {
+            return;
+        }
+        mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
+    }
+
+    private void update() {
+        update(false /* force */);
+    }
+
+    private void update(boolean force) {
+        if (mView == null) {
+            return;
+        }
+        int state = getState();
+        boolean shouldUpdate = mLastState != state || force;
+        if (mBlockUpdates && canBlockUpdates()) {
+            shouldUpdate = false;
+        }
+        if (shouldUpdate && mView.getVisibility() != GONE) {
+            mView.update(state, mDozing, mKeyguardJustShown);
+        }
+        mLastState = state;
+        mKeyguardJustShown = false;
+        updateIconVisibility();
+        updateClickability();
+    }
+
+    private int getState() {
+        if ((mKeyguardStateController.canDismissLockScreen()
+                || !mKeyguardStateController.isShowing()
+                || mKeyguardStateController.isKeyguardGoingAway()
+                || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
+            return STATE_LOCK_OPEN;
+        } else if (mTransientBiometricsError) {
+            return STATE_BIOMETRICS_ERROR;
+        } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
+            return STATE_SCANNING_FACE;
+        } else {
+            return STATE_LOCKED;
+        }
+    }
+
+    private boolean canBlockUpdates() {
+        return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
+    }
+
+    /** Set the StatusBarState. */
+    private void setStatusBarState(int statusBarState) {
+        mStatusBarState = statusBarState;
+        updateIconVisibility();
+    }
+
+    /**
+     * Update the icon visibility
+     * @return true if the visibility changed
+     */
+    private boolean updateIconVisibility() {
+        if (mView == null) {
+            return false;
+        }
+        if (!mKeyguardUpdateMonitor.canShowLockIcon()) {
+            boolean changed = mView.getVisibility() != GONE;
+            mView.setVisibility(GONE);
+            return changed;
+        }
+
+        boolean onAodOrDocked = mDozing || mDocked;
+        boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
+        boolean fingerprintOrBypass = mFingerprintUnlock
+                || mKeyguardBypassController.getBypassEnabled();
+        if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
+            if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
+                    || mHeadsUpManagerPhone.hasPinnedHeadsUp()
+                    || mStatusBarState == StatusBarState.KEYGUARD
+                    || mStatusBarState == StatusBarState.SHADE)
+                    && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
+                invisible = true;
+            }
+        }
+        return mView.updateIconVisibility(!invisible);
+    }
+
+    private void updateClickability() {
+        if (mView == null) {
+            return;
+        }
+        boolean canLock = mKeyguardStateController.isMethodSecure()
+                && mKeyguardStateController.canDismissLockScreen();
+        boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
+        mView.setClickable(clickToUnlock);
+        mView.setLongClickable(canLock && !clickToUnlock);
+        mView.setFocusable(mAccessibilityController.isAccessibilityEnabled());
+    }
 
     private final StatusBarStateController.StateListener mSBStateListener =
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onDozingChanged(boolean isDozing) {
-                    setDozing(isDozing);
+                    if (mDozing != isDozing) {
+                        mDozing = isDozing;
+                        update();
+                    }
                 }
 
                 @Override
                 public void onDozeAmountChanged(float linear, float eased) {
-                    if (mLockIcon != null) {
-                        mLockIcon.setDozeAmount(eased);
+                    if (mView != null) {
+                        mView.setDozeAmount(eased);
                     }
                 }
 
@@ -160,29 +417,21 @@
 
         @Override
         public void onDensityOrFontScaleChanged() {
-            if (mLockIcon == null) {
-                return;
-            }
-
-            ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams();
+            ViewGroup.LayoutParams lp = mView.getLayoutParams();
             if (lp == null) {
                 return;
             }
-            lp.width = mLockIcon.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
-            lp.height = mLockIcon.getResources().getDimensionPixelSize(
+            lp.width = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
+            lp.height = mView.getResources().getDimensionPixelSize(
                     R.dimen.keyguard_lock_height);
-            mLockIcon.setLayoutParams(lp);
+            mView.setLayoutParams(lp);
             update(true /* force */);
         }
 
         @Override
         public void onLocaleListChanged() {
-            if (mLockIcon == null) {
-                return;
-            }
-
-            mLockIcon.setContentDescription(
-                    mLockIcon.getResources().getText(R.string.accessibility_unlock_button));
+            mView.setContentDescription(
+                    mView.getResources().getText(R.string.accessibility_unlock_button));
             update(true /* force */);
         }
 
@@ -299,9 +548,9 @@
                     if (fingerprintRunning && unlockingAllowed) {
                         AccessibilityNodeInfo.AccessibilityAction unlock =
                                 new AccessibilityNodeInfo.AccessibilityAction(
-                                AccessibilityNodeInfo.ACTION_CLICK,
-                                mResources.getString(
-                                        R.string.accessibility_unlock_without_fingerprint));
+                                        AccessibilityNodeInfo.ACTION_CLICK,
+                                        mResources.getString(
+                                                R.string.accessibility_unlock_without_fingerprint));
                         info.addAction(unlock);
                         info.setHintText(mResources.getString(
                                 R.string.accessibility_waiting_for_fingerprint));
@@ -313,277 +562,4 @@
                     }
                 }
             };
-    private int mLastState;
-
-    @Inject
-    public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger,
-            KeyguardUpdateMonitor keyguardUpdateMonitor,
-            LockPatternUtils lockPatternUtils,
-            ShadeController shadeController,
-            AccessibilityController accessibilityController,
-            KeyguardIndicationController keyguardIndicationController,
-            StatusBarStateController statusBarStateController,
-            ConfigurationController configurationController,
-            NotificationWakeUpCoordinator notificationWakeUpCoordinator,
-            KeyguardBypassController keyguardBypassController,
-            @Nullable DockManager dockManager,
-            KeyguardStateController keyguardStateController,
-            @Main Resources resources,
-            HeadsUpManagerPhone headsUpManagerPhone,
-            KeyguardSecurityModel keyguardSecurityModel) {
-        mLockscreenGestureLogger = lockscreenGestureLogger;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mLockPatternUtils = lockPatternUtils;
-        mShadeController = shadeController;
-        mAccessibilityController = accessibilityController;
-        mKeyguardIndicationController = keyguardIndicationController;
-        mStatusBarStateController = statusBarStateController;
-        mConfigurationController = configurationController;
-        mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
-        mKeyguardBypassController = keyguardBypassController;
-        mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
-        mKeyguardStateController = keyguardStateController;
-        mResources = resources;
-        mHeadsUpManagerPhone = headsUpManagerPhone;
-        mKeyguardSecurityModel = keyguardSecurityModel;
-
-        mKeyguardIndicationController.setLockIconController(this);
-    }
-
-    /**
-     * Associate the controller with a {@link LockIcon}
-     *
-     * TODO: change to an init method and inject the view.
-     */
-    public void attach(LockIcon lockIcon) {
-        mLockIcon = lockIcon;
-
-        mLockIcon.setOnClickListener(this::handleClick);
-        mLockIcon.setOnLongClickListener(this::handleLongClick);
-        mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
-
-        if (mLockIcon.isAttachedToWindow()) {
-            mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
-        }
-        mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
-        setStatusBarState(mStatusBarStateController.getState());
-    }
-
-    public LockIcon getView() {
-        return mLockIcon;
-    }
-
-    /**
-     * Called whenever the scrims become opaque, transparent or semi-transparent.
-     */
-    public void onScrimVisibilityChanged(Integer scrimsVisible) {
-        if (mWakeAndUnlockRunning
-                && scrimsVisible == ScrimController.TRANSPARENT) {
-            mWakeAndUnlockRunning = false;
-            update();
-        }
-    }
-
-    /**
-     * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
-     * icon on top of the black front scrim.
-     * We also want to halt padlock the animation when we're in face bypass mode or dismissing the
-     * keyguard with fingerprint.
-     * @param wakeAndUnlock are we wake and unlocking
-     * @param isUnlock are we currently unlocking
-     */
-    public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock,
-            BiometricSourceType type) {
-        if (wakeAndUnlock) {
-            mWakeAndUnlockRunning = true;
-        }
-        mFingerprintUnlock = type == BiometricSourceType.FINGERPRINT;
-        if (isUnlock && (mFingerprintUnlock || mKeyguardBypassController.getBypassEnabled())
-                && canBlockUpdates()) {
-            // We don't want the icon to change while we are unlocking
-            mBlockUpdates = true;
-        }
-        update();
-    }
-
-    /**
-     * When we're launching an affordance, like double pressing power to open camera.
-     */
-    public void onShowingLaunchAffordanceChanged(Boolean showing) {
-        mShowingLaunchAffordance = showing;
-        update();
-    }
-
-    /** Sets whether the bouncer is showing. */
-    public void setBouncerShowingScrimmed(boolean showing, boolean scrimmed) {
-        mBouncerShowingScrimmed = scrimmed;
-        update();
-    }
-
-    /**
-     * Sets how hidden the bouncer is, where 0f is fully visible and 1f is fully hidden
-     * See {@link KeyguardBouncer#EXPANSION_VISIBLE} and {@link KeyguardBouncer#EXPANSION_HIDDEN}.
-     */
-    public void setBouncerHideAmount(float hideAmount) {
-        mBouncerHiddenAmount = hideAmount;
-        updateColor();
-    }
-
-    private void updateColor() {
-        if (mLockIcon == null) {
-            return;
-        }
-
-        int iconColor = -1;
-        if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_VISIBLE) {
-            TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
-                    null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
-            iconColor = typedArray.getColor(0, Color.WHITE);
-            typedArray.recycle();
-        } else if (mBouncerHiddenAmount == KeyguardBouncer.EXPANSION_HIDDEN) {
-            iconColor = Utils.getColorAttrDefaultColor(
-                    mLockIcon.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
-        } else {
-            // bouncer is transitioning
-            TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
-                    null, new int[]{ android.R.attr.textColorPrimary }, 0, 0);
-            int bouncerIconColor = typedArray.getColor(0, Color.WHITE);
-            typedArray.recycle();
-            int keyguardIconColor = Utils.getColorAttrDefaultColor(
-                    mLockIcon.getContext(), com.android.systemui.R.attr.wallpaperTextColor);
-            iconColor = (int) new ArgbEvaluator().evaluate(
-                    mBouncerHiddenAmount, bouncerIconColor, keyguardIconColor);
-        }
-        mLockIcon.updateColor(iconColor);
-    }
-
-    /**
-     * Animate padlock opening when bouncer challenge is solved.
-     */
-    public void onBouncerPreHideAnimation() {
-        update();
-    }
-
-    /**
-     * If we're currently presenting an authentication error message.
-     */
-    public void setTransientBiometricsError(boolean transientBiometricsError) {
-        mTransientBiometricsError = transientBiometricsError;
-        update();
-    }
-
-    private boolean handleLongClick(View view) {
-        mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
-                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
-        mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
-        mKeyguardIndicationController.showTransientIndication(
-                R.string.keyguard_indication_trust_disabled);
-        mKeyguardUpdateMonitor.onLockIconPressed();
-        mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
-
-        return true;
-    }
-
-
-    private void handleClick(View view) {
-        if (!mAccessibilityController.isAccessibilityEnabled()) {
-            return;
-        }
-        mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
-    }
-
-    private void update() {
-        update(false /* force */);
-    }
-
-    private void update(boolean force) {
-        int state = getState();
-        boolean shouldUpdate = mLastState != state || force;
-        if (mBlockUpdates && canBlockUpdates()) {
-            shouldUpdate = false;
-        }
-        if (shouldUpdate && mLockIcon != null && mLockIcon.getVisibility() != GONE) {
-            mLockIcon.update(state,
-                    mStatusBarStateController.isDozing(), mKeyguardJustShown);
-        }
-        mLastState = state;
-        mKeyguardJustShown = false;
-        updateIconVisibility();
-        updateClickability();
-    }
-
-    private int getState() {
-        if ((mKeyguardStateController.canDismissLockScreen()
-                || !mKeyguardStateController.isShowing()
-                || mKeyguardStateController.isKeyguardGoingAway()
-                || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
-            return STATE_LOCK_OPEN;
-        } else if (mTransientBiometricsError) {
-            return STATE_BIOMETRICS_ERROR;
-        } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
-            return STATE_SCANNING_FACE;
-        } else {
-            return STATE_LOCKED;
-        }
-    }
-
-    private boolean canBlockUpdates() {
-        return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
-    }
-
-    private void setDozing(boolean isDozing) {
-        update();
-    }
-
-    /** Set the StatusBarState. */
-    private void setStatusBarState(int statusBarState) {
-        mStatusBarState = statusBarState;
-        updateIconVisibility();
-    }
-
-    /**
-     * Update the icon visibility
-     * @return true if the visibility changed
-     */
-    private boolean updateIconVisibility() {
-        if (mLockIcon == null) {
-            return false;
-        }
-
-        if (!mKeyguardUpdateMonitor.shouldShowLockIcon()) {
-            boolean changed = mLockIcon.getVisibility() != GONE;
-            mLockIcon.setVisibility(GONE);
-            return changed;
-        }
-
-        boolean onAodOrDocked = mStatusBarStateController.isDozing() || mDocked;
-        boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
-        boolean fingerprintOrBypass = mFingerprintUnlock
-                || mKeyguardBypassController.getBypassEnabled();
-        if (fingerprintOrBypass && !mBouncerShowingScrimmed) {
-            if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
-                    || mHeadsUpManagerPhone.hasPinnedHeadsUp()
-                    || mStatusBarState == StatusBarState.KEYGUARD
-                    || mStatusBarState == StatusBarState.SHADE)
-                    && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
-                invisible = true;
-            }
-        }
-        return mLockIcon.updateIconVisibility(!invisible);
-    }
-
-    private void updateClickability() {
-        if (mAccessibilityController == null) {
-            return;
-        }
-        boolean canLock = mKeyguardStateController.isMethodSecure()
-                && mKeyguardStateController.canDismissLockScreen();
-        boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
-        if (mLockIcon != null) {
-            mLockIcon.setClickable(clickToUnlock);
-            mLockIcon.setLongClickable(canLock && !clickToUnlock);
-            mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
-        }
-    }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index b24d0e7..e0ef3b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -80,8 +80,10 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
@@ -95,6 +97,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.GestureRecorder;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -120,6 +123,7 @@
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -227,6 +231,7 @@
                 public void onLockScreenModeChanged(int mode) {
                     mLockScreenMode = mode;
                     mClockPositionAlgorithm.onLockScreenModeChanged(mode);
+                    mKeyguardBottomArea.onLockScreenModeChanged(mode);
                 }
 
                 @Override
@@ -291,7 +296,10 @@
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
     private final QSDetailDisplayer mQSDetailDisplayer;
+    private final FeatureFlags mFeatureFlags;
     private final ScrimController mScrimController;
+    private final ControlsComponent mControlsComponent;
+
     // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
     // If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
     private final int mMaxKeyguardNotifications;
@@ -500,6 +508,7 @@
     private NotificationShelfController mNotificationShelfController;
 
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+    private BroadcastDispatcher mBroadcastDispatcher;
 
     private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
         @Override
@@ -552,10 +561,15 @@
             AuthController authController,
             QSDetailDisplayer qsDetailDisplayer,
             ScrimController scrimController,
-            MediaDataManager mediaDataManager) {
+            MediaDataManager mediaDataManager,
+            AmbientState ambientState,
+            FeatureFlags featureFlags,
+            ControlsComponent controlsComponent,
+            BroadcastDispatcher broadcastDispatcher) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
-                latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager);
+                latencyTracker, flingAnimationUtilsBuilder.get(), statusBarTouchableRegionManager,
+                ambientState);
         mView = view;
         mMetricsLogger = metricsLogger;
         mActivityManager = activityManager;
@@ -568,6 +582,7 @@
         mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mQSDetailDisplayer = qsDetailDisplayer;
+        mFeatureFlags = featureFlags;
         mView.setWillNotDraw(!DEBUG);
         mLayoutInflater = layoutInflater;
         mFalsingManager = falsingManager;
@@ -584,6 +599,7 @@
         mBiometricUnlockController = biometricUnlockController;
         mScrimController = scrimController;
         mMediaDataManager = mediaDataManager;
+        mControlsComponent = controlsComponent;
         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
             if (mQs != null) {
                 mQs.animateHeaderSlidingOut();
@@ -622,6 +638,7 @@
         mEntryManager = notificationEntryManager;
         mConversationNotificationManager = conversationNotificationManager;
         mAuthController = authController;
+        mBroadcastDispatcher = broadcastDispatcher;
 
         mView.setBackgroundColor(Color.TRANSPARENT);
         OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
@@ -753,21 +770,38 @@
 
     public void updateResources() {
         int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
-        int panelGravity = mResources.getInteger(R.integer.notification_panel_layout_gravity);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
-        if (lp.width != qsWidth || lp.gravity != panelGravity) {
+        ViewGroup.LayoutParams lp = mQsFrame.getLayoutParams();
+        if (lp.width != qsWidth) {
             lp.width = qsWidth;
-            lp.gravity = panelGravity;
             mQsFrame.setLayoutParams(lp);
         }
 
         int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
         lp = mNotificationStackScrollLayoutController.getLayoutParams();
-        if (lp.width != panelWidth || lp.gravity != panelGravity) {
+        if (lp.width != panelWidth) {
             lp.width = panelWidth;
-            lp.gravity = panelGravity;
             mNotificationStackScrollLayoutController.setLayoutParams(lp);
         }
+
+        if (shouldUseSplitNotificationShade()) {
+            // In order to change the constraints at runtime, all children of the Constraint Layout
+            // must have ids.
+            ensureAllViewsHaveIds(mNotificationContainerParent);
+        }
+    }
+
+    private boolean shouldUseSplitNotificationShade() {
+        return mFeatureFlags.isTwoColumnNotificationShadeEnabled()
+                && mResources.getBoolean(R.bool.config_use_split_notification_shade);
+    }
+
+    private static void ensureAllViewsHaveIds(ViewGroup parentView) {
+        for (int i = 0; i < parentView.getChildCount(); i++) {
+            View childView = parentView.getChildAt(i);
+            if (childView.getId() == View.NO_ID) {
+                childView.setId(View.generateViewId());
+            }
+        }
     }
 
     private void reInflateViews() {
@@ -813,6 +847,7 @@
         mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
         mKeyguardBottomArea.setStatusBar(mStatusBar);
         mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
+        mKeyguardBottomArea.setupControls(mControlsComponent, mBroadcastDispatcher);
     }
 
     private void updateMaxDisplayedNotifications(boolean recompute) {
@@ -910,7 +945,7 @@
                     clockPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
                     bypassEnabled, getUnlockedStackScrollerPadding(),
-                    mUpdateMonitor.shouldShowLockIcon(),
+                    mUpdateMonitor.canShowLockIcon(),
                     getQsExpansionFraction(),
                     mDisplayCutoutTopInset);
             mClockPositionAlgorithm.run(mClockPositionResult);
@@ -2411,11 +2446,6 @@
     }
 
     @Override
-    protected void setIsShadeOpening(boolean isOpening) {
-        mNotificationStackScrollLayoutController.setIsShadeOpening(isOpening);
-    }
-
-    @Override
     public void setSectionPadding(float padding) {
         if (padding == mSectionPadding) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
index 27c94d2..b367406 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
@@ -28,6 +28,7 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.DimenRes;
+import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.systemui.R;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -42,7 +43,7 @@
 /**
  * The container with notification stack scroller and quick settings inside.
  */
-public class NotificationsQuickSettingsContainer extends FrameLayout
+public class NotificationsQuickSettingsContainer extends ConstraintLayout
         implements OnInflateListener, FragmentListener,
         AboveShelfObserver.HasViewAboveShelfChangedListener {
 
@@ -68,11 +69,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mQsFrame = (FrameLayout) findViewById(R.id.qs_frame);
+        mQsFrame = findViewById(R.id.qs_frame);
         mStackScroller = findViewById(R.id.notification_stack_scroller);
         mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin;
         mKeyguardStatusBar = findViewById(R.id.keyguard_header);
-        ViewStub userSwitcher = (ViewStub) findViewById(R.id.keyguard_user_switcher);
+        ViewStub userSwitcher = findViewById(R.id.keyguard_user_switcher);
         userSwitcher.setOnInflateListener(this);
         mUserSwitcher = userSwitcher;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index bee0cb1..28cfe21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -53,7 +53,7 @@
         if (DEBUG) LOG("go state: %d -> %d", mState, state);
         mState = state;
         if (mPanel != null) {
-            mPanel.setIsShadeOpening(state == STATE_OPENING);
+            mPanel.getAmbientState().setIsShadeOpening(state == STATE_OPENING);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 3031b8a..55744f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -27,6 +27,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.SystemClock;
@@ -54,6 +55,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -145,6 +147,9 @@
     private float mInitialTouchX;
     private boolean mTouchDisabled;
 
+    // AmbientState will never be null since it provides an @Inject constructor for Dagger to call.
+    private AmbientState mAmbientState;
+
     /**
      * Whether or not the PanelView can be expanded or collapsed with a drag.
      */
@@ -223,13 +228,19 @@
         mJustPeeked = true;
     }
 
+    protected AmbientState getAmbientState() {
+        return mAmbientState;
+    }
+
     public PanelViewController(PanelView view,
             FalsingManager falsingManager, DozeLog dozeLog,
             KeyguardStateController keyguardStateController,
             SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper,
             LatencyTracker latencyTracker,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            AmbientState ambientState) {
+        mAmbientState = ambientState;
         mView = view;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
@@ -776,8 +787,6 @@
      */
     protected abstract boolean isTrackingBlocked();
 
-    protected abstract void setIsShadeOpening(boolean isShadeOpening);
-
     protected abstract void setSectionPadding(float padding);
 
     protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c0a5ffa..31a432e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.util.leak.RotationUtils;
 
+import java.util.List;
 import java.util.Objects;
 
 public class PhoneStatusBarView extends PanelBar {
@@ -75,6 +76,8 @@
     @Nullable
     private DisplayCutout mDisplayCutout;
     private int mStatusBarHeight;
+    @Nullable
+    private List<StatusBar.ExpansionChangedListener> mExpansionChangedListeners;
 
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -93,6 +96,11 @@
         mBar = bar;
     }
 
+    public void setExpansionChangedListeners(
+            @Nullable List<StatusBar.ExpansionChangedListener> listeners) {
+        mExpansionChangedListeners = listeners;
+    }
+
     public void setScrimController(ScrimController scrimController) {
         mScrimController = scrimController;
     }
@@ -277,6 +285,12 @@
         if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) {
             mBar.getNavigationBarView().onStatusBarPanelStateChanged();
         }
+
+        if (mExpansionChangedListeners != null) {
+            for (StatusBar.ExpansionChangedListener listener : mExpansionChangedListeners) {
+                listener.onExpansionChanged(frac, expanded);
+            }
+        }
     }
 
     private void updateScrimFraction() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 03d17e5..7b2330b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -47,6 +47,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.BlurUtils;
 import com.android.systemui.statusbar.FeatureFlags;
@@ -62,6 +63,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -147,6 +149,7 @@
     private final AlarmTimeout mTimeTicker;
     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
     private final Handler mHandler;
+    private final Executor mMainExecutor;
     private final BlurUtils mBlurUtils;
 
     private GradientColors mColors;
@@ -201,8 +204,7 @@
             DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager,
             BlurUtils blurUtils, ConfigurationController configurationController,
-            FeatureFlags featureFlags) {
-
+            FeatureFlags featureFlags, @Main Executor mainExecutor) {
         mScrimStateListener = lightBarController::setScrimState;
         mDefaultScrimAlpha = featureFlags.isShadeOpaque() ? BUSY_SCRIM_ALPHA : GAR_SCRIM_ALPHA;
         ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(featureFlags.isShadeOpaque()
@@ -214,6 +216,7 @@
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mHandler = handler;
+        mMainExecutor = mainExecutor;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
                 "hide_aod_wallpaper", mHandler);
         mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build();
@@ -259,7 +262,7 @@
         updateThemeColors();
 
         if (mScrimBehindChangeRunnable != null) {
-            mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable);
+            mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor);
             mScrimBehindChangeRunnable = null;
         }
 
@@ -1000,7 +1003,7 @@
         if (mScrimBehind == null) {
             mScrimBehindChangeRunnable = changeRunnable;
         } else {
-            mScrimBehind.setChangeRunnable(changeRunnable);
+            mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d4a2b41..7095afd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -26,6 +26,7 @@
 import static android.view.InsetsState.containsType;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
 
 import static androidx.lifecycle.Lifecycle.State.RESUMED;
 
@@ -337,6 +338,10 @@
         ONLY_CORE_APPS = onlyCoreApps;
     }
 
+    public interface ExpansionChangedListener {
+        void onExpansionChanged(float expansion, boolean expanded);
+    }
+
     /**
      * The {@link StatusBarState} of the status bar.
      */
@@ -367,7 +372,6 @@
     protected NotificationShadeWindowController mNotificationShadeWindowController;
     protected StatusBarWindowController mStatusBarWindowController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    private final LockscreenLockIconController mLockscreenLockIconController;
     @VisibleForTesting
     DozeServiceHost mDozeServiceHost;
     private boolean mWakeUpComingFromTouch;
@@ -414,6 +418,7 @@
     // expanded notifications
     // the sliding/resizing panel within the notification window
     protected NotificationPanelViewController mNotificationPanelViewController;
+    protected LockscreenLockIconController mLockscreenLockIconController;
 
     // settings
     private QSPanelController mQSPanelController;
@@ -438,6 +443,8 @@
     protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
     private final BrightnessSlider.Factory mBrightnessSliderFactory;
 
+    private final List<ExpansionChangedListener> mExpansionChangedListeners;
+
     // for disabling the status bar
     private int mDisabled1 = 0;
     private int mDisabled2 = 0;
@@ -724,7 +731,6 @@
             Lazy<AssistManager> assistManagerLazy,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
-            LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
             @Nullable KeyguardLiftController keyguardLiftController,
@@ -806,7 +812,6 @@
         mAssistManagerLazy = assistManagerLazy;
         mConfigurationController = configurationController;
         mNotificationShadeWindowController = notificationShadeWindowController;
-        mLockscreenLockIconController = lockscreenLockIconController;
         mDozeServiceHost = dozeServiceHost;
         mPowerManager = powerManager;
         mDozeParameters = dozeParameters;
@@ -839,10 +844,14 @@
         mNotificationIconAreaController = notificationIconAreaController;
         mBrightnessSliderFactory = brightnessSliderFactory;
 
+        mExpansionChangedListeners = new ArrayList<>();
+
         mBubbleExpandListener =
                 (isExpanding, key) -> {
-                    mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged");
-                    updateScrimController();
+                    mContext.getMainExecutor().execute(() -> {
+                        mNotificationsController.requestNotificationUpdate("onBubbleExpandChanged");
+                        updateScrimController();
+                    });
                 };
 
         mActivityIntentHelper = new ActivityIntentHelper(mContext);
@@ -1076,6 +1085,7 @@
                     mStatusBarView.setBar(this);
                     mStatusBarView.setPanel(mNotificationPanelViewController);
                     mStatusBarView.setScrimController(mScrimController);
+                    mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
 
                     statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
                     // CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
@@ -1170,9 +1180,7 @@
 
         mScrimController.setScrimVisibleListener(scrimsVisible -> {
             mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
-            if (mNotificationShadeWindowView != null) {
-                mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
-            }
+            mLockscreenLockIconController.onScrimVisibilityChanged(scrimsVisible);
         });
         mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
 
@@ -1205,9 +1213,6 @@
             createUserSwitcher();
         }
 
-        mNotificationPanelViewController.setLaunchAffordanceListener(
-                mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
-
         // Set up the quick settings tile panel
         final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
         if (container != null) {
@@ -1487,6 +1492,11 @@
         mStatusBarWindowController = statusBarComponent.getStatusBarWindowController();
         mPhoneStatusBarWindow = mSuperStatusBarViewFactory.getStatusBarWindowView();
         mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
+        mLockscreenLockIconController = statusBarComponent.getLockscreenLockIconController();
+        mLockscreenLockIconController.init();
+
+        mNotificationPanelViewController.setLaunchAffordanceListener(
+                mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
     }
 
     protected void startKeyguard() {
@@ -2331,11 +2341,11 @@
                 && mStatusBarWindowState != state) {
             mStatusBarWindowState = state;
             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Status bar " + windowStateToString(state));
-            if (!showing && mState == StatusBarState.SHADE) {
-                mStatusBarView.collapsePanel(false /* animate */, false /* delayed */,
-                        1.0f /* speedUpFactor */);
-            }
             if (mStatusBarView != null) {
+                if (!showing && mState == StatusBarState.SHADE) {
+                    mStatusBarView.collapsePanel(false /* animate */, false /* delayed */,
+                            1.0f /* speedUpFactor */);
+                }
                 mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
                 updateHideIconsForBouncer(false /* animate */);
             }
@@ -2428,6 +2438,8 @@
             return MODE_LIGHTS_OUT_TRANSPARENT;
         } else if ((appearance & APPEARANCE_OPAQUE_STATUS_BARS) != 0) {
             return MODE_OPAQUE;
+        } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS) != 0) {
+            return MODE_SEMI_TRANSPARENT;
         } else {
             return MODE_TRANSPARENT;
         }
@@ -4547,4 +4559,8 @@
     public void suppressAmbientDisplay(boolean suppressed) {
         mDozeServiceHost.setDozeSuppressed(suppressed);
     }
+
+    public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
+        mExpansionChangedListeners.add(listener);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index d11e864..f6165f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -221,7 +221,7 @@
             int qsType, boolean activityIn, boolean activityOut,
             CharSequence typeContentDescription,
             CharSequence typeContentDescriptionHtml, CharSequence description,
-            boolean isWide, int subId, boolean roaming) {
+            boolean isWide, int subId, boolean roaming, boolean showTriangle) {
         if (DEBUG) {
             Log.d(TAG, "setMobileDataIndicators: "
                     + "statusIcon = " + (statusIcon == null ? "" : statusIcon.toString()) + ","
@@ -235,7 +235,8 @@
                     + "description = " + description + ","
                     + "isWide = " + isWide + ","
                     + "subId = " + subId + ","
-                    + "roaming = " + roaming);
+                    + "roaming = " + roaming + ","
+                    + "showTriangle = " + showTriangle);
         }
         MobileIconState state = getState(subId);
         if (state == null) {
@@ -250,6 +251,7 @@
         state.typeId = statusType;
         state.contentDescription = statusIcon.contentDescription;
         state.typeContentDescription = typeContentDescription;
+        state.showTriangle = showTriangle;
         state.roaming = roaming;
         state.activityIn = activityIn && mActivityEnabled;
         state.activityOut = activityOut && mActivityEnabled;
@@ -314,11 +316,23 @@
 
         mIconController.removeAllIconsForSlot(mSlotMobile);
         mMobileStates.clear();
+        List<NoCallingIconState> noCallingStates = new ArrayList<NoCallingIconState>();
+        noCallingStates.addAll(mNoCallingStates);
         mNoCallingStates.clear();
         final int n = subs.size();
         for (int i = 0; i < n; i++) {
             mMobileStates.add(new MobileIconState(subs.get(i).getSubscriptionId()));
-            mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+            boolean isNewSub = true;
+            for (NoCallingIconState state : noCallingStates) {
+                if (state.subId == subs.get(i).getSubscriptionId()) {
+                    mNoCallingStates.add(state);
+                    isNewSub = false;
+                    break;
+                }
+            }
+            if (isNewSub) {
+                mNoCallingStates.add(new NoCallingIconState(subs.get(i).getSubscriptionId()));
+            }
         }
     }
 
@@ -551,6 +565,7 @@
         public int subId;
         public int strengthId;
         public int typeId;
+        public boolean showTriangle;
         public boolean roaming;
         public boolean needsLeadingPadding;
         public CharSequence typeContentDescription;
@@ -569,20 +584,21 @@
                 return false;
             }
             MobileIconState that = (MobileIconState) o;
-            return subId == that.subId &&
-                    strengthId == that.strengthId &&
-                    typeId == that.typeId &&
-                    roaming == that.roaming &&
-                    needsLeadingPadding == that.needsLeadingPadding &&
-                    Objects.equals(typeContentDescription, that.typeContentDescription);
+            return subId == that.subId
+                    && strengthId == that.strengthId
+                    && typeId == that.typeId
+                    && showTriangle == that.showTriangle
+                    && roaming == that.roaming
+                    && needsLeadingPadding == that.needsLeadingPadding
+                    && Objects.equals(typeContentDescription, that.typeContentDescription);
         }
 
         @Override
         public int hashCode() {
 
             return Objects
-                    .hash(super.hashCode(), subId, strengthId, typeId, roaming, needsLeadingPadding,
-                            typeContentDescription);
+                    .hash(super.hashCode(), subId, strengthId, typeId, showTriangle, roaming,
+                            needsLeadingPadding, typeContentDescription);
         }
 
         public MobileIconState copy() {
@@ -596,6 +612,7 @@
             other.subId = subId;
             other.strengthId = strengthId;
             other.typeId = typeId;
+            other.showTriangle = showTriangle;
             other.roaming = roaming;
             other.needsLeadingPadding = needsLeadingPadding;
             other.typeContentDescription = typeContentDescription;
@@ -613,8 +630,9 @@
         }
 
         @Override public String toString() {
-            return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId + ", roaming="
-                    + roaming + ", typeId=" + typeId + ", visible=" + visible + ")";
+            return "MobileIconState(subId=" + subId + ", strengthId=" + strengthId
+                    + ", showTriangle=" + showTriangle + ", roaming=" + roaming
+                    + ", typeId=" + typeId + ", visible=" + visible + ")";
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index 802da3e..ecd9613 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -18,6 +18,7 @@
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
+import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
@@ -73,4 +74,9 @@
     @StatusBarScope
     NotificationPanelViewController getNotificationPanelViewController();
 
+    /**
+     * Creates a LockscreenLockIconController.
+     */
+    @StatusBarScope
+    LockscreenLockIconController getLockscreenLockIconController();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 26e1959..9e9533d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -77,7 +77,6 @@
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightsOutNotifController;
-import com.android.systemui.statusbar.phone.LockscreenLockIconController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
@@ -167,7 +166,6 @@
             Lazy<AssistManager> assistManagerLazy,
             ConfigurationController configurationController,
             NotificationShadeWindowController notificationShadeWindowController,
-            LockscreenLockIconController lockscreenLockIconController,
             DozeParameters dozeParameters,
             ScrimController scrimController,
             @Nullable KeyguardLiftController keyguardLiftController,
@@ -248,7 +246,6 @@
                 assistManagerLazy,
                 configurationController,
                 notificationShadeWindowController,
-                lockscreenLockIconController,
                 dozeParameters,
                 scrimController,
                 keyguardLiftController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 37d8c9a..781abe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
+import android.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 
@@ -32,4 +36,12 @@
         return notificationShadeWindowView.getNotificationPanelView();
     }
 
+    /** */
+    @Provides
+    @StatusBarComponent.StatusBarScope
+    @Nullable
+    public static LockIcon getLockIcon(
+            NotificationShadeWindowView notificationShadeWindowView) {
+        return notificationShadeWindowView.findViewById(R.id.lock_icon);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index 5e88cd5..08a4492 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -124,12 +124,13 @@
             final int statusType, final int qsType, final boolean activityIn,
             final boolean activityOut, final CharSequence typeContentDescription,
             CharSequence typeContentDescriptionHtml, final CharSequence description,
-            final boolean isWide, final int subId, boolean roaming) {
+            final boolean isWide, final int subId, boolean roaming, boolean showTriangle) {
         post(() -> {
             for (SignalCallback signalCluster : mSignalCallbacks) {
                 signalCluster.setMobileDataIndicators(statusIcon, qsIcon, statusType, qsType,
                         activityIn, activityOut, typeContentDescription,
-                        typeContentDescriptionHtml, description, isWide, subId, roaming);
+                        typeContentDescriptionHtml, description, isWide, subId, roaming,
+                        showTriangle);
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index d097cfa..499d1e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -316,6 +316,7 @@
     @Override
     public void onDarkChanged(Rect area, float darkIntensity, int tint) {
         mNonAdaptedColor = DarkIconDispatcher.getTint(area, this, tint);
+        setTextColor(mNonAdaptedColor);
     }
 
     // Update text color based when shade scrim changes color.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
index 2eff04e..80b75a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EthernetSignalController.java
@@ -53,13 +53,21 @@
     public void notifyListeners(SignalCallback callback) {
         boolean ethernetVisible = mCurrentState.connected;
         String contentDescription = getTextIfExists(getContentDescription()).toString();
-
         // TODO: wire up data transfer using WifiSignalPoller.
         callback.setEthernetIndicators(new IconState(ethernetVisible, getCurrentIconId(),
                 contentDescription));
     }
 
     @Override
+    public int getContentDescription() {
+        if (mCurrentState.connected) {
+            return getIcons().contentDesc[1];
+        } else {
+            return getIcons().discContentDesc;
+        }
+    }
+
+    @Override
     public State cleanState() {
         return new State();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index 231fe08..32d15ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA;
-import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
 
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManager.IndividualSensor;
@@ -30,7 +30,8 @@
 
 public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {
 
-    private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE};
+    private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA,
+            INDIVIDUAL_SENSOR_MICROPHONE};
 
     private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
     private final SparseBooleanArray mState = new SparseBooleanArray();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 2f66508..39472de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -284,13 +284,15 @@
                     && !mCurrentState.carrierNetworkChangeMode
                     && mCurrentState.activityOut;
             showDataIcon &= mCurrentState.dataSim && mCurrentState.isDefault;
+            boolean showTriangle = showDataIcon && !mCurrentState.airplaneMode;
+            int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.dataType : 0;
+            showDataIcon |= mCurrentState.roaming;
             IconState statusIcon = new IconState(showDataIcon && !mCurrentState.airplaneMode,
                     getCurrentIconId(), contentDescription);
-            int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.dataType : 0;
             callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
                     activityIn, activityOut, dataContentDescription, dataContentDescriptionHtml,
                     description, icons.isWide, mSubscriptionInfo.getSubscriptionId(),
-                    mCurrentState.roaming);
+                    mCurrentState.roaming, showTriangle);
         } else {
             boolean showDataIcon = mCurrentState.dataConnected || dataDisabled;
             IconState statusIcon = new IconState(
@@ -316,10 +318,11 @@
                     && mCurrentState.activityOut;
             showDataIcon &= mCurrentState.isDefault || dataDisabled;
             int typeIcon = (showDataIcon || mConfig.alwaysShowDataRatIcon) ? icons.dataType : 0;
+            boolean showTriangle = mCurrentState.enabled && !mCurrentState.airplaneMode;
             callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
                     activityIn, activityOut, dataContentDescription, dataContentDescriptionHtml,
                     description, icons.isWide, mSubscriptionInfo.getSubscriptionId(),
-                    mCurrentState.roaming);
+                    mCurrentState.roaming, showTriangle);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index f2b0d76..e60d5c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -66,12 +66,13 @@
          * @param isWide //TODO: unused?
          * @param subId subscription ID for which to update the UI
          * @param roaming indicates roaming
+         * @param showTriangle whether to show the mobile triangle the in status bar
          */
         default void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
                 int qsType, boolean activityIn, boolean activityOut,
                 CharSequence typeContentDescription,
                 CharSequence typeContentDescriptionHtml, CharSequence description,
-                boolean isWide, int subId, boolean roaming) {
+                boolean isWide, int subId, boolean roaming, boolean showTriangle) {
         }
 
         default void setSubs(List<SubscriptionInfo> subs) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f9450ae..e13e30b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -524,6 +524,10 @@
         return mWifiSignalController.isCarrierMergedWifi(subId);
     }
 
+    boolean isEthernetDefault() {
+        return mConnectedTransports.get(NetworkCapabilities.TRANSPORT_ETHERNET);
+    }
+
     String getNonDefaultMobileDataNetworkName(int subId) {
         MobileSignalController controller = getControllerWithSubId(subId);
         return controller != null ? controller.getNonDefaultCarrierName() : "";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 6c5251b..9380d91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.Nullable;
@@ -50,6 +52,8 @@
 import android.view.View;
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
@@ -61,6 +65,8 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.statusbar.IStatusBarService;
@@ -76,6 +82,7 @@
 
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -135,6 +142,27 @@
 
         mEditText = (RemoteEditText) getChildAt(0);
         mEditText.setInnerFocusable(false);
+        mEditText.setWindowInsetsAnimationCallback(
+                new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+            @NonNull
+            @Override
+            public WindowInsets onProgress(@NonNull WindowInsets insets,
+                    @NonNull List<WindowInsetsAnimation> runningAnimations) {
+                return insets;
+            }
+
+            @Override
+            public void onEnd(@NonNull WindowInsetsAnimation animation) {
+                super.onEnd(animation);
+                if (animation.getTypeMask() == WindowInsets.Type.ime()) {
+                    mEntry.mRemoteEditImeVisible =
+                            mEditText.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
+                    if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {
+                        mController.removeRemoteInput(mEntry, mToken);
+                    }
+                }
+            }
+        });
     }
 
     protected Intent prepareRemoteInputFromText() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index 7042e2f..b2120d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -106,7 +106,8 @@
         IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(), contentDescription);
         if (mProviderModel) {
             IconState qsIcon = null;
-            if (mCurrentState.isDefault) {
+            if (mCurrentState.isDefault || (!mNetworkController.isRadioOn()
+                    && !mNetworkController.isEthernetDefault())) {
                 qsIcon = new IconState(mCurrentState.connected,
                         mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected
                                 : getQsCurrentIconId(), contentDescription);
@@ -150,7 +151,7 @@
         callback.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
                 mCurrentState.activityIn, mCurrentState.activityOut, dataContentDescription,
                 dataContentDescriptionHtml, description, icons.isWide,
-                mCurrentState.subId, /* roaming= */ false);
+                mCurrentState.subId, /* roaming= */ false, /* showTriangle= */ true);
     }
 
     private int getCurrentIconIdForCarrierWifi() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
new file mode 100644
index 0000000..1af2c9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 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.util
+
+import android.content.res.Resources
+import android.content.res.TypedArray
+import android.graphics.Canvas
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+import android.util.AttributeSet
+import com.android.systemui.R
+import org.xmlpull.v1.XmlPullParser
+
+/**
+ * [DrawableWrapper] to use in the progress of a slider.
+ *
+ * This drawable is used to change the bounds of the enclosed drawable depending on the level to
+ * simulate a sliding progress, instead of using clipping or scaling. That way, the shape of the
+ * edges is maintained.
+ *
+ * Meant to be used with a rounded ends background, it will also prevent deformation when the slider
+ * is meant to be smaller than the rounded corner. The background should have rounded corners that
+ * are half of the height.
+ */
+class RoundedCornerProgressDrawable(drawable: Drawable?) : DrawableWrapper(drawable) {
+
+    constructor() : this(null)
+
+    companion object {
+        private const val MAX_LEVEL = 10000 // Taken from Drawable
+    }
+
+    private var clipPath: Path = Path()
+
+    init {
+        setClipPath(Rect())
+    }
+
+    override fun inflate(
+        r: Resources,
+        parser: XmlPullParser,
+        attrs: AttributeSet,
+        theme: Resources.Theme?
+    ) {
+        val a = obtainAttributes(r, theme, attrs, R.styleable.RoundedCornerProgressDrawable)
+
+        // Inflation will advance the XmlPullParser and AttributeSet.
+        super.inflate(r, parser, attrs, theme)
+
+        updateStateFromTypedArray(a)
+        if (drawable == null) {
+            throw IllegalStateException("${this::class.java.simpleName} needs a drawable")
+        }
+        a.recycle()
+    }
+
+    override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+        onLevelChange(level)
+        return super.onLayoutDirectionChanged(layoutDirection)
+    }
+
+    private fun updateStateFromTypedArray(a: TypedArray) {
+        if (a.hasValue(R.styleable.RoundedCornerProgressDrawable_android_drawable)) {
+            setDrawable(a.getDrawable(R.styleable.RoundedCornerProgressDrawable_android_drawable))
+        }
+    }
+
+    override fun onBoundsChange(bounds: Rect) {
+        setClipPath(bounds)
+        super.onBoundsChange(bounds)
+        onLevelChange(level)
+    }
+
+    private fun setClipPath(bounds: Rect) {
+        clipPath.reset()
+        clipPath.addRoundRect(
+                bounds.left.toFloat(),
+                bounds.top.toFloat(),
+                bounds.right.toFloat(),
+                bounds.bottom.toFloat(),
+                bounds.height().toFloat() / 2,
+                bounds.height().toFloat() / 2,
+                Path.Direction.CW
+        )
+    }
+
+    override fun onLevelChange(level: Int): Boolean {
+        val db = drawable?.bounds!!
+        val width = bounds.width() * level / MAX_LEVEL
+        // Extra space on the left to keep the rounded shape on the right end
+        val leftBound = bounds.left - bounds.height()
+        drawable?.setBounds(leftBound, db.top, bounds.left + width, db.bottom)
+        return super.onLevelChange(level)
+    }
+
+    override fun draw(canvas: Canvas) {
+        canvas.save()
+        canvas.clipPath(clipPath)
+        super.draw(canvas)
+        canvas.restore()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index bf823b4..6e7aed0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -38,6 +38,7 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -83,10 +84,14 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.function.IntConsumer;
+import java.util.function.Supplier;
 
 /**
  * The SysUi side bubbles manager which communicate with other SysUi components.
@@ -106,8 +111,9 @@
     private final NotificationGroupManagerLegacy mNotificationGroupManager;
     private final NotificationEntryManager mNotificationEntryManager;
     private final NotifPipeline mNotifPipeline;
+    private final Executor mSysuiMainExecutor;
 
-    private final ScrimView mBubbleScrim;
+    private ScrimView mBubbleScrim;
     private final Bubbles.SysuiProxy mSysuiProxy;
     // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
     private final List<NotifCallback> mCallbacks = new ArrayList<>();
@@ -133,14 +139,15 @@
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
             FeatureFlags featureFlags,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            Executor sysuiMainExecutor) {
         if (bubblesOptional.isPresent()) {
             return new BubblesManager(context, bubblesOptional.get(),
                     notificationShadeWindowController, statusBarStateController, shadeController,
                     configurationController, statusBarService, notificationManager,
                     interruptionStateProvider, zenModeController, notifUserManager,
                     groupManager, entryManager, notifPipeline, sysUiState, featureFlags,
-                    dumpManager);
+                    dumpManager, sysuiMainExecutor);
         } else {
             return null;
         }
@@ -163,7 +170,8 @@
             NotifPipeline notifPipeline,
             SysUiState sysUiState,
             FeatureFlags featureFlags,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            Executor sysuiMainExecutor) {
         mContext = context;
         mBubbles = bubbles;
         mNotificationShadeWindowController = notificationShadeWindowController;
@@ -173,6 +181,7 @@
         mNotificationGroupManager = groupManager;
         mNotificationEntryManager = entryManager;
         mNotifPipeline = notifPipeline;
+        mSysuiMainExecutor = sysuiMainExecutor;
 
         mBarService = statusBarService == null
                 ? IStatusBarService.Stub.asInterface(
@@ -181,7 +190,9 @@
 
         mBubbleScrim = new ScrimView(mContext);
         mBubbleScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-        mBubbles.setBubbleScrim(mBubbleScrim);
+        mBubbles.setBubbleScrim(mBubbleScrim, (executor, looper) -> {
+            mBubbleScrim.setExecutor(executor, looper);
+        });
 
         if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
             setupNotifPipeline();
@@ -237,128 +248,177 @@
                 });
 
         mSysuiProxy = new Bubbles.SysuiProxy() {
+            private <T> T executeBlockingForResult(Supplier<T> runnable, Executor executor,
+                    Class clazz) {
+                if (Looper.myLooper() == Looper.getMainLooper()) {
+                    return runnable.get();
+                }
+                final T[] result = (T[]) Array.newInstance(clazz, 1);
+                final CountDownLatch latch = new CountDownLatch(1);
+                executor.execute(() -> {
+                    result[0] = runnable.get();
+                    latch.countDown();
+                });
+                try {
+                    latch.await();
+                    return result[0];
+                } catch (InterruptedException e) {
+                    return null;
+                }
+            }
+
             @Override
             @Nullable
             public BubbleEntry getPendingOrActiveEntry(String key) {
-                NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
-                return entry == null ? null : notifToBubbleEntry(entry);
+                return executeBlockingForResult(() -> {
+                    NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    return entry == null ? null : notifToBubbleEntry(entry);
+                }, sysuiMainExecutor, BubbleEntry.class);
             }
 
             @Override
             public List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys) {
-                List<BubbleEntry> result = new ArrayList<>();
-                List<NotificationEntry> activeEntries =
-                        mNotificationEntryManager.getActiveNotificationsForCurrentUser();
-                for (int i = 0; i < activeEntries.size(); i++) {
-                    NotificationEntry entry = activeEntries.get(i);
-                    if (savedBubbleKeys.contains(entry.getKey())
-                            && mNotificationInterruptStateProvider.shouldBubbleUp(entry)
-                            && entry.isBubble()) {
-                        result.add(notifToBubbleEntry(entry));
+                return executeBlockingForResult(() -> {
+                    List<BubbleEntry> result = new ArrayList<>();
+                    List<NotificationEntry> activeEntries =
+                            mNotificationEntryManager.getActiveNotificationsForCurrentUser();
+                    for (int i = 0; i < activeEntries.size(); i++) {
+                        NotificationEntry entry = activeEntries.get(i);
+                        if (savedBubbleKeys.contains(entry.getKey())
+                                && mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+                                && entry.isBubble()) {
+                            result.add(notifToBubbleEntry(entry));
+                        }
                     }
-                }
-                return result;
+                    return result;
+                }, sysuiMainExecutor, List.class);
             }
 
             @Override
             public boolean isNotificationShadeExpand() {
-                return mNotificationShadeWindowController.getPanelExpanded();
+                return executeBlockingForResult(() -> {
+                    return mNotificationShadeWindowController.getPanelExpanded();
+                }, sysuiMainExecutor, Boolean.class);
             }
 
             @Override
             public boolean shouldBubbleUp(String key) {
-                final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
-                        key);
-                if (entry != null) {
-                    return mNotificationInterruptStateProvider.shouldBubbleUp(entry);
-                }
-                return false;
+                return executeBlockingForResult(() -> {
+                    final NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    if (entry != null) {
+                        return mNotificationInterruptStateProvider.shouldBubbleUp(entry);
+                    }
+                    return false;
+                }, sysuiMainExecutor, Boolean.class);
             }
 
             @Override
             public void setNotificationInterruption(String key) {
-                final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
-                        key);
-                if (entry != null && entry.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
-                    entry.setInterruption();
-                }
+                sysuiMainExecutor.execute(() -> {
+                    final NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    if (entry != null
+                            && entry.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
+                        entry.setInterruption();
+                    }
+                });
             }
 
             @Override
             public void requestNotificationShadeTopUi(boolean requestTopUi, String componentTag) {
-                mNotificationShadeWindowController.setRequestTopUi(requestTopUi, componentTag);
+                sysuiMainExecutor.execute(() -> {
+                    mNotificationShadeWindowController.setRequestTopUi(requestTopUi, componentTag);
+                });
             }
 
             @Override
             public void notifyRemoveNotification(String key, int reason) {
-                final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
-                        key);
-                if (entry != null) {
-                    for (NotifCallback cb : mCallbacks) {
-                        cb.removeNotification(entry, getDismissedByUserStats(entry, true), reason);
+                sysuiMainExecutor.execute(() -> {
+                    final NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    if (entry != null) {
+                        for (NotifCallback cb : mCallbacks) {
+                            cb.removeNotification(entry, getDismissedByUserStats(entry, true),
+                                    reason);
+                        }
                     }
-                }
+                });
             }
 
             @Override
             public void notifyInvalidateNotifications(String reason) {
-                for (NotifCallback cb : mCallbacks) {
-                    cb.invalidateNotifications(reason);
-                }
+                sysuiMainExecutor.execute(() -> {
+                    for (NotifCallback cb : mCallbacks) {
+                        cb.invalidateNotifications(reason);
+                    }
+                });
             }
 
             @Override
             public void notifyMaybeCancelSummary(String key) {
-                final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
-                        key);
-                if (entry != null) {
-                    for (NotifCallback cb : mCallbacks) {
-                        cb.maybeCancelSummary(entry);
+                sysuiMainExecutor.execute(() -> {
+                    final NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    if (entry != null) {
+                        for (NotifCallback cb : mCallbacks) {
+                            cb.maybeCancelSummary(entry);
+                        }
                     }
-                }
+                });
             }
 
             @Override
             public void removeNotificationEntry(String key) {
-                final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
-                        key);
-                if (entry != null) {
-                    mNotificationGroupManager.onEntryRemoved(entry);
-                }
+                sysuiMainExecutor.execute(() -> {
+                    final NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    if (entry != null) {
+                        mNotificationGroupManager.onEntryRemoved(entry);
+                    }
+                });
             }
 
             @Override
             public void updateNotificationBubbleButton(String key) {
-                final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
-                        key);
-                if (entry != null && entry.getRow() != null) {
-                    entry.getRow().updateBubbleButton();
-                }
+                sysuiMainExecutor.execute(() -> {
+                    final NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    if (entry != null && entry.getRow() != null) {
+                        entry.getRow().updateBubbleButton();
+                    }
+                });
             }
 
             @Override
             public void updateNotificationSuppression(String key) {
-                final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
-                        key);
-                if (entry != null) {
-                    mNotificationGroupManager.updateSuppression(entry);
-                }
+                sysuiMainExecutor.execute(() -> {
+                    final NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    if (entry != null) {
+                        mNotificationGroupManager.updateSuppression(entry);
+                    }
+                });
             }
 
             @Override
             public void onStackExpandChanged(boolean shouldExpand) {
-                sysUiState
-                        .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
-                        .commitUpdate(mContext.getDisplayId());
+                sysuiMainExecutor.execute(() -> {
+                    sysUiState.setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
+                            .commitUpdate(mContext.getDisplayId());
+                });
             }
 
             @Override
             public void onUnbubbleConversation(String key) {
-                final NotificationEntry entry =
-                        mNotificationEntryManager.getPendingOrActiveNotif(key);
-                if (entry != null) {
-                    onUserChangedBubble(entry, false /* shouldBubble */);
-                }
+                sysuiMainExecutor.execute(() -> {
+                    final NotificationEntry entry =
+                            mNotificationEntryManager.getPendingOrActiveNotif(key);
+                    if (entry != null) {
+                        onUserChangedBubble(entry, false /* shouldBubble */);
+                    }
+                });
             }
         };
         mBubbles.setSysuiProxy(mSysuiProxy);
@@ -424,9 +484,8 @@
                         final String groupKey = group.summary != null
                                 ? group.summary.getSbn().getGroupKey()
                                 : null;
-                        if (!suppressed && groupKey != null
-                                && mBubbles.isSummarySuppressed(groupKey)) {
-                            mBubbles.removeSuppressedSummary(groupKey);
+                        if (!suppressed && groupKey != null) {
+                            mBubbles.removeSuppressedSummaryIfNecessary(groupKey, null, null);
                         }
                     }
                 });
@@ -449,19 +508,16 @@
                 // Check if removed bubble has an associated suppressed group summary that needs
                 // to be removed now.
                 final String groupKey = entry.getSbn().getGroupKey();
-                if (mBubbles.isSummarySuppressed(groupKey)) {
-                    mBubbles.removeSuppressedSummary(groupKey);
-
+                mBubbles.removeSuppressedSummaryIfNecessary(groupKey, (summaryKey) -> {
                     final NotificationEntry summary =
-                            mNotificationEntryManager.getActiveNotificationUnfiltered(
-                                    mBubbles.getSummaryKey(groupKey));
+                            mNotificationEntryManager.getActiveNotificationUnfiltered(summaryKey);
                     if (summary != null) {
                         mNotificationEntryManager.performRemoveNotification(
                                 summary.getSbn(),
                                 getDismissedByUserStats(summary, false),
                                 UNDEFINED_DISMISS_REASON);
                     }
-                }
+                }, mSysuiMainExecutor);
 
                 // Check if we still need to remove the summary from NoManGroup because the summary
                 // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above.
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index 8a79ace..0795d89 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -17,24 +17,31 @@
 package com.android.systemui.wmshell;
 
 import android.content.Context;
+import android.os.Handler;
 
 import com.android.systemui.dagger.WMSingleton;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.tv.TvPipController;
 import com.android.wm.shell.pip.tv.TvPipMenuController;
 import com.android.wm.shell.pip.tv.TvPipNotificationController;
+import com.android.wm.shell.pip.tv.TvPipTransition;
+import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
 
@@ -55,20 +62,24 @@
             PipTaskOrganizer pipTaskOrganizer,
             TvPipMenuController tvPipMenuController,
             PipMediaController pipMediaController,
+            PipTransitionController pipTransitionController,
             TvPipNotificationController tvPipNotificationController,
             TaskStackListenerImpl taskStackListener,
-            WindowManagerShellWrapper windowManagerShellWrapper) {
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.of(
-                new TvPipController(
+                TvPipController.create(
                         context,
                         pipBoundsState,
                         pipBoundsAlgorithm,
                         pipTaskOrganizer,
+                        pipTransitionController,
                         tvPipMenuController,
                         pipMediaController,
                         tvPipNotificationController,
                         taskStackListener,
-                        windowManagerShellWrapper));
+                        windowManagerShellWrapper,
+                        mainExecutor));
     }
 
     @WMSingleton
@@ -84,21 +95,43 @@
         return new PipBoundsState(context);
     }
 
+    // Handler needed for loadDrawableAsync() in PipControlsViewController
+    @WMSingleton
+    @Provides
+    static PipTransitionController provideTvPipTransition(
+            Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
+            PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState, TvPipMenuController pipMenuController) {
+        return new TvPipTransition(pipBoundsState, pipMenuController,
+                pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
+    }
+
     @WMSingleton
     @Provides
     static TvPipMenuController providesTvPipMenuController(
             Context context,
             PipBoundsState pipBoundsState,
             SystemWindows systemWindows,
-            PipMediaController pipMediaController) {
-        return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController);
+            PipMediaController pipMediaController,
+            @ShellMainThread Handler mainHandler) {
+        return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController,
+                mainHandler);
+    }
+
+    // Handler needed for registerReceiverForAllUsers()
+    @WMSingleton
+    @Provides
+    static TvPipNotificationController provideTvPipNotificationController(Context context,
+            PipMediaController pipMediaController,
+            @ShellMainThread Handler mainHandler) {
+        return new TvPipNotificationController(context, pipMediaController, mainHandler);
     }
 
     @WMSingleton
     @Provides
-    static TvPipNotificationController provideTvPipNotificationController(Context context,
-            PipMediaController pipMediaController) {
-        return new TvPipNotificationController(context, pipMediaController);
+    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+            pipSurfaceTransactionHelper) {
+        return new PipAnimationController(pipSurfaceTransactionHelper);
     }
 
     @WMSingleton
@@ -107,11 +140,15 @@
             TvPipMenuController tvPipMenuController,
             PipBoundsState pipBoundsState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipAnimationController pipAnimationController,
+            PipTransitionController pipTransitionController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
-            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
+            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
-                tvPipMenuController, pipSurfaceTransactionHelper, splitScreenOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer);
+                tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
+                pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index bbc238a..b7aa907 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -71,6 +71,8 @@
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
+import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
@@ -250,14 +252,17 @@
     @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
             IActivityManager activityManager,
-            PipTouchHandler pipTouchHandler) {
-        return new PipAppOpsListener(context, activityManager, pipTouchHandler.getMotionHelper());
+            PipTouchHandler pipTouchHandler,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
     }
 
+    // Needs handler for registering broadcast receivers
     @WMSingleton
     @Provides
-    static PipMediaController providePipMediaController(Context context) {
-        return new PipMediaController(context);
+    static PipMediaController providePipMediaController(Context context,
+            @ShellMainThread Handler mainHandler) {
+        return new PipMediaController(context, mainHandler);
     }
 
     @WMSingleton
@@ -290,8 +295,8 @@
     @WMSingleton
     @Provides
     static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
-            Context context) {
-        return new ShellTaskOrganizer(mainExecutor, context);
+            Context context, SizeCompatUI sizeCompatUI) {
+        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI);
     }
 
     @WMSingleton
@@ -328,6 +333,7 @@
     @BindsOptionalOf
     abstract AppPairs optionalAppPairs();
 
+    // Note: Handler needed for LauncherApps.register
     @WMSingleton
     @Provides
     static Optional<Bubbles> provideBubbles(Context context,
@@ -338,11 +344,12 @@
             LauncherApps launcherApps,
             UiEventLogger uiEventLogger,
             ShellTaskOrganizer organizer,
-            @ShellMainThread ShellExecutor mainExecutor) {
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
         return Optional.of(BubbleController.create(context, null /* synchronizer */,
                 floatingContentCoordinator, statusBarService, windowManager,
                 windowManagerShellWrapper, launcherApps, uiEventLogger, organizer,
-                mainExecutor));
+                mainExecutor, mainHandler));
     }
 
     // Needs the shell main handler for ContentObserver callbacks
@@ -375,8 +382,7 @@
 
     @WMSingleton
     @Provides
-    static FullscreenTaskListener provideFullscreenTaskListener(
-            SyncTransactionQueue syncQueue) {
+    static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
         return new FullscreenTaskListener(syncQueue);
     }
 
@@ -387,4 +393,12 @@
             @ShellAnimationThread ShellExecutor animExecutor) {
         return new Transitions(organizer, pool, mainExecutor, animExecutor);
     }
+
+    @WMSingleton
+    @Provides
+    static SizeCompatUI provideSizeCompatUI(Context context, DisplayController displayController,
+            DisplayImeController imeController, @ShellMainThread ShellExecutor mainExecutor) {
+        return SizeCompatUIController.create(context, displayController, imeController,
+                mainExecutor);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 8105250..2aaa095 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -17,12 +17,11 @@
 package com.android.systemui.wmshell;
 
 import android.animation.AnimationHandler;
-import android.app.ActivityTaskManager;
 import android.content.Context;
+import android.os.Handler;
 import android.view.IWindowManager;
 
 import com.android.systemui.dagger.WMSingleton;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairs;
@@ -40,18 +39,19 @@
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
 import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransition;
+import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipUiEventLogger;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.pip.phone.PipAppOpsListener;
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
 
 import java.util.Optional;
@@ -103,12 +103,13 @@
             PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
             PipBoundsState pipBoundsState, PipMediaController pipMediaController,
             PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
-            PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
+            PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
+            WindowManagerShellWrapper windowManagerShellWrapper,
             TaskStackListenerImpl taskStackListener,
             @ShellMainThread ShellExecutor mainExecutor) {
         return Optional.ofNullable(PipController.create(context, displayController,
                 pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, pipMediaController,
-                phonePipMenuController, pipTaskOrganizer, pipTouchHandler,
+                phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, mainExecutor));
     }
 
@@ -125,11 +126,15 @@
         return new PipBoundsAlgorithm(context, pipBoundsState);
     }
 
+    // Handler is used by Icon.loadDrawableAsync
     @WMSingleton
     @Provides
     static PhonePipMenuController providesPipPhoneMenuController(Context context,
-            PipMediaController pipMediaController, SystemWindows systemWindows) {
-        return new PhonePipMenuController(context, pipMediaController, systemWindows);
+            PipMediaController pipMediaController, SystemWindows systemWindows,
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellMainThread Handler mainHandler) {
+        return new PhonePipMenuController(context, pipMediaController, systemWindows,
+                mainExecutor, mainHandler);
     }
 
     @WMSingleton
@@ -138,12 +143,13 @@
             PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm,
             PipBoundsState pipBoundsState,
             PipTaskOrganizer pipTaskOrganizer,
+            PipTransitionController pipTransitionController,
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTouchHandler(context, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, pipTaskOrganizer, floatingContentCoordinator, pipUiEventLogger,
-                mainExecutor);
+                pipBoundsState, pipTaskOrganizer, pipTransitionController,
+                floatingContentCoordinator, pipUiEventLogger, mainExecutor);
     }
 
     @WMSingleton
@@ -152,11 +158,32 @@
             PipBoundsState pipBoundsState,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PhonePipMenuController menuPhoneController,
+            PipAnimationController pipAnimationController,
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
+            PipTransitionController pipTransitionController,
             Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
-            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
+            PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
+            @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
-                menuPhoneController, pipSurfaceTransactionHelper, splitScreenOptional,
-                displayController, pipUiEventLogger, shellTaskOrganizer);
+                menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
+                pipTransitionController, splitScreenOptional, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipAnimationController providePipAnimationController(PipSurfaceTransactionHelper
+            pipSurfaceTransactionHelper) {
+        return new PipAnimationController(pipSurfaceTransactionHelper);
+    }
+
+    @WMSingleton
+    @Provides
+    static PipTransitionController providePipTransitionController(Context context,
+            Transitions transitions, ShellTaskOrganizer shellTaskOrganizer,
+            PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
+            PipBoundsState pipBoundsState, PhonePipMenuController pipMenuController) {
+        return new PipTransition(context, pipBoundsState, pipMenuController,
+                pipBoundsAlgorithm, pipAnimationController, transitions, shellTaskOrganizer);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
new file mode 100644
index 0000000..826be2b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 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.keyguard;
+
+import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.dagger.KeyguardStatusViewComponent;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.NavigationBarController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class KeyguardDisplayManagerTest extends SysuiTestCase {
+
+    @Mock
+    private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
+
+    @Mock
+    private DisplayManager mDisplayManager;
+
+    @Mock
+    private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
+
+    private Executor mBackgroundExecutor = Runnable::run;
+    private KeyguardDisplayManager mManager;
+
+    // The default and secondary displays are both in the default group
+    private Display mDefaultDisplay;
+    private Display mSecondaryDisplay;
+
+    // This display is in a different group from the default and secondary displays.
+    private Display mDifferentGroupDisplay;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+        mDependency.injectMockDependency(NavigationBarController.class);
+        mManager = spy(new KeyguardDisplayManager(mContext, mKeyguardStatusViewComponentFactory,
+                mBackgroundExecutor));
+        doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
+
+        mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
+                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
+        mSecondaryDisplay = new Display(DisplayManagerGlobal.getInstance(),
+                Display.DEFAULT_DISPLAY + 1,
+                new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
+
+        DisplayInfo differentGroupInfo = new DisplayInfo();
+        differentGroupInfo.displayId = Display.DEFAULT_DISPLAY + 2;
+        differentGroupInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        mDifferentGroupDisplay = new Display(DisplayManagerGlobal.getInstance(),
+                Display.DEFAULT_DISPLAY,
+                differentGroupInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
+    }
+
+    @Test
+    public void testShow_defaultDisplayOnly() {
+        when(mDisplayManager.getDisplays()).thenReturn(new Display[]{mDefaultDisplay});
+        mManager.show();
+        verify(mManager, never()).createPresentation(any());
+    }
+
+    @Test
+    public void testShow_includeSecondaryDisplay() {
+        when(mDisplayManager.getDisplays()).thenReturn(
+                new Display[]{mDefaultDisplay, mSecondaryDisplay});
+        mManager.show();
+        verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
+    }
+
+    @Test
+    public void testShow_includeNonDefaultGroupDisplay() {
+        when(mDisplayManager.getDisplays()).thenReturn(
+                new Display[]{mDefaultDisplay, mDifferentGroupDisplay});
+
+        mManager.show();
+        verify(mManager, never()).createPresentation(any());
+    }
+
+    @Test
+    public void testShow_includeSecondaryAndNonDefaultGroupDisplays() {
+        when(mDisplayManager.getDisplays()).thenReturn(
+                new Display[]{mDefaultDisplay, mSecondaryDisplay, mDifferentGroupDisplay});
+
+        mManager.show();
+        verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
deleted file mode 100644
index 71a0434..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2019 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;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.inputmethodservice.InputMethodService;
-import android.os.IBinder;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SizeCompatModeActivityController.RestartActivityButton;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.CommandQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * runtest systemui -c com.android.systemui.SizeCompatModeActivityControllerTest
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SizeCompatModeActivityControllerTest extends SysuiTestCase {
-    private static final int DISPLAY_ID = 0;
-
-    private SizeCompatModeActivityController mController;
-    private TaskStackChangeListener mTaskStackListener;
-    private @Mock TaskStackChangeListeners mMockTaskListeners;
-    private @Mock RestartActivityButton mMockButton;
-    private @Mock IBinder mMockActivityToken;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        doReturn(true).when(mMockButton).show();
-
-        mController = new SizeCompatModeActivityController(mContext, mMockTaskListeners,
-                new CommandQueue(mContext)) {
-            @Override
-            RestartActivityButton createRestartButton(Context context) {
-                return mMockButton;
-            };
-        };
-
-        ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
-                ArgumentCaptor.forClass(TaskStackChangeListener.class);
-        verify(mMockTaskListeners).registerTaskStackListener(listenerCaptor.capture());
-        mTaskStackListener = listenerCaptor.getValue();
-    }
-
-    @Test
-    public void testOnSizeCompatModeActivityChanged() {
-        // Verifies that the restart button is added with non-null component name.
-        mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, mMockActivityToken);
-        verify(mMockButton).show();
-        verify(mMockButton).updateLastTargetActivity(eq(mMockActivityToken));
-
-        // Verifies that the restart button is removed with null component name.
-        mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, null /* activityToken */);
-        verify(mMockButton).remove();
-    }
-
-    @Test
-    public void testChangeButtonVisibilityOnImeShowHide() {
-        mTaskStackListener.onSizeCompatModeActivityChanged(DISPLAY_ID, mMockActivityToken);
-
-        // Verifies that the restart button is hidden when IME is visible.
-        doReturn(View.VISIBLE).when(mMockButton).getVisibility();
-        mController.setImeWindowStatus(DISPLAY_ID, null /* token */, InputMethodService.IME_VISIBLE,
-                0 /* backDisposition */, false /* showImeSwitcher */);
-        verify(mMockButton).setVisibility(eq(View.GONE));
-
-        // Verifies that the restart button is visible when IME is hidden.
-        doReturn(View.GONE).when(mMockButton).getVisibility();
-        mController.setImeWindowStatus(DISPLAY_ID, null /* token */, 0 /* vis */,
-                0 /* backDisposition */, false /* showImeSwitcher */);
-        verify(mMockButton).setVisibility(eq(View.VISIBLE));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 02143a7..bc322f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.appops;
 
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA;
+import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE;
+
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
@@ -49,6 +52,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -81,6 +85,8 @@
     private PermissionFlagsCache mFlagsCache;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private IndividualSensorPrivacyController mSensorPrivacyController;
     @Mock(stubOnly = true)
     private AudioManager mAudioManager;
     @Mock()
@@ -118,12 +124,18 @@
         when(mAudioManager.getActiveRecordingConfigurations())
                 .thenReturn(List.of(mPausedMockRecording));
 
+        when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
+                .thenReturn(false);
+        when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA))
+                .thenReturn(false);
+
         mController = new AppOpsControllerImpl(
                 mContext,
                 mTestableLooper.getLooper(),
                 mDumpManager,
                 mFlagsCache,
                 mAudioManager,
+                mSensorPrivacyController,
                 mDispatcher
         );
     }
@@ -133,6 +145,7 @@
         mController.setListening(true);
         verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
         verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
+        verify(mSensorPrivacyController, times(1)).addCallback(mController);
     }
 
     @Test
@@ -140,6 +153,7 @@
         mController.setListening(false);
         verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
         verify(mDispatcher, times(1)).unregisterReceiver(mController);
+        verify(mSensorPrivacyController, times(1)).removeCallback(mController);
     }
 
     @Test
@@ -476,6 +490,71 @@
                 AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
     }
 
+    @Test
+    public void testAudioFilteredWhenMicDisabled() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
+                mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+        assertFalse(list.get(0).isDisabled());
+
+        // Add a camera op, and disable the microphone. The camera op should be the only op returned
+        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+
+
+        // Re enable the microphone, and verify the op returns
+        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, false);
+        mTestableLooper.processAllMessages();
+
+        list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+        int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
+        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(micIdx).getCode());
+    }
+
+    @Test
+    public void testCameraFilteredWhenCameraDisabled() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
+                mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+        assertFalse(list.get(0).isDisabled());
+
+        // Add an audio op, and disable the camera. The audio op should be the only op returned
+        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+
+        // Re enable the camera, and verify the op returns
+        mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, false);
+        mTestableLooper.processAllMessages();
+
+        list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+        int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 0 : 1;
+        assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode());
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index ed3cf9a..3e873d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -27,7 +27,6 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.hardware.biometrics.SensorProperties;
-import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -45,8 +44,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -85,13 +84,12 @@
     @Mock
     private FingerprintManager mFingerprintManager;
     @Mock
-    private DisplayManager mDisplayManager;
-    @Mock
     private WindowManager mWindowManager;
     @Mock
     private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private StatusBar mStatusBar;
 
-    private FakeSettings mSystemSettings;
     private FakeExecutor mFgExecutor;
 
     // Stuff for configuring mocks
@@ -106,7 +104,7 @@
     @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
     private IUdfpsOverlayController mOverlayController;
     @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
-    @Captor private ArgumentCaptor<Runnable> mRunAfterShowingScrimAndDotCaptor;
+    @Captor private ArgumentCaptor<Runnable> mOnIlluminatedRunnableCaptor;
 
     @Before
     public void setUp() {
@@ -119,18 +117,16 @@
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
                 true /* resetLockoutRequiresHardwareAuthToken */));
         when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
-        mSystemSettings = new FakeSettings();
         mFgExecutor = new FakeExecutor(new FakeSystemClock());
         mUdfpsController = new UdfpsController(
                 mContext,
                 mResources,
                 mLayoutInflater,
                 mFingerprintManager,
-                mDisplayManager,
                 mWindowManager,
-                mSystemSettings,
                 mStatusBarStateController,
-                mFgExecutor);
+                mFgExecutor,
+                mStatusBar);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
 
@@ -179,7 +175,7 @@
     @Test
     public void fingerDown() throws RemoteException {
         // Configure UdfpsView to accept the ACTION_DOWN event
-        when(mUdfpsView.isShowScrimAndDot()).thenReturn(false);
+        when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
         when(mUdfpsView.isValidTouch(anyFloat(), anyFloat(), anyFloat())).thenReturn(true);
 
         // GIVEN that the overlay is showing
@@ -191,12 +187,10 @@
         MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         event.recycle();
-        // THEN the scrim and dot is shown
-        verify(mUdfpsView).showScrimAndDot();
-        // AND a runnable that passes the event to FingerprintManager is set on the view
-        verify(mUdfpsView).setRunAfterShowingScrimAndDot(
-                mRunAfterShowingScrimAndDotCaptor.capture());
-        mRunAfterShowingScrimAndDotCaptor.getValue().run();
+        // THEN illumination begins
+        // AND onIlluminatedRunnable that notifies FingerprintManager is set
+        verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
+        mOnIlluminatedRunnableCaptor.getValue().run();
         verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
                 eq(0), eq(0f), eq(0f));
     }
@@ -209,12 +203,10 @@
         mFgExecutor.runAllReady();
         // WHEN fingerprint is requested because of AOD interrupt
         mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
-        // THEN the scrim and dot is shown
-        verify(mUdfpsView).showScrimAndDot();
-        // AND a runnable that passes the event to FingerprintManager is set on the view
-        verify(mUdfpsView).setRunAfterShowingScrimAndDot(
-                mRunAfterShowingScrimAndDotCaptor.capture());
-        mRunAfterShowingScrimAndDotCaptor.getValue().run();
+        // THEN illumination begins
+        // AND onIlluminatedRunnable that notifies FingerprintManager is set
+        verify(mUdfpsView).startIllumination(mOnIlluminatedRunnableCaptor.capture());
+        mOnIlluminatedRunnableCaptor.getValue().run();
         verify(mFingerprintManager).onPointerDown(eq(mUdfpsController.mSensorProps.sensorId), eq(0),
                 eq(0), eq(3f) /* minor */, eq(2f) /* major */);
     }
@@ -228,8 +220,8 @@
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         // WHEN it is cancelled
         mUdfpsController.onCancelAodInterrupt();
-        // THEN the scrim and dot is hidden
-        verify(mUdfpsView).hideScrimAndDot();
+        // THEN the illumination is hidden
+        verify(mUdfpsView).stopIllumination();
     }
 
     @Test
@@ -242,7 +234,13 @@
         // WHEN it times out
         mFgExecutor.advanceClockToNext();
         mFgExecutor.runAllReady();
-        // THEN the scrim and dot is hidden
-        verify(mUdfpsView).hideScrimAndDot();
+        // THEN the illumination is hidden
+        verify(mUdfpsView).stopIllumination();
+    }
+
+    @Test
+    public void registersViewForCallbacks() throws RemoteException {
+        verify(mStatusBarStateController).addCallback(mUdfpsView);
+        verify(mStatusBar).addExpansionChangedListener(mUdfpsView);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index 51cf501..47f4183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.view.View;
+import android.view.WindowInsetsController;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -87,6 +88,8 @@
     @Test
     public void testOnRotationProposalShowButtonShowNav() {
         // No navigation bar should not call to set visibility state
+        mRotationButtonController.onBehaviorChanged(
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
         mRotationButtonController.onNavigationBarWindowVisibilityChange(false /* showing */);
         verify(mRotationButtonController, times(0)).setRotateSuggestionButtonState(
                 false /* visible */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index b7d1bc6..6db21f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -362,34 +362,6 @@
     }
 
     @Test
-    public void testAugmentTileFromStorageWithNotification() {
-        PeopleSpaceTile tile =
-                new PeopleSpaceTile
-                        .Builder("id", "userName", ICON, new Intent())
-                        .build();
-        PeopleSpaceTile actual = PeopleSpaceUtils
-                .augmentTileFromStorage(tile, mAppWidgetManager, WIDGET_ID_WITH_SHORTCUT);
-
-        assertThat(actual.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
-        assertThat(actual.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT);
-        assertThat(actual.getNotificationDataUri()).isEqualTo(URI);
-    }
-
-    @Test
-    public void testAugmentTileFromStorageWithoutNotification() {
-        PeopleSpaceTile tile =
-                new PeopleSpaceTile
-                        .Builder("id", "userName", ICON, new Intent())
-                        .build();
-        PeopleSpaceTile actual = PeopleSpaceUtils
-                .augmentTileFromStorage(tile, mAppWidgetManager, WIDGET_ID_WITHOUT_SHORTCUT);
-
-        assertThat(actual.getNotificationKey()).isEqualTo(null);
-        assertThat(actual.getNotificationKey()).isEqualTo(null);
-        assertThat(actual.getNotificationDataUri()).isEqualTo(null);
-    }
-
-    @Test
     public void testDoNotUpdateSingleConversationAppWidgetWhenNotBirthday() {
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT};
         when(mMockCursor.moveToNext()).thenReturn(true, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 8c0afb8..ef314ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -21,6 +21,8 @@
 
 import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -31,26 +33,24 @@
 
 import static java.util.Objects.requireNonNull;
 
-import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.Person;
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
+import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
-import android.widget.RemoteViews;
 
 import androidx.preference.PreferenceManager;
 import androidx.test.filters.SmallTest;
@@ -58,6 +58,7 @@
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.PeopleSpaceUtils;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
 import com.android.systemui.statusbar.SbnBuilder;
@@ -74,26 +75,27 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class PeopleSpaceWidgetManagerTest extends SysuiTestCase {
     private static final long MIN_LINGER_DURATION = 5;
 
-    private static final String TEST_PACKAGE_A = "com.test.package_a";
+    private static final String TEST_PACKAGE_A = "com.android.systemui.tests";
     private static final String TEST_PACKAGE_B = "com.test.package_b";
     private static final String TEST_CHANNEL_ID = "channel_id";
     private static final String TEST_CHANNEL_NAME = "channel_name";
     private static final String TEST_PARENT_CHANNEL_ID = "parent_channel_id";
     private static final String TEST_CONVERSATION_ID = "conversation_id";
     private static final int WIDGET_ID_WITH_SHORTCUT = 1;
+    private static final int SECOND_WIDGET_ID_WITH_SHORTCUT = 3;
     private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2;
     private static final String SHORTCUT_ID = "101";
     private static final String OTHER_SHORTCUT_ID = "102";
-    private static final String NOTIFICATION_KEY = "notification_key";
-    private static final String NOTIFICATION_CONTENT = "notification_content";
+    private static final String NOTIFICATION_KEY = "0|com.android.systemui.tests|0|null|0";
+    private static final String NOTIFICATION_CONTENT = "message text";
     private static final Uri URI = Uri.parse("fake_uri");
     private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android);
     private static final Person PERSON = new Person.Builder()
@@ -105,10 +107,14 @@
     private static final PeopleSpaceTile PERSON_TILE =
             new PeopleSpaceTile
                     .Builder(SHORTCUT_ID, "username", ICON, new Intent())
-                    .setNotificationKey(NOTIFICATION_KEY)
+                    .setPackageName(TEST_PACKAGE_A)
+                    .setUid(0)
+                    .setNotificationKey(NOTIFICATION_KEY + "1")
                     .setNotificationContent(NOTIFICATION_CONTENT)
                     .setNotificationDataUri(URI)
                     .build();
+    private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext,
+            SHORTCUT_ID).setLongLabel("name").build();
 
     private PeopleSpaceWidgetManager mManager;
 
@@ -119,175 +125,101 @@
     @Mock
     private AppWidgetManager mAppWidgetManager;
     @Mock
-    private INotificationManager mINotificationManager;
+    private IPeopleManager mIPeopleManager;
 
     @Captor
     private ArgumentCaptor<NotificationHandler> mListenerCaptor;
+    @Captor
+    private ArgumentCaptor<Bundle> mBundleArgumentCaptor;
 
     private final NoManSimulator mNoMan = new NoManSimulator();
     private final FakeSystemClock mClock = new FakeSystemClock();
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mManager =
                 new PeopleSpaceWidgetManager(mContext);
-        mManager.setAppWidgetManager(mIAppWidgetService, mAppWidgetManager, mINotificationManager);
+        mManager.setAppWidgetManager(mIAppWidgetService, mAppWidgetManager, mIPeopleManager);
         mManager.attach(mListenerService);
 
         verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
         NotificationHandler serviceListener = requireNonNull(mListenerCaptor.getValue());
         mNoMan.addListener(serviceListener);
+        // Default to single People tile widgets.
         Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 2);
+                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
 
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
-        SharedPreferences.Editor editor = sp.edit();
-        editor.putString(String.valueOf(WIDGET_ID_WITH_SHORTCUT), SHORTCUT_ID);
-        editor.apply();
+        setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
+
         Bundle options = new Bundle();
-        options.putParcelable(OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE);
-
+        options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE);
         when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT)))
                 .thenReturn(options);
         when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
                 .thenReturn(new Bundle());
+        when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(
+                getConversationWithShortcutId(SHORTCUT_ID));
     }
 
     @Test
-    public void testDoNotNotifyAppWidgetIfNoWidgets() throws RemoteException {
+    public void testDoNotUpdateAppWidgetIfNoWidgets() throws Exception {
         int[] widgetIdsArray = {};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
-        NotifEvent notif1 = mNoMan.postNotif(
-                new NotificationEntryBuilder()
-                        .setId(0)
-                        .setPkg(TEST_PACKAGE_A));
+        StatusBarNotification sbn = createConversationNotification(OTHER_SHORTCUT_ID);
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setSbn(sbn)
+                .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mIAppWidgetService, never()).notifyAppWidgetViewDataChanged(any(), any(), anyInt());
+        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
+                any());
     }
 
     @Test
-    public void testDoNotNotifySingleConversationAppWidgetIfNoWidgets() throws RemoteException {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
+    public void testDoNotUpdateAppWidgetIfNoShortcutInfo() throws Exception {
         int[] widgetIdsArray = {};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
-        NotifEvent notif1 = mNoMan.postNotif(
-                new NotificationEntryBuilder()
-                        .setId(0)
-                        .setPkg(TEST_PACKAGE_A));
+        Notification notificationWithoutShortcut = new Notification.Builder(mContext)
+                .setContentTitle("TEST_TITLE")
+                .setContentText("TEST_TEXT")
+                .setStyle(new Notification.MessagingStyle(PERSON)
+                        .addMessage(new Notification.MessagingStyle.Message("text3", 10, PERSON))
+                )
+                .build();
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setSbn(new SbnBuilder()
+                        .setNotification(notificationWithoutShortcut)
+                        .setPkg(TEST_PACKAGE_A)
+                        .build())
+                .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(), any(RemoteViews.class));
-        verify(mIAppWidgetService, never()).notifyAppWidgetViewDataChanged(any(), any(), anyInt());
+        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
+                any());
     }
 
     @Test
-    public void testNotifyAppWidgetIfNotificationPosted() throws RemoteException {
-        int[] widgetIdsArray = {1};
+    public void testDoNotUpdateAppWidgetIfNoPackage() throws Exception {
+        int[] widgetIdsArray = {};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
-        NotifEvent notif1 = mNoMan.postNotif(
-                new NotificationEntryBuilder()
-                        .setId(0)
-                        .setPkg(TEST_PACKAGE_A));
+        StatusBarNotification sbnWithoutPackageName = new SbnBuilder()
+                .setNotification(createMessagingStyleNotification(SHORTCUT_ID))
+                .build();
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setSbn(sbnWithoutPackageName)
+                .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mIAppWidgetService, times(1))
-                .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
-        verify(mIAppWidgetService, never()).updateAppWidgetIds(any(), any(),
-                any(RemoteViews.class));
-    }
-
-    @Test
-    public void testNotifySingleConversationAppWidgetOnceIfNotificationPosted()
-            throws RemoteException {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
-        when(mINotificationManager.getConversations(false)).thenReturn(
-                new ParceledListSlice(getConversationWithShortcutId()));
-        int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
-        when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
-
-        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1));
-
-        verify(mIAppWidgetService, never())
-                .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
-        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
-                any(RemoteViews.class));
-        verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITHOUT_SHORTCUT),
-                any(RemoteViews.class));
-    }
-
-    @Test
-    public void testNotifySingleConversationAppWidgetTwiceIfTwoNotificationsPosted()
-            throws RemoteException {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
-        when(mINotificationManager.getConversations(false)).thenReturn(
-                new ParceledListSlice(getConversationWithShortcutId()));
-        int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
-        when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
-
-        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1));
-        mClock.advanceTime(4);
-        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_B)
-                .setId(2));
-
-        verify(mIAppWidgetService, never())
-                .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
-        verify(mAppWidgetManager, times(2)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
-                any(RemoteViews.class));
-        verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITHOUT_SHORTCUT),
-                any(RemoteViews.class));
-    }
-
-    @Test
-    public void testNotifyAppWidgetTwiceIfTwoNotificationsPosted() throws RemoteException {
-        int[] widgetIdsArray = {1, 2};
-        when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
-
-        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1));
-        mClock.advanceTime(4);
-        NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_B)
-                .setId(2));
-
-        verify(mIAppWidgetService, times(2))
-                .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
         verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
-                any(RemoteViews.class));
+                any());
     }
 
     @Test
-    public void testNotifyAppWidgetTwiceIfNotificationPostedAndRemoved() throws RemoteException {
-        int[] widgetIdsArray = {1, 2};
-        when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
-
-        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setPkg(TEST_PACKAGE_A)
-                .setId(1));
-        mClock.advanceTime(4);
-        NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0);
-
-        verify(mIAppWidgetService, times(2))
-                .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
-        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
-                any(RemoteViews.class));
-    }
-
-    @Test
-    public void testDoNotNotifyAppWidgetIfNonConversationChannelModified() throws RemoteException {
+    public void testDoNotUpdateAppWidgetIfNonConversationChannelModified() throws Exception {
         int[] widgetIdsArray = {1};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
@@ -298,13 +230,12 @@
                 UserHandle.getUserHandleForUid(0), channel, IMPORTANCE_HIGH);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mIAppWidgetService, never()).notifyAppWidgetViewDataChanged(any(), any(), anyInt());
         verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
-                any(RemoteViews.class));
+                any());
     }
 
     @Test
-    public void testNotifyAppWidgetIfConversationChannelModified() throws RemoteException {
+    public void testUpdateAppWidgetIfConversationChannelModified() throws Exception {
         int[] widgetIdsArray = {1};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
@@ -316,45 +247,57 @@
                 UserHandle.getUserHandleForUid(0), channel, IMPORTANCE_HIGH);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mIAppWidgetService, times(1))
-                .notifyAppWidgetViewDataChanged(any(), eq(widgetIdsArray), anyInt());
-        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
-                any(RemoteViews.class));
+        verify(mAppWidgetManager, times(1)).updateAppWidget(anyInt(),
+                any());
     }
 
     @Test
-    public void testDoNotUpdateNotificationPostedIfNoExistingTile() throws RemoteException {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
-        when(mINotificationManager.getConversations(false)).thenReturn(
-                new ParceledListSlice(getConversationWithShortcutId()));
+    public void testDoNotUpdateNotificationPostedIfDifferentShortcutId() throws Exception {
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
         StatusBarNotification sbn = createConversationNotification(OTHER_SHORTCUT_ID);
         NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
                 .setSbn(sbn)
-                .setPkg(TEST_PACKAGE_A)
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
         verify(mAppWidgetManager, never())
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any());
+                .updateAppWidgetOptions(anyInt(), any());
+        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
+                any());
     }
 
     @Test
-    public void testDoNotUpdateNotificationRemovedIfNoExistingTile() throws RemoteException {
+    public void testDoNotUpdateNotificationPostedIfDifferentPackageName() throws Exception {
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
-        when(mINotificationManager.getConversations(false)).thenReturn(
-                new ParceledListSlice(getConversationWithShortcutId()));
+        int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
+        when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+        StatusBarNotification sbnWithDifferentPackageName = new SbnBuilder()
+                .setNotification(createMessagingStyleNotification(SHORTCUT_ID))
+                .setPkg(TEST_PACKAGE_B)
+                .build();
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setSbn(sbnWithDifferentPackageName)
+                .setId(1));
+        mClock.advanceTime(MIN_LINGER_DURATION);
+
+        verify(mAppWidgetManager, never())
+                .updateAppWidgetOptions(anyInt(), any());
+        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
+                any());
+    }
+
+    @Test
+    public void testDoNotUpdateNotificationRemovedIfDifferentShortcutId() throws Exception {
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
         StatusBarNotification sbn = createConversationNotification(OTHER_SHORTCUT_ID);
         NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
                 .setSbn(sbn)
-                .setPkg(TEST_PACKAGE_A)
                 .setId(1));
         mClock.advanceTime(4);
         NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0);
@@ -362,99 +305,215 @@
 
         verify(mAppWidgetManager, never())
                 .updateAppWidgetOptions(anyInt(), any());
+        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
+                any());
     }
 
     @Test
-    public void testUpdateNotificationPostedIfExistingTile() throws RemoteException {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
-        when(mINotificationManager.getConversations(false)).thenReturn(
-                new ParceledListSlice(getConversationWithShortcutId()));
+    public void testDoNotUpdateNotificationRemovedIfDifferentPackageName() throws Exception {
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
-        StatusBarNotification sbn = createConversationNotification(SHORTCUT_ID);
+        StatusBarNotification sbnWithDifferentPackageName = new SbnBuilder()
+                .setNotification(createMessagingStyleNotification(SHORTCUT_ID))
+                .setPkg(TEST_PACKAGE_B)
+                .build();
         NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
-                .setSbn(sbn)
-                .setPkg(TEST_PACKAGE_A)
+                .setSbn(sbnWithDifferentPackageName)
+                .setId(1));
+        mClock.advanceTime(4);
+        NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0);
+        mClock.advanceTime(MIN_LINGER_DURATION);
+
+        verify(mAppWidgetManager, never())
+                .updateAppWidgetOptions(anyInt(), any());
+        verify(mAppWidgetManager, never()).updateAppWidget(anyInt(),
+                any());
+    }
+
+    @Test
+    public void testUpdateNotificationPostedIfExistingTile() throws Exception {
+        int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
+        when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setSbn(createConversationNotification(SHORTCUT_ID))
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
         verify(mAppWidgetManager, times(1))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any());
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        Bundle bundle = mBundleArgumentCaptor.getValue();
+        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE);
+        assertThat(tile.getNotificationKey()).isEqualTo(NOTIFICATION_KEY);
+        assertThat(tile.getNotificationContent()).isEqualTo(NOTIFICATION_CONTENT);
+        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+                any());
+    }
+
+    @Test
+    public void testUpdateNotificationPostedOnTwoExistingTiles() throws Exception {
+        Bundle options = new Bundle();
+        options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE);
+        when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT)))
+                .thenReturn(options);
+        // Set the same Person associated on another People Tile widget ID.
+        setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
+        setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT);
+
+        int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT,
+                SECOND_WIDGET_ID_WITH_SHORTCUT};
+        when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setSbn(createConversationNotification(SHORTCUT_ID))
+                .setId(1));
+        mClock.advanceTime(MIN_LINGER_DURATION);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        any());
+        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+                any());
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
+                        any());
+        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
+                any());
+    }
+
+    @Test
+    public void testUpdateNotificationOnExistingTileAfterRemovingTileForSamePerson()
+            throws Exception {
+        Bundle options = new Bundle();
+        options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, PERSON_TILE);
+        when(mAppWidgetManager.getAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT)))
+                .thenReturn(options);
+        // Set the same Person associated on another People Tile widget ID.
+        setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT);
+        setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, SECOND_WIDGET_ID_WITH_SHORTCUT);
+
+        int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT,
+                SECOND_WIDGET_ID_WITH_SHORTCUT};
+        when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
+
+        PeopleSpaceUtils.removeStorageForTile(mContext, SECOND_WIDGET_ID_WITH_SHORTCUT);
+        NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+                .setSbn(createConversationNotification(SHORTCUT_ID))
+                .setId(1));
+        mClock.advanceTime(MIN_LINGER_DURATION);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        any());
+        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+                any());
+        verify(mAppWidgetManager, never())
+                .updateAppWidgetOptions(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
+                        any());
+        verify(mAppWidgetManager, never()).updateAppWidget(eq(SECOND_WIDGET_ID_WITH_SHORTCUT),
+                any());
     }
 
     @Test
     public void testDoNotUpdateNotificationPostedWithoutMessagesIfExistingTile()
-            throws RemoteException {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
-        when(mINotificationManager.getConversations(false)).thenReturn(
-                new ParceledListSlice(getConversationWithShortcutId()));
+            throws Exception {
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
-        Notification notification = new Notification.Builder(mContext)
+        Notification notificationWithoutMessagingStyle = new Notification.Builder(mContext)
                 .setContentTitle("TEST_TITLE")
                 .setContentText("TEST_TEXT")
                 .setShortcutId(SHORTCUT_ID)
                 .build();
         StatusBarNotification sbn = new SbnBuilder()
-                .setNotification(notification)
+                .setNotification(notificationWithoutMessagingStyle)
                 .build();
         NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
                 .setSbn(sbn)
-                .setPkg(TEST_PACKAGE_A)
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
 
         verify(mAppWidgetManager, never())
                 .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any());
+        verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+                any());
     }
 
     @Test
-    public void testUpdateNotificationRemovedIfExistingTile() throws RemoteException {
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0);
-        when(mINotificationManager.getConversations(false)).thenReturn(
-                new ParceledListSlice(getConversationWithShortcutId()));
+    public void testUpdateNotificationRemovedIfExistingTile() throws Exception {
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT};
         when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
         StatusBarNotification sbn = createConversationNotification(SHORTCUT_ID);
         NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
                 .setSbn(sbn)
-                .setPkg(TEST_PACKAGE_A)
                 .setId(1));
         mClock.advanceTime(MIN_LINGER_DURATION);
         NotifEvent notif1b = mNoMan.retractNotif(notif1.sbn, 0);
         mClock.advanceTime(MIN_LINGER_DURATION);
 
-        verify(mAppWidgetManager, times(2))
-                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any());
+        verify(mAppWidgetManager, times(2)).updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                mBundleArgumentCaptor.capture());
+        Bundle bundle = mBundleArgumentCaptor.getValue();
+        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_SPACE_TILE);
+        assertThat(tile.getNotificationKey()).isEqualTo(null);
+        assertThat(tile.getNotificationContent()).isEqualTo(null);
+        assertThat(tile.getNotificationDataUri()).isEqualTo(null);
+        verify(mAppWidgetManager, times(2)).updateAppWidget(anyInt(),
+                any());
     }
 
-    /** Returns a list of a single conversation associated with {@code SHORTCUT_ID}. */
-    private List<ConversationChannelWrapper> getConversationWithShortcutId() {
-        List<ConversationChannelWrapper> convos = new ArrayList<>();
-        ConversationChannelWrapper convo1 = new ConversationChannelWrapper();
-        convo1.setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID).setLongLabel(
-                "name").build());
-        convos.add(convo1);
-        return convos;
+    /**
+     * Returns a single conversation associated with {@code shortcutId}.
+     */
+    private ConversationChannel getConversationWithShortcutId(String shortcutId) throws Exception {
+        ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext, shortcutId).setLongLabel(
+                "name").build();
+        ConversationChannel convo = new ConversationChannel(shortcutInfo, 0, null, null,
+                0L, false);
+        return convo;
     }
 
-    private StatusBarNotification createConversationNotification(String shortcutId) {
-        Notification notification = new Notification.Builder(mContext)
+    private Notification createMessagingStyleNotification(String shortcutId) {
+        return new Notification.Builder(mContext)
                 .setContentTitle("TEST_TITLE")
                 .setContentText("TEST_TEXT")
                 .setShortcutId(shortcutId)
                 .setStyle(new Notification.MessagingStyle(PERSON)
-                        .addMessage(new Notification.MessagingStyle.Message("text3", 10, PERSON))
+                        .addMessage(
+                                new Notification.MessagingStyle.Message(NOTIFICATION_CONTENT, 10,
+                                        PERSON))
                 )
                 .build();
+    }
+
+    private StatusBarNotification createConversationNotification(String shortcutId) {
+        Notification notification = createMessagingStyleNotification(shortcutId);
         return new SbnBuilder()
                 .setNotification(notification)
+                .setPkg(TEST_PACKAGE_A)
                 .build();
     }
+
+    private void setStorageForTile(String shortcutId, String packageName, int widgetId) {
+        SharedPreferences widgetSp = mContext.getSharedPreferences(
+                String.valueOf(widgetId),
+                Context.MODE_PRIVATE);
+        SharedPreferences.Editor widgetEditor = widgetSp.edit();
+        widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, packageName);
+        widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, shortcutId);
+        widgetEditor.putInt(PeopleSpaceUtils.USER_ID, 0);
+        widgetEditor.apply();
+
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putString(String.valueOf(widgetId), shortcutId);
+        String key = PeopleSpaceUtils.getKey(shortcutId, packageName, 0);
+        Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>()));
+        storedWidgetIds.add(String.valueOf(widgetId));
+        editor.putStringSet(key, storedWidgetIds);
+        editor.apply();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 2546813..f86b465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -295,8 +295,9 @@
         )
         controller.showDialog(context)
         exhaustExecutors()
-        assertThat(dialogProvider.list).hasSize(1)
+        assertThat(dialogProvider.list).hasSize(2)
         assertThat(dialogProvider.list?.get(0)?.lastActiveTimestamp).isEqualTo(1L)
+        assertThat(dialogProvider.list?.get(1)?.lastActiveTimestamp).isEqualTo(0L)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
index 9762fff..eb5dd4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt
@@ -72,7 +72,7 @@
         )
         dialog = PrivacyDialog(context, list, starter)
         dialog.show()
-        dialog.requireViewById<View>(R.id.link).callOnClick()
+        dialog.requireViewById<View>(R.id.privacy_item).callOnClick()
         verify(starter).invoke(PrivacyType.TYPE_MICROPHONE.permGroupName)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index c6f97fa..4381158 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -118,7 +118,7 @@
                 mQSTileHost, mQSCustomizerController, true, mMediaHost,
                 mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger,
                 mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory,
-                /* labelsFlag */ false, /* sideLabels */ false);
+                /* labelsFlag */ false);
 
         mController.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index c490c4c..107160f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -83,7 +83,8 @@
                 metricsLogger,
                 uiEventLogger,
                 qsLogger,
-                dumpManager
+                dumpManager,
+                false
         )
 
         controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 8a412bf..b452d3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -218,7 +218,7 @@
         mSignalCallback.setMobileDataIndicators(
                 mock(NetworkController.IconState.class),
                 mock(NetworkController.IconState.class),
-                0, 0, true, true, "", "", "", true, 0, true);
+                0, 0, true, true, "", "", "", true, 0, true, true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
new file mode 100644
index 0000000..b37ac4a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 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.qs.tiles;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.logging.QSLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class NfcTileTest extends SysuiTestCase {
+
+    private static final String TILES_STOCK_WITHOUT_NFC = "wifi,cell,battery,dnd,flashlight,bt";
+    private static final String TILES_STOCK_WITH_NFC = "wifi,cell,battery,dnd,nfc,flashlight,bt";
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private ActivityStarter mActivityStarter;
+    @Mock
+    private QSTileHost mHost;
+    @Mock
+    private MetricsLogger mMetricsLogger;
+    @Mock
+    private StatusBarStateController mStatusBarStateController;
+    @Mock
+    private QSLogger mQSLogger;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+
+    private TestableLooper mTestableLooper;
+    private NfcTile mNfcTile;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mTestableLooper = TestableLooper.get(this);
+
+        when(mHost.getContext()).thenReturn(mMockContext);
+        when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+
+        mNfcTile = new NfcTile(
+                mHost,
+                mTestableLooper.getLooper(),
+                new Handler(mTestableLooper.getLooper()),
+                mMetricsLogger,
+                mStatusBarStateController,
+                mActivityStarter,
+                mQSLogger,
+                mBroadcastDispatcher
+        );
+    }
+
+    @Test
+    public void testIsAvailable_stockWithoutNfc_returnsFalse() {
+        when(mMockContext.getString(R.string.quick_settings_tiles_stock)).thenReturn(
+                TILES_STOCK_WITHOUT_NFC);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)).thenReturn(true);
+        assertFalse(mNfcTile.isAvailable());
+    }
+
+    @Test
+    public void testIsAvailable_stockWithNfc_returnsTrue() {
+        when(mMockContext.getString(R.string.quick_settings_tiles_stock)).thenReturn(
+                TILES_STOCK_WITH_NFC);
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)).thenReturn(true);
+        assertTrue(mNfcTile.isAvailable());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
index b63274b..451c78f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -71,7 +72,8 @@
     @Mock private NavigationModeController mMockNavModeController;
     @Mock private NotificationShadeWindowController mMockStatusBarWinController;
     @Mock private Optional<Pip> mMockPipOptional;
-    @Mock private Optional<LegacySplitScreen> mMockSplitScreenOptional;
+    @Mock private Optional<LegacySplitScreen> mMockLegacySplitScreenOptional;
+    @Mock private Optional<SplitScreen> mMockSplitScreenOptional;
     @Mock private Optional<Lazy<StatusBar>> mMockStatusBarOptionalLazy;
     @Mock private Optional<com.android.wm.shell.onehanded.OneHanded> mMockOneHandedOptional;
     @Mock private PackageManager mPackageManager;
@@ -89,8 +91,8 @@
 
         mSpiedOverviewProxyService = spy(new OverviewProxyService(mSpiedContext, mMockCommandQueue,
                 mMockNavBarControllerLazy, mMockNavModeController, mMockStatusBarWinController,
-                mMockSysUiState, mMockPipOptional, mMockSplitScreenOptional,
-                mMockStatusBarOptionalLazy, mMockOneHandedOptional,
+                mMockSysUiState, mMockPipOptional, mMockLegacySplitScreenOptional,
+                mMockSplitScreenOptional, mMockStatusBarOptionalLazy, mMockOneHandedOptional,
                 mMockBroadcastDispatcher, mMockTransitions));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
index 53ff957..fe2f5f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java
@@ -146,7 +146,8 @@
                 mAssistantFeedbackController);
         TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
         assertEquals("This notification was automatically demoted to Silent by the system. "
-                        + "Was this correct?", prompt.getText().toString());
+                        + "Let the developer know your feedback. Was this correct?",
+                        prompt.getText().toString());
     }
 
     @Test
@@ -157,7 +158,8 @@
                 mAssistantFeedbackController);
         TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
         assertEquals("This notification was automatically ranked higher in your shade. "
-                        + "Was this correct?", prompt.getText().toString());
+                        + "Let the developer know your feedback. Was this correct?",
+                        prompt.getText().toString());
     }
 
     @Test
@@ -168,7 +170,7 @@
                 mAssistantFeedbackController);
         TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
         assertEquals("This notification was automatically promoted to Default by the system. "
-                        + "Was this correct?",
+                        + "Let the developer know your feedback. Was this correct?",
                 prompt.getText().toString());
     }
 
@@ -180,7 +182,8 @@
                 mAssistantFeedbackController);
         TextView prompt = mFeedbackInfo.findViewById(R.id.prompt);
         assertEquals("This notification was automatically ranked lower in your shade. "
-                        + "Was this correct?", prompt.getText().toString());
+                        + "Let the developer know your feedback. Was this correct?",
+                        prompt.getText().toString());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 63bfd6a..919ddcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager.SMALL_CORNER_RADIUS;
-
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -25,12 +23,14 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -61,10 +61,14 @@
     private ExpandableNotificationRow mSecond;
     @Mock
     private KeyguardBypassController mBypassController;
+    private float mSmallRadiusRatio;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        final Resources resources = mContext.getResources();
+        mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small)
+                / resources.getDimension(R.dimen.notification_corner_radius);
         mRoundnessManager = new NotificationRoundnessManager(
                 mBypassController,
                 new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
@@ -141,7 +145,7 @@
                 createSection(null, null)
         });
         Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mSecond.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f);
     }
 
     @Test
@@ -168,8 +172,8 @@
 
         row.setHeadsUp(false);
         mRoundnessManager.updateView(entry.getRow(), false);
-        Assert.assertEquals(SMALL_CORNER_RADIUS, row.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(SMALL_CORNER_RADIUS, row.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, row.getCurrentBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, row.getCurrentTopRoundness(), 0.0f);
     }
 
     @Test
@@ -179,7 +183,7 @@
                 createSection(null, mSecond)
         });
         Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mSecond.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f);
     }
 
     @Test
@@ -188,7 +192,7 @@
                 createSection(mFirst, mFirst),
                 createSection(mSecond, null)
         });
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mSecond.getCurrentBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentBottomRoundness(), 0.0f);
         Assert.assertEquals(1.0f, mSecond.getCurrentTopRoundness(), 0.0f);
     }
 
@@ -198,7 +202,7 @@
                 createSection(mFirst, null),
                 createSection(null, null)
         });
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
         Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f);
     }
 
@@ -208,8 +212,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
     }
 
     @Test
@@ -255,8 +259,8 @@
                 createSection(mSecond, mSecond),
                 createSection(null, null)
         });
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
     }
 
     @Test
@@ -305,8 +309,8 @@
         });
         mFirst.setHeadsUpAnimatingAway(true);
         mFirst.setHeadsUpAnimatingAway(false);
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentBottomRoundness(), 0.0f);
-        Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentTopRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f);
+        Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index ceb4d84..461f64e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -83,6 +83,7 @@
 
     private NotificationStackScrollLayout mStackScroller;  // Normally test this
     private NotificationStackScrollLayout mStackScrollerInternal;  // See explanation below
+    private AmbientState mAmbientState;
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
     @Mock private StatusBar mBar;
@@ -123,6 +124,9 @@
                 });
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
 
+        // Interact with real instance of AmbientState.
+        mAmbientState = new AmbientState(mContext, mNotificationSectionsManager);
+
         // The actual class under test.  You may need to work with this class directly when
         // testing anonymous class members of mStackScroller, like mMenuEventListener,
         // which refer to members of NotificationStackScrollLayout. The spy
@@ -134,7 +138,8 @@
                 mNotificationSectionsManager,
                 mGroupMembershipManger,
                 mGroupExpansionManager,
-                mStatusBarStateController
+                mStatusBarStateController,
+                mAmbientState
         );
         mStackScrollerInternal.initView(getContext(), mKeyguardBypassEnabledProvider,
                 mNotificationSwipeHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 23c0930..bdde822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -88,7 +88,6 @@
     @Mock private NotificationPanelViewController mNotificationPanel;
     @Mock private View mAmbientIndicationContainer;
     @Mock private BiometricUnlockController mBiometricUnlockController;
-    @Mock private LockscreenLockIconController mLockscreenLockIconController;
     @Mock private AuthController mAuthController;
 
     @Before
@@ -100,7 +99,7 @@
                 mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
-                mLockscreenLockIconController, mAuthController, mNotificationIconAreaController);
+                mAuthController, mNotificationIconAreaController);
 
         mDozeServiceHost.initialize(
                 mStatusBar,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
index 95a3505..60af16a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -30,12 +29,12 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardSecurityModel;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -45,6 +44,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -64,7 +64,7 @@
     @Mock
     private KeyguardIndicationController mKeyguardIndicationController;
     @Mock
-    private LockIcon mLockIcon; // TODO: make this not a mock once inject is removed.
+    private LockIcon mLockIcon;
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
@@ -81,34 +81,36 @@
     private Resources mResources;
     @Mock
     private HeadsUpManagerPhone mHeadsUpManagerPhone;
-    @Mock
-    private KeyguardSecurityModel mKeyguardSecurityModel;
 
     private LockscreenLockIconController mLockIconController;
+
+    @Captor ArgumentCaptor<OnAttachStateChangeListener> mOnAttachStateChangeCaptor;
+    @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+
     private OnAttachStateChangeListener mOnAttachStateChangeListener;
+    private StatusBarStateController.StateListener mStatusBarStateListener;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mKeyguardUpdateMonitor.shouldShowLockIcon()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.canShowLockIcon()).thenReturn(true);
         when(mLockIcon.getContext()).thenReturn(mContext);
-        mLockIconController = new LockscreenLockIconController(
+        mLockIconController = new LockscreenLockIconController(mLockIcon,
                 mLockscreenGestureLogger, mKeyguardUpdateMonitor, mLockPatternUtils,
                 mShadeController, mAccessibilityController, mKeyguardIndicationController,
                 mStatusBarStateController, mConfigurationController, mNotificationWakeUpCoordinator,
                 mKeyguardBypassController, mDockManager, mKeyguardStateController, mResources,
-                mHeadsUpManagerPhone, mKeyguardSecurityModel);
+                mHeadsUpManagerPhone);
 
-        ArgumentCaptor<OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
-                ArgumentCaptor.forClass(OnAttachStateChangeListener.class);
+        when(mLockIcon.isAttachedToWindow()).thenReturn(true);
+        mLockIconController.init();
 
-        doNothing().when(mLockIcon)
-                .addOnAttachStateChangeListener(
-                        onAttachStateChangeListenerArgumentCaptor.capture());
-        mLockIconController.attach(mLockIcon);
-
-        mOnAttachStateChangeListener = onAttachStateChangeListenerArgumentCaptor.getValue();
+        verify(mLockIcon).addOnAttachStateChangeListener(
+                mOnAttachStateChangeCaptor.capture());
+        mOnAttachStateChangeListener = mOnAttachStateChangeCaptor.getValue();
+        verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+        mStatusBarStateListener = mStateListenerCaptor.getValue();
     }
 
     @Test
@@ -133,23 +135,17 @@
 
     @Test
     public void testVisibility_Dozing() {
-        ArgumentCaptor<StatusBarStateController.StateListener> sBStateListenerCaptor =
-                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
-
-        mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
-        verify(mStatusBarStateController).addCallback(sBStateListenerCaptor.capture());
-
         when(mStatusBarStateController.isDozing()).thenReturn(true);
-        sBStateListenerCaptor.getValue().onDozingChanged(true);
+        mStatusBarStateListener.onDozingChanged(true);
 
         verify(mLockIcon).updateIconVisibility(false);
     }
 
     @Test
     public void testVisibility_doNotShowLockIcon() {
-        when(mKeyguardUpdateMonitor.shouldShowLockIcon()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.canShowLockIcon()).thenReturn(false);
+        mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
 
-        mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
         verify(mLockIcon).setVisibility(View.GONE);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index aa7143e..c07ba72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -61,13 +61,16 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.qs.QSDetailDisplayer;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShelfController;
@@ -80,6 +83,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -202,7 +206,16 @@
     private ScrimController mScrimController;
     @Mock
     private MediaDataManager mMediaDataManager;
-
+    @Mock
+    private FeatureFlags mFeatureFlags;
+    @Mock
+    private ControlsComponent mControlsComponent;
+    @Mock
+    private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private NotificationsQuickSettingsContainer mNotificationContainerParent;
+    @Mock
+    private AmbientState mAmbientState;
     private NotificationPanelViewController mNotificationPanelViewController;
     private View.AccessibilityDelegate mAccessibiltyDelegate;
 
@@ -217,6 +230,8 @@
         when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
         mDisplayMetrics.density = 100;
         when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
+        when(mResources.getDimensionPixelSize(R.dimen.qs_panel_width)).thenReturn(400);
+        when(mResources.getDimensionPixelSize(R.dimen.notification_panel_width)).thenReturn(400);
         when(mView.getContext()).thenReturn(getContext());
         when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
         when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
@@ -235,6 +250,8 @@
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
         when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
+        when(mView.findViewById(R.id.notification_container_parent))
+                .thenReturn(mNotificationContainerParent);
         FlingAnimationUtils.Builder flingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(
                 mDisplayMetrics);
 
@@ -262,6 +279,11 @@
                 .thenReturn(mKeyguardClockSwitchController);
         when(mKeyguardStatusViewComponent.getKeyguardStatusViewController())
                 .thenReturn(mKeyguardStatusViewController);
+        when(mQsFrame.getLayoutParams()).thenReturn(
+                new ViewGroup.LayoutParams(600, 400));
+        when(mNotificationStackScrollLayoutController.getLayoutParams()).thenReturn(
+                new ViewGroup.LayoutParams(600, 400));
+
         mNotificationPanelViewController = new NotificationPanelViewController(mView,
                 mResources,
                 mLayoutInflater,
@@ -282,7 +304,11 @@
                 mAuthController,
                 new QSDetailDisplayer(),
                 mScrimController,
-                mMediaDataManager);
+                mMediaDataManager,
+                mAmbientState,
+                mFeatureFlags,
+                mControlsComponent,
+                mBroadcastDispatcher);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
                 mNotificationShelfController);
@@ -397,6 +423,25 @@
         verify(mStatusBarKeyguardViewManager).showBouncer(true);
     }
 
+    @Test
+    public void testAllChildrenOfNotificationContainer_haveIds() {
+        when(mNotificationContainerParent.getChildCount()).thenReturn(2);
+        when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+        when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
+
+        View view1 = new View(mContext);
+        view1.setId(1);
+        when(mNotificationContainerParent.getChildAt(0)).thenReturn(view1);
+
+        View view2 = mock(View.class);
+        when(mNotificationContainerParent.getChildAt(1)).thenReturn(view2);
+
+        mNotificationPanelViewController.updateResources();
+
+        assertThat(mNotificationContainerParent.getChildAt(0).getId()).isEqualTo(1);
+        assertThat(mNotificationContainerParent.getChildAt(1).getId()).isNotEqualTo(View.NO_ID);
+    }
+
     private void onTouchEvent(MotionEvent ev) {
         mTouchHandler.onTouch(mView, ev);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 3b2e055..21368d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -54,6 +54,8 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.utils.os.FakeHandler;
 
@@ -220,7 +222,8 @@
         mScrimController = new ScrimController(mLightBarController,
                 mDozeParamenters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
                 new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
-                mDockManager, mBlurUtils, mConfigurationController, mFeatureFlags);
+                mDockManager, mBlurUtils, mConfigurationController, mFeatureFlags,
+                new FakeExecutor(new FakeSystemClock()));
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mScrimInFront, mScrimForBubble);
         mScrimController.setAnimatorListener(mAnimatorListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 2c781ba..cae488a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -386,7 +386,6 @@
                 () -> mAssistManager,
                 configurationController,
                 mNotificationShadeWindowController,
-                mLockscreenLockIconController,
                 mDozeParameters,
                 mScrimController,
                 mKeyguardLiftController,
@@ -436,6 +435,7 @@
         // initialized automatically.
         mStatusBar.mNotificationShadeWindowView = mNotificationShadeWindowView;
         mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
+        mStatusBar.mLockscreenLockIconController = mLockscreenLockIconController;
         mStatusBar.mDozeScrimController = mDozeScrimController;
         mStatusBar.mPresenter = mNotificationPresenter;
         mStatusBar.mKeyguardIndicationController = mKeyguardIndicationController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index ebc45f4..c212cf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -125,7 +125,7 @@
         int subId = 5;
         boolean roaming = true;
         mHandler.setMobileDataIndicators(status, qs, type, qsType, in, out, typeDescription,
-                typeDescriptionHtml, description, wide, subId, roaming);
+                typeDescriptionHtml, description, wide, subId, roaming, true);
         waitForCallbacks();
 
         ArgumentCaptor<IconState> statusArg = ArgumentCaptor.forClass(IconState.class);
@@ -143,7 +143,7 @@
         Mockito.verify(mSignalCallback).setMobileDataIndicators(statusArg.capture(),
                 qsArg.capture(), typeIconArg.capture(), qsTypeIconArg.capture(), inArg.capture(),
                 outArg.capture(), typeContentArg.capture(), typeContentHtmlArg.capture(),
-                descArg.capture(), wideArg.capture(), subIdArg.capture(), eq(roaming));
+                descArg.capture(), wideArg.capture(), subIdArg.capture(), eq(roaming), eq(true));
         assertEquals(status, statusArg.getValue());
         assertEquals(qs, qsArg.getValue());
         assertEquals(type, (int) typeIconArg.getValue());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index deabcbe..f8b6383 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -37,6 +37,7 @@
 import android.app.Instrumentation;
 import android.content.Intent;
 import android.net.ConnectivityManager;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -343,6 +344,8 @@
         setConnectivityCommon(networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_WIFI) {
             if (isConnected) {
+                mNetworkCallback.onAvailable(mock(Network.class),
+                        new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
                 mNetworkCallback.onCapabilitiesChanged(
                         mock(Network.class), new NetworkCapabilities(mNetCapabilities));
             } else {
@@ -357,6 +360,8 @@
         setConnectivityCommon(networkType, validated, isConnected);
         if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) {
             if (isConnected) {
+                mNetworkCallback.onAvailable(mock(Network.class),
+                        new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false);
                 mNetworkCallback.onCapabilitiesChanged(
                         mock(Network.class), new NetworkCapabilities(mNetCapabilities));
             } else {
@@ -489,7 +494,7 @@
                     anyInt(),
                     typeIconArg.capture(), dataInArg.capture(), dataOutArg.capture(),
                     any(CharSequence.class), any(CharSequence.class), any(CharSequence.class),
-                    anyBoolean(), anyInt(), anyBoolean());
+                    anyBoolean(), anyInt(), anyBoolean(), anyBoolean());
         IconState iconState = iconArg.getValue();
         int state = SignalDrawable.getState(icon, CellSignalStrength.getNumSignalStrengthLevels(),
                 false);
@@ -523,7 +528,7 @@
                 typeIconArg.capture(),
                 anyInt(), anyBoolean(), anyBoolean(),
                 any(CharSequence.class), any(CharSequence.class), any(),
-                anyBoolean(), anyInt(), eq(roaming));
+                anyBoolean(), anyInt(), eq(roaming), anyBoolean());
         IconState iconState = iconArg.getValue();
         int state = icon == -1 ? 0
                 : SignalDrawable.getState(icon, CellSignalStrength.getNumSignalStrengthLevels(),
@@ -544,7 +549,7 @@
                 typeIconArg.capture(),
                 anyInt(), anyBoolean(), anyBoolean(),
                 any(CharSequence.class), any(CharSequence.class), any(),
-                anyBoolean(), anyInt(), anyBoolean());
+                anyBoolean(), anyInt(), anyBoolean(), anyBoolean());
         IconState iconState = iconArg.getValue();
         int state = SignalDrawable.getState(
                 level, CellSignalStrength.getNumSignalStrengthLevels(), !inet);
@@ -591,7 +596,7 @@
                 dataOutArg.capture(),
                 typeContentDescriptionArg.capture(),
                 typeContentDescriptionHtmlArg.capture(),
-                any(), anyBoolean(), anyInt(), anyBoolean());
+                any(), anyBoolean(), anyInt(), anyBoolean(), anyBoolean());
 
         IconState iconState = iconArg.getValue();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 2b82f66..10166cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -134,7 +134,7 @@
         // Still be on wifi though.
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
-        verifyLastMobileDataIndicators(false, DEFAULT_LEVEL, 0, true);
+        verifyLastMobileDataIndicators(true, DEFAULT_LEVEL, 0, true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index ccc2eb3..76269dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -166,6 +166,7 @@
     private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
 
     private BubblesManager mBubblesManager;
+    // TODO(178618782): Move tests on the controller directly to the shell
     private TestableBubbleController mBubbleController;
     private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
     private NotificationEntryListener mEntryListener;
@@ -221,6 +222,9 @@
 
         mTestableLooper = TestableLooper.get(this);
 
+        // For the purposes of this test, just run everything synchronously
+        ShellExecutor syncExecutor = new SyncExecutor();
+
         mContext.addMockSystemService(FaceManager.class, mFaceManager);
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
@@ -257,8 +261,9 @@
                 mSysUiStateBubblesExpanded =
                         (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0);
 
+        // TODO: Fix
         mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
-        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner);
+        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
 
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
@@ -273,7 +278,7 @@
                 );
 
         when(mFeatureFlagsOldPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
-        when(mShellTaskOrganizer.getExecutor()).thenReturn(new FakeExecutor(new FakeSystemClock()));
+        when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
         mBubbleController = new TestableBubbleController(
                 mContext,
                 mBubbleData,
@@ -286,12 +291,13 @@
                 mBubbleLogger,
                 mShellTaskOrganizer,
                 mPositioner,
-                mock(ShellExecutor.class));
+                syncExecutor,
+                mock(Handler.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController,
+                mBubbleController.getImpl(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
@@ -306,7 +312,8 @@
                 mNotifPipeline,
                 mSysUiState,
                 mFeatureFlagsOldPipeline,
-                mDumpManager);
+                mDumpManager,
+                syncExecutor);
 
         // Get a reference to the BubbleController's entry listener
         verify(mNotificationEntryManager, atLeastOnce())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 00f4e3a..5340ff7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -83,8 +83,6 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.BubbleData;
@@ -203,6 +201,9 @@
 
         mTestableLooper = TestableLooper.get(this);
 
+        // For the purposes of this test, just run everything synchronously
+        ShellExecutor syncExecutor = new SyncExecutor();
+
         when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
 
         mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
@@ -227,7 +228,7 @@
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
         mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
-        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner);
+        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
 
         TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
                 new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
@@ -241,7 +242,7 @@
                         mock(Handler.class)
                 );
         when(mFeatureFlagsNewPipeline.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
-        when(mShellTaskOrganizer.getExecutor()).thenReturn(new FakeExecutor(new FakeSystemClock()));
+        when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
         mBubbleController = new TestableBubbleController(
                 mContext,
                 mBubbleData,
@@ -254,12 +255,13 @@
                 mBubbleLogger,
                 mShellTaskOrganizer,
                 mPositioner,
-                mock(ShellExecutor.class));
+                syncExecutor,
+                mock(Handler.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
         mBubblesManager = new BubblesManager(
                 mContext,
-                mBubbleController,
+                mBubbleController.getImpl(),
                 mNotificationShadeWindowController,
                 mStatusBarStateController,
                 mShadeController,
@@ -274,7 +276,8 @@
                 mNotifPipeline,
                 mSysUiState,
                 mFeatureFlagsNewPipeline,
-                mDumpManager);
+                mDumpManager,
+                syncExecutor);
         mBubblesManager.addNotifCallback(mNotifCallback);
 
         // Get a reference to the BubbleController's entry listener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java
new file mode 100644
index 0000000..d40eecf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/SyncExecutor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 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.wmshell;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+/**
+ * And executor that just executes everything synchronously.  To be removed once we move the
+ * tests of shell behavior over to the shell.
+ */
+public class SyncExecutor implements ShellExecutor {
+    @Override
+    public void execute(Runnable runnable) {
+        runnable.run();
+    }
+
+    @Override
+    public void executeDelayed(Runnable runnable, long delayMillis) {
+        runnable.run();
+    }
+
+    @Override
+    public void removeAllCallbacks() {
+    }
+
+    @Override
+    public void removeCallbacks(Runnable runnable) {
+    }
+
+    @Override
+    public boolean hasCallback(Runnable runnable) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 3f918e8..cdf47b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -49,10 +49,11 @@
             BubbleLogger bubbleLogger,
             ShellTaskOrganizer shellTaskOrganizer,
             BubblePositioner positioner,
-            ShellExecutor shellMainExecutor) {
+            ShellExecutor shellMainExecutor,
+            Handler shellMainHandler) {
         super(context, data, Runnable::run, floatingContentCoordinator, dataRepository,
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                bubbleLogger, shellTaskOrganizer, positioner, shellMainExecutor);
+                bubbleLogger, shellTaskOrganizer, positioner, shellMainExecutor, shellMainHandler);
         setInflateSynchronously(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 8b86403..79641db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -41,7 +40,6 @@
 import com.android.wm.shell.onehanded.OneHandedGestureHandler;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -51,6 +49,12 @@
 
 import java.util.Optional;
 
+/**
+ * Tests for {@link WMShell}.
+ *
+ * Build/Install/Run:
+ *  atest SystemUITests:WMShellTest
+ */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class WMShellTest extends SysuiTestCase {
@@ -63,7 +67,6 @@
     @Mock ScreenLifecycle mScreenLifecycle;
     @Mock SysUiState mSysUiState;
     @Mock Pip mPip;
-    @Mock PipTouchHandler mPipTouchHandler;
     @Mock LegacySplitScreen mLegacySplitScreen;
     @Mock OneHanded mOneHanded;
     @Mock HideDisplayCutout mHideDisplayCutout;
@@ -80,8 +83,6 @@
                 Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
                 mKeyguardUpdateMonitor, mNavigationModeController,
                 mScreenLifecycle, mSysUiState, mProtoTracer, mSysUiMainExecutor);
-
-        when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
     }
 
     @Test
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index ae024ff..5dd271c 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -260,6 +260,10 @@
     // Package: android
     NOTE_ADB_WIFI_ACTIVE = 62;
 
+    // Notify the user a carrier suggestion is available to get IMSI exemption.
+    // Package: android
+    NOTE_CARRIER_SUGGESTION_AVAILABLE = 63;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/Android.bp b/services/Android.bp
index b11a2e8..61591c2 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -28,6 +28,8 @@
         ":services.profcollect-sources",
         ":services.restrictions-sources",
         ":services.searchui-sources",
+        ":services.smartspace-sources",
+        ":services.speech-sources",
         ":services.startop.iorap-sources",
         ":services.systemcaptions-sources",
         ":services.translation-sources",
@@ -35,6 +37,7 @@
         ":services.usb-sources",
         ":services.voiceinteraction-sources",
         ":services.wifi-sources",
+        ":service-media-s-sources", // TODO (b/177640454)
         ":service-permission-sources",
         ":service-statsd-sources",
     ],
@@ -75,6 +78,8 @@
         "services.profcollect",
         "services.restrictions",
         "services.searchui",
+        "services.smartspace",
+        "services.speech",
         "services.startop",
         "services.systemcaptions",
         "services.translation",
@@ -139,7 +144,7 @@
         last_released: {
             api_file: ":android.api.system-server.latest",
             removed_api_file: ":removed.api.system-server.latest",
-            baseline_file: ":system-server-api-incompatibilities-with-last-released"
+            baseline_file: ":android-incompatibilities.api.system-server.latest"
         },
         api_lint: {
             enabled: true,
diff --git a/services/OWNERS b/services/OWNERS
index f1fa542..03e0807 100644
--- a/services/OWNERS
+++ b/services/OWNERS
@@ -2,3 +2,5 @@
 
 # art-team@ manages the system server profile
 per-file art-profile* = calin@google.com, mathieuc@google.com, ngeoffray@google.com
+
+per-file java/com/android/server/* = toddke@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 8f093c7..065e2bb 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1113,7 +1113,7 @@
         try {
             final IBinder overlayWindowToken = new Binder();
             mWindowManagerService.addWindowToken(overlayWindowToken, TYPE_ACCESSIBILITY_OVERLAY,
-                    displayId);
+                    displayId, null /* options */);
             synchronized (mLock) {
                 mOverlayWindowTokens.put(displayId, overlayWindowToken);
             }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9aa0aed..ea1473e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -166,8 +166,6 @@
     //       their capabilities are ready.
     private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000;
 
-    static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction";
-    static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction";
     private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
         "registerUiTestAutomationService";
 
@@ -748,9 +746,7 @@
      */
     @Override
     public void registerSystemAction(RemoteAction action, int actionId) {
-        mSecurityPolicy.enforceCallerIsRecentsOrHasPermission(
-                Manifest.permission.MANAGE_ACCESSIBILITY,
-                FUNCTION_REGISTER_SYSTEM_ACTION);
+        mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
         getSystemActionPerformer().registerSystemAction(actionId, action);
     }
 
@@ -761,9 +757,7 @@
      */
     @Override
     public void unregisterSystemAction(int actionId) {
-        mSecurityPolicy.enforceCallerIsRecentsOrHasPermission(
-                Manifest.permission.MANAGE_ACCESSIBILITY,
-                FUNCTION_UNREGISTER_SYSTEM_ACTION);
+        mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
         getSystemActionPerformer().unregisterSystemAction(actionId);
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index d766431..bef6d3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -38,8 +38,6 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.server.LocalServices;
-import com.android.server.wm.ActivityTaskManagerInternal;
 
 import libcore.util.EmptyArray;
 
@@ -88,7 +86,6 @@
 
     private final AccessibilityUserManager mAccessibilityUserManager;
     private AccessibilityWindowManager mAccessibilityWindowManager;
-    private final ActivityTaskManagerInternal mAtmInternal;
 
     /**
      * Constructor for AccessibilityManagerService.
@@ -100,7 +97,6 @@
         mPackageManager = mContext.getPackageManager();
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
     }
 
     /**
@@ -572,13 +568,4 @@
                     + permission);
         }
     }
-
-    /**
-     * Enforcing permission check to IPC caller or grant it if it's recents.
-     *
-     * @param permission The permission to check
-     */
-    public void enforceCallerIsRecentsOrHasPermission(@NonNull String permission, String func) {
-        mAtmInternal.enforceCallerIsRecentsOrHasPermission(permission, func);
-    }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
index 7824fd9..9c54100 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTapAndHold.java
@@ -44,7 +44,7 @@
 
     @Override
     protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mCompletedTapCount + 1 == mTargetFingerCount) {
+        if (mCompletedTapCount + 1 == mTargetTapCount) {
             // Calling super.onUp  would complete the multi-tap version of this.
             cancelGesture(event, rawEvent, policyFlags);
         } else {
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index f9f064c..e4a86c3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -55,7 +55,6 @@
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
@@ -66,9 +65,8 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
 import android.graphics.Point;
-import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -119,7 +117,6 @@
 import com.android.internal.widget.IRemoteViewsFactory;
 import com.android.server.LocalServices;
 import com.android.server.WidgetBackupProvider;
-import com.android.server.policy.IconUtilities;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -253,8 +250,6 @@
     private boolean mSafeMode;
     private int mMaxWidgetBitmapMemory;
 
-    private IconUtilities mIconUtilities;
-
     AppWidgetServiceImpl(Context context) {
         mContext = context;
     }
@@ -271,7 +266,6 @@
         mCallbackHandler = new CallbackHandler(mContext.getMainLooper());
         mBackupRestoreController = new BackupRestoreController();
         mSecurityPolicy = new SecurityPolicy();
-        mIconUtilities = new IconUtilities(mContext);
 
         computeMaximumWidgetBitmapMemory();
         registerBroadcastReceiver();
@@ -578,44 +572,6 @@
         }
     }
 
-    private Bitmap createMaskedWidgetBitmap(String providerPackage, int providerUserId) {
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            // Load the unbadged application icon and pass it to the widget to appear on
-            // the masked view.
-            Context userContext = mContext.createPackageContextAsUser(providerPackage, 0,
-                    UserHandle.of(providerUserId));
-            PackageManager pm = userContext.getPackageManager();
-            Drawable icon = pm.getApplicationInfo(providerPackage, 0).loadUnbadgedIcon(pm).mutate();
-            // Create a bitmap of the icon which is what the widget's remoteview requires.
-            icon.setColorFilter(mIconUtilities.getDisabledColorFilter());
-            return mIconUtilities.createIconBitmap(icon);
-        } catch (NameNotFoundException e) {
-            Slog.e(TAG, "Fail to get application icon", e);
-            // Provider package removed, no need to mask its views as its state will be
-            // purged very soon.
-            return null;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private RemoteViews createMaskedWidgetRemoteViews(Bitmap icon, boolean showBadge,
-            PendingIntent onClickIntent) {
-        RemoteViews views = new RemoteViews(mContext.getPackageName(),
-                R.layout.work_widget_mask_view);
-        if (icon != null) {
-            views.setImageViewBitmap(R.id.work_widget_app_icon, icon);
-        }
-        if (!showBadge) {
-            views.setViewVisibility(R.id.work_widget_badge_icon, View.INVISIBLE);
-        }
-        if (onClickIntent != null) {
-            views.setOnClickPendingIntent(R.id.work_widget_mask_frame, onClickIntent);
-        }
-        return views;
-    }
-
     /**
      * Mask the target widget belonging to the specified provider, or all active widgets
      * of the provider if target widget == null.
@@ -625,59 +581,63 @@
         if (widgetCount == 0) {
             return;
         }
-        final String providerPackage = provider.id.componentName.getPackageName();
-        final int providerUserId = provider.getUserId();
-        Bitmap iconBitmap = createMaskedWidgetBitmap(providerPackage, providerUserId);
-        if (iconBitmap == null) {
-            return;
-        }
-        final boolean showBadge;
-        final Intent onClickIntent;
+        RemoteViews views = new RemoteViews(mContext.getPackageName(),
+                R.layout.work_widget_mask_view);
+        ApplicationInfo appInfo = provider.info.providerInfo.applicationInfo;
+        final int appUserId = provider.getUserId();
+        boolean showBadge;
+
         final long identity = Binder.clearCallingIdentity();
         try {
+            final Intent onClickIntent;
+
             if (provider.maskedBySuspendedPackage) {
-                showBadge = mUserManager.hasBadge(providerUserId);
+                showBadge = mUserManager.hasBadge(appUserId);
                 final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
-                        providerPackage, providerUserId);
+                        appInfo.packageName, appUserId);
                 if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
                     onClickIntent = mDevicePolicyManagerInternal.createShowAdminSupportIntent(
-                            providerUserId, true);
+                            appUserId, true);
                 } else {
                     final SuspendDialogInfo dialogInfo =
-                            mPackageManagerInternal.getSuspendedDialogInfo(providerPackage,
-                                    suspendingPackage, providerUserId);
+                            mPackageManagerInternal.getSuspendedDialogInfo(
+                                    appInfo.packageName, suspendingPackage, appUserId);
                     // onUnsuspend is null because we don't want to start any activity on
                     // unsuspending from a suspended widget.
                     onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(
-                            providerPackage, suspendingPackage, dialogInfo, null, null,
-                            providerUserId);
+                            appInfo.packageName, suspendingPackage, dialogInfo, null, null,
+                            appUserId);
                 }
             } else if (provider.maskedByQuietProfile) {
                 showBadge = true;
-                onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(
-                        providerUserId);
+                onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(appUserId);
             } else /* provider.maskedByLockedProfile */ {
                 showBadge = true;
-                onClickIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null,
-                        providerUserId);
+                onClickIntent = mKeyguardManager
+                        .createConfirmDeviceCredentialIntent(null, null, appUserId);
                 if (onClickIntent != null) {
-                    onClickIntent.setFlags(FLAG_ACTIVITY_NEW_TASK
-                            | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                    onClickIntent.setFlags(
+                            FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                 }
             }
+
+            if (onClickIntent != null) {
+                views.setOnClickPendingIntent(R.id.work_widget_mask_frame,
+                        PendingIntent.getActivity(mContext, 0, onClickIntent,
+                                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+            }
+
+            Icon icon = appInfo.icon != 0
+                    ? Icon.createWithResource(appInfo.packageName, appInfo.icon)
+                    : Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+            views.setImageViewIcon(R.id.work_widget_app_icon, icon);
+            if (!showBadge) {
+                views.setViewVisibility(R.id.work_widget_badge_icon, View.INVISIBLE);
+            }
+
             for (int j = 0; j < widgetCount; j++) {
                 Widget widget = provider.widgets.get(j);
                 if (targetWidget != null && targetWidget != widget) continue;
-                PendingIntent intent = null;
-                if (onClickIntent != null) {
-                    // Rare informational activity click is okay being
-                    // immutable; the tradeoff is more security in exchange for
-                    // losing bounds-based window animations
-                    intent = PendingIntent.getActivity(mContext, widget.appWidgetId,
-                            onClickIntent,
-                            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-                }
-                RemoteViews views = createMaskedWidgetRemoteViews(iconBitmap, showBadge, intent);
                 if (widget.replaceWithMaskedViewsLocked(views)) {
                     scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
                 }
diff --git a/services/backup/OWNERS b/services/backup/OWNERS
index 3c5268c..ba2a63a 100644
--- a/services/backup/OWNERS
+++ b/services/backup/OWNERS
@@ -3,6 +3,7 @@
 aabhinav@google.com
 bryanmawhinney@google.com
 jstemmer@google.com
+millmore@google.com
 nathch@google.com
 niagra@google.com
 niamhfw@google.com
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 04e08ae..55e3ef2 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -926,7 +926,7 @@
         mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
                 deviceProfile, FgThread.getExecutor(), desc -> {
                         try {
-                            result.complete(requireNonNull(desc));
+                            result.complete(desc);
                         } catch (Exception e) {
                             result.completeExceptionally(e);
                         }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 2f3ad19..37d2cdc 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -87,6 +87,7 @@
         ":storaged_aidl",
         ":vold_aidl",
         ":platform-compat-config",
+        ":platform-compat-overrides",
         ":display-device-config",
         ":cec-config",
         ":device-state-config",
@@ -99,10 +100,10 @@
     libs: [
         "services.net",
         "android.hardware.light-V2.0-java",
-        "android.hardware.gnss-java",
-        "android.hardware.power-java",
+        "android.hardware.gnss-V1-java",
+        "android.hardware.power-V1-java",
         "android.hardware.power-V1.0-java",
-        "android.hardware.vibrator-java",
+        "android.hardware.vibrator-V1-java",
         "android.net.ipsec.ike.stubs.module_lib",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
@@ -127,22 +128,22 @@
         "android.hardware.health-V1.0-java",
         "android.hardware.health-V2.0-java",
         "android.hardware.health-V2.1-java",
-        "android.hardware.light-java",
+        "android.hardware.light-V1-java",
         "android.hardware.tv.cec-V1.0-java",
         "android.hardware.weaver-V1.0-java",
         "android.hardware.biometrics.face-V1.1-java",
-        "android.hardware.biometrics.face-java",
+        "android.hardware.biometrics.face-V1-java",
         "android.hardware.biometrics.fingerprint-V2.3-java",
-        "android.hardware.biometrics.fingerprint-java",
+        "android.hardware.biometrics.fingerprint-V1-java",
         "android.hardware.oemlock-V1.0-java",
         "android.hardware.configstore-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
-        "android.hardware.rebootescrow-java",
+        "android.hardware.rebootescrow-V1-java",
         "android.hardware.soundtrigger-V2.3-java",
-        "android.hardware.power.stats-java",
+        "android.hardware.power.stats-V1-java",
         "android.hidl.manager-V1.2-java",
         "capture_state_listener-aidl-java",
-        "dnsresolver_aidl_interface-java",
+        "dnsresolver_aidl_interface-V7-java",
         "icu4j_calendar_astronomer",
         "netd-client",
         "overlayable_policy_aidl-java",
@@ -157,9 +158,9 @@
     srcs: [":services.core.unboosted"],
     tools: ["lockedregioncodeinjection"],
     cmd: "$(location lockedregioncodeinjection) " +
-        "  --targets \"Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/wm/WindowManagerGlobalLock;\" " +
-        "  --pre \"com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection\" " +
-        "  --post \"com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection\" " +
+        "  --targets \"Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/am/ActivityManagerGlobalLock;,Lcom/android/server/wm/WindowManagerGlobalLock;\" " +
+        "  --pre \"com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/am/ActivityManagerService.boostPriorityForProcLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection\" " +
+        "  --post \"com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/am/ActivityManagerService.resetPriorityAfterProcLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection\" " +
         "  -o $(out) " +
         "  -i $(in)",
     out: ["services.core.priorityboosted.jar"],
diff --git a/services/core/java/android/content/pm/OWNERS b/services/core/java/android/content/pm/OWNERS
new file mode 100644
index 0000000..5eed0b5
--- /dev/null
+++ b/services/core/java/android/content/pm/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/content/pm/OWNERS
\ No newline at end of file
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index dad8bd8..6886cde 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -997,28 +997,6 @@
     public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
 
     /**
-     * Register to listen for loading progress of an installed package.
-     * @param packageName The name of the installed package
-     * @param callback To loading reporting progress
-     * @param userId The user under which to check.
-     * @return Whether the registration was successful. It can fail if the package has not been
-     *          installed yet.
-     */
-    public abstract boolean registerInstalledLoadingProgressCallback(@NonNull String packageName,
-            @NonNull InstalledLoadingProgressCallback callback, int userId);
-
-    /**
-     * Unregister to stop listening to loading progress of an installed package
-     * @param packageName The name of the installed package
-     * @param callback To unregister
-     * @return True if the callback is removed from registered callback list. False is the callback
-     *         does not exist on the registered callback list, which can happen if the callback has
-     *         already been unregistered.
-     */
-    public abstract boolean unregisterInstalledLoadingProgressCallback(@NonNull String packageName,
-            @NonNull InstalledLoadingProgressCallback callback);
-
-    /**
      * Returns the string representation of a known package. For example,
      * {@link #PACKAGE_SETUP_WIZARD} is represented by the string Setup Wizard.
      *
@@ -1137,7 +1115,8 @@
      */
     public abstract void requestChecksums(@NonNull String packageName, boolean includeSplits,
             @Checksum.Type int optional, @Checksum.Type int required,
-            @Nullable List trustedInstallers, @NonNull IntentSender statusReceiver, int userId,
+            @Nullable List trustedInstallers,
+            @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId,
             @NonNull Executor executor, @NonNull Handler handler);
 
     /**
diff --git a/services/core/java/android/os/OWNERS b/services/core/java/android/os/OWNERS
new file mode 100644
index 0000000..d0a2daf
--- /dev/null
+++ b/services/core/java/android/os/OWNERS
@@ -0,0 +1 @@
+per-file BatteryStats* = file:/BATTERY_STATS_OWNERS
diff --git a/services/core/java/android/power/PowerStatsInternal.java b/services/core/java/android/power/PowerStatsInternal.java
index f7417da..40107a5 100644
--- a/services/core/java/android/power/PowerStatsInternal.java
+++ b/services/core/java/android/power/PowerStatsInternal.java
@@ -18,8 +18,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.power.stats.Channel;
 import android.hardware.power.stats.EnergyConsumer;
 import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyMeasurement;
+import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.StateResidencyResult;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -51,4 +55,45 @@
     @NonNull
     public abstract CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
             int[] energyConsumerIds);
+
+    /**
+     * Returns the power entity info for all available {@link PowerEntity}
+     *
+     * @return List of available {@link PowerEntity}
+     */
+    public abstract PowerEntity[] getPowerEntityInfo();
+
+    /**
+     * Returns a CompletableFuture that will get a {@link StateResidencyResult} array for the
+     * available requested power entities.
+     *
+     * @param powerEntityIds Array of {@link PowerEntity.id} for which state residency is being
+     *                          requested.
+     *
+     * @return A Future containing a list of {@link StateResidencyResult} objects containing state
+     *         residency results for all listed {@link PowerEntity.id}.
+     */
+    @NonNull
+    public abstract CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
+            int[] powerEntityIds);
+
+    /**
+     * Returns the channel info for all available {@link Channel}
+     *
+     * @return List of available {@link Channel}
+     */
+    public abstract Channel[] getEnergyMeterInfo();
+
+    /**
+     * Returns a CompletableFuture that will get a {@link EnergyMeasurement} array for the
+     * available requested channels.
+     *
+     * @param channelIds Array of {@link Channel.id} for accumulated energy is being requested.
+     *
+     * @return A Future containing a list of {@link EnergyMeasurement} objects containing
+     *         accumulated energy measurements for all listed {@link Channel.id}.
+     */
+    @NonNull
+    public abstract CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
+            int[] channelIds);
 }
diff --git a/services/core/java/com/android/server/BundleUtils.java b/services/core/java/com/android/server/BundleUtils.java
new file mode 100644
index 0000000..20ebe29
--- /dev/null
+++ b/services/core/java/com/android/server/BundleUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+/**
+ * Utility methods for handling {@link Bundle}.
+ *
+ */
+public final class BundleUtils {
+    private BundleUtils() {
+    }
+
+    /**
+     * Returns true if {@code in} is null or empty.
+     */
+    public static boolean isEmpty(@Nullable Bundle in) {
+        return (in == null) || (in.size() == 0);
+    }
+
+    /**
+     * Creates a copy of the {@code in} Bundle.  If {@code in} is null, it'll return an empty
+     * bundle.
+     *
+     * <p>The resulting {@link Bundle} is always writable. (i.e. it won't return
+     * {@link Bundle#EMPTY})
+     */
+    public static @NonNull Bundle clone(@Nullable Bundle in) {
+        return (in != null) ? new Bundle(in) : new Bundle();
+    }
+
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b6232a0..3933e37 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -120,6 +120,7 @@
 import android.net.NetworkTestResultParcelable;
 import android.net.NetworkUtils;
 import android.net.NetworkWatchlistManager;
+import android.net.OemNetworkPreferences;
 import android.net.PrivateDnsConfigParcel;
 import android.net.ProxyInfo;
 import android.net.QosCallbackException;
@@ -132,6 +133,7 @@
 import android.net.TetheringManager;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
+import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnService;
@@ -184,7 +186,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.AsyncChannel;
@@ -216,14 +217,13 @@
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.utils.PriorityDump;
 
-import com.google.android.collect.Lists;
-
 import libcore.io.IoUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.net.Inet4Address;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -282,15 +282,18 @@
     // connect anyway?" dialog after the user selects a network that doesn't validate.
     private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
 
-    // Default to 30s linger time-out. Modifiable only for testing.
+    // Default to 30s linger time-out, and 5s for nascent network. Modifiable only for testing.
     private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
     private static final int DEFAULT_LINGER_DELAY_MS = 30_000;
+    private static final int DEFAULT_NASCENT_DELAY_MS = 5_000;
 
     // The maximum number of network request allowed per uid before an exception is thrown.
     private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
 
     @VisibleForTesting
     protected int mLingerDelayMs;  // Can't be final, or test subclass constructors can't change it.
+    @VisibleForTesting
+    protected int mNascentDelayMs;
 
     // How long to delay to removal of a pending intent based request.
     // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
@@ -325,6 +328,8 @@
     private boolean mRestrictBackground;
 
     private final Context mContext;
+    // The Context is created for UserHandle.ALL.
+    private final Context mUserAllContext;
     private final Dependencies mDeps;
     // 0 is full bad, 100 is full good
     private int mDefaultInetConditionPublished = 0;
@@ -990,6 +995,15 @@
         }
 
         /**
+         * Gets the UID that owns a socket connection. Needed because opening SOCK_DIAG sockets
+         * requires CAP_NET_ADMIN, which the unit tests do not have.
+         */
+        public int getConnectionOwnerUid(int protocol, InetSocketAddress local,
+                InetSocketAddress remote) {
+            return InetDiagMessage.getConnectionOwnerUid(protocol, local, remote);
+        }
+
+        /**
          * @see MultinetworkPolicyTracker
          */
         public MultinetworkPolicyTracker makeMultinetworkPolicyTracker(
@@ -1021,11 +1035,13 @@
         mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID);
 
         mMetricsLog = logger;
-        mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST);
         mNetworkRanker = new NetworkRanker();
-        NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest, new Binder());
-        mNetworkRequests.put(mDefaultRequest, defaultNRI);
-        mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
+        final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport(
+                -1, NetworkRequest.Type.REQUEST);
+        mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder());
+        mNetworkRequests.put(defaultInternetRequest, mDefaultRequest);
+        mDefaultNetworkRequests.add(mDefaultRequest);
+        mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest);
 
         mDefaultMobileDataRequest = createDefaultInternetRequestForTransport(
                 NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
@@ -1051,6 +1067,8 @@
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000);
 
         mLingerDelayMs = mSystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS);
+        // TODO: Consider making the timer customizable.
+        mNascentDelayMs = DEFAULT_NASCENT_DELAY_MS;
 
         mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService");
         mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService");
@@ -1160,8 +1178,8 @@
         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
         intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
 
-        final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
-        userAllContext.registerReceiver(
+        mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
+        mUserAllContext.registerReceiver(
                 mIntentReceiver,
                 intentFilter,
                 null /* broadcastPermission */,
@@ -1177,7 +1195,7 @@
         intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter.addDataScheme("package");
-        userAllContext.registerReceiver(
+        mUserAllContext.registerReceiver(
                 mIntentReceiver,
                 intentFilter,
                 null /* broadcastPermission */,
@@ -1186,7 +1204,7 @@
         // Listen to lockdown VPN reset.
         intentFilter = new IntentFilter();
         intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET);
-        userAllContext.registerReceiver(
+        mUserAllContext.registerReceiver(
                 mIntentReceiver, intentFilter, NETWORK_STACK, mHandler);
 
         mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS);
@@ -1215,6 +1233,14 @@
 
         mDnsManager = new DnsManager(mContext, mDnsResolver);
         registerPrivateDnsSettingsCallbacks();
+
+        mNoServiceNetwork =  new NetworkAgentInfo(null,
+                new Network(NO_SERVICE_NET_ID),
+                new NetworkInfo(TYPE_NONE, 0, "", ""),
+                new LinkProperties(), new NetworkCapabilities(), 0, mContext,
+                null, new NetworkAgentConfig(), this, null,
+                null, null, 0, INVALID_UID,
+                mQosCallbackTracker);
     }
 
     private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
@@ -1327,31 +1353,6 @@
         return mNextNetworkRequestId++;
     }
 
-    private NetworkState getFilteredNetworkState(int networkType, int uid) {
-        if (mLegacyTypeTracker.isTypeSupported(networkType)) {
-            final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
-            final NetworkState state;
-            if (nai != null) {
-                state = nai.getNetworkState();
-                state.networkInfo.setType(networkType);
-            } else {
-                final NetworkInfo info = new NetworkInfo(networkType, 0,
-                        getNetworkTypeName(networkType), "");
-                info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
-                info.setIsAvailable(true);
-                final NetworkCapabilities capabilities = new NetworkCapabilities();
-                capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING,
-                        !info.isRoaming());
-                state = new NetworkState(info, new LinkProperties(), capabilities,
-                        null, null, null);
-            }
-            filterNetworkStateForUid(state, uid, false);
-            return state;
-        } else {
-            return NetworkState.EMPTY;
-        }
-    }
-
     @VisibleForTesting
     protected NetworkAgentInfo getNetworkAgentInfoForNetwork(Network network) {
         if (network == null) {
@@ -1389,7 +1390,7 @@
     }
 
     private NetworkState getUnfilteredActiveNetworkState(int uid) {
-        NetworkAgentInfo nai = getDefaultNetwork();
+        NetworkAgentInfo nai = getDefaultNetworkForUid(uid);
 
         final Network[] networks = getVpnUnderlyingNetworks(uid);
         if (networks != null) {
@@ -1456,13 +1457,24 @@
             return;
         }
         final String action = blocked ? "BLOCKED" : "UNBLOCKED";
-        final NetworkRequest satisfiedRequest = nri.getSatisfiedRequest();
-        final int requestId =  satisfiedRequest != null
-                ? satisfiedRequest.requestId : nri.mRequests.get(0).requestId;
+        final int requestId = nri.getActiveRequest() != null
+                ? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId;
         mNetworkInfoBlockingLogs.log(String.format(
                 "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId()));
     }
 
+    private void filterNetworkInfo(@NonNull NetworkInfo networkInfo,
+            @NonNull NetworkCapabilities nc, int uid, boolean ignoreBlocked) {
+        if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
+            networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
+        }
+        synchronized (mVpns) {
+            if (mLockdownTracker != null) {
+                mLockdownTracker.augmentNetworkInfo(networkInfo);
+            }
+        }
+    }
+
     /**
      * Apply any relevant filters to {@link NetworkState} for the given UID. For
      * example, this may mark the network as {@link DetailedState#BLOCKED} based
@@ -1470,16 +1482,7 @@
      */
     private void filterNetworkStateForUid(NetworkState state, int uid, boolean ignoreBlocked) {
         if (state == null || state.networkInfo == null || state.linkProperties == null) return;
-
-        if (isNetworkWithCapabilitiesBlocked(state.networkCapabilities, uid,
-                ignoreBlocked)) {
-            state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
-        }
-        synchronized (mVpns) {
-            if (mLockdownTracker != null) {
-                mLockdownTracker.augmentNetworkInfo(state.networkInfo);
-            }
-        }
+        filterNetworkInfo(state.networkInfo, state.networkCapabilities, uid, ignoreBlocked);
     }
 
     /**
@@ -1520,7 +1523,7 @@
             }
         }
 
-        NetworkAgentInfo nai = getDefaultNetwork();
+        NetworkAgentInfo nai = getDefaultNetworkForUid(uid);
         if (nai == null || isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid,
                 ignoreBlocked)) {
             return null;
@@ -1544,6 +1547,27 @@
         return state.networkInfo;
     }
 
+    private NetworkInfo getFilteredNetworkInfo(int networkType, int uid) {
+        if (!mLegacyTypeTracker.isTypeSupported(networkType)) {
+            return null;
+        }
+        final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+        final NetworkInfo info;
+        final NetworkCapabilities nc;
+        if (nai != null) {
+            info = new NetworkInfo(nai.networkInfo);
+            info.setType(networkType);
+            nc = nai.networkCapabilities;
+        } else {
+            info = new NetworkInfo(networkType, 0, getNetworkTypeName(networkType), "");
+            info.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
+            info.setIsAvailable(true);
+            nc = new NetworkCapabilities();
+        }
+        filterNetworkInfo(info, nc, uid, false);
+        return info;
+    }
+
     @Override
     public NetworkInfo getNetworkInfo(int networkType) {
         enforceAccessPermission();
@@ -1558,8 +1582,7 @@
                 return state.networkInfo;
             }
         }
-        final NetworkState state = getFilteredNetworkState(networkType, uid);
-        return state.networkInfo;
+        return getFilteredNetworkInfo(networkType, uid);
     }
 
     @Override
@@ -1578,7 +1601,7 @@
     @Override
     public NetworkInfo[] getAllNetworkInfo() {
         enforceAccessPermission();
-        final ArrayList<NetworkInfo> result = Lists.newArrayList();
+        final ArrayList<NetworkInfo> result = new ArrayList<>();
         for (int networkType = 0; networkType <= ConnectivityManager.MAX_NETWORK_TYPE;
                 networkType++) {
             NetworkInfo info = getNetworkInfo(networkType);
@@ -1592,10 +1615,16 @@
     @Override
     public Network getNetworkForType(int networkType) {
         enforceAccessPermission();
+        if (!mLegacyTypeTracker.isTypeSupported(networkType)) {
+            return null;
+        }
+        final NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
+        if (nai == null) {
+            return null;
+        }
         final int uid = mDeps.getCallingUid();
-        NetworkState state = getFilteredNetworkState(networkType, uid);
-        if (!isNetworkWithCapabilitiesBlocked(state.networkCapabilities, uid, false)) {
-            return state.network;
+        if (!isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, false)) {
+            return nai.network;
         }
         return null;
     }
@@ -1633,21 +1662,28 @@
 
         HashMap<Network, NetworkCapabilities> result = new HashMap<>();
 
-        NetworkAgentInfo nai = getDefaultNetwork();
-        NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
-        if (nc != null) {
-            result.put(
-                    nai.network,
-                    createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                            nc, mDeps.getCallingUid(), callingPackageName));
+        for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
+            if (!nri.isBeingSatisfied()) {
+                continue;
+            }
+            final NetworkAgentInfo nai = nri.getSatisfier();
+            final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
+            if (null != nc
+                    && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                    && !result.containsKey(nai.network)) {
+                result.put(
+                        nai.network,
+                        createWithLocationInfoSanitizedIfNecessaryWhenParceled(
+                                nc, mDeps.getCallingUid(), callingPackageName));
+            }
         }
 
         // No need to check mLockdownEnabled. If it's true, getVpnUnderlyingNetworks returns null.
         final Network[] networks = getVpnUnderlyingNetworks(Binder.getCallingUid());
-        if (networks != null) {
-            for (Network network : networks) {
-                nc = getNetworkCapabilitiesInternal(network);
-                if (nc != null) {
+        if (null != networks) {
+            for (final Network network : networks) {
+                final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network);
+                if (null != nc) {
                     result.put(
                             network,
                             createWithLocationInfoSanitizedIfNecessaryWhenParceled(
@@ -1669,9 +1705,7 @@
 
     /**
      * Return LinkProperties for the active (i.e., connected) default
-     * network interface.  It is assumed that at most one default network
-     * is active at a time. If more than one is active, it is indeterminate
-     * which will be returned.
+     * network interface for the calling uid.
      * @return the ip properties for the active network, or {@code null} if
      * none is active
      */
@@ -1846,7 +1880,7 @@
         // This contains IMSI details, so make sure the caller is privileged.
         NetworkStack.checkNetworkStackPermission(mContext);
 
-        final ArrayList<NetworkState> result = Lists.newArrayList();
+        final ArrayList<NetworkState> result = new ArrayList<>();
         for (Network network : getAllNetworks()) {
             final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
             if (nai != null) {
@@ -2002,7 +2036,7 @@
                 mHandler.sendMessage(mHandler.obtainMessage(
                         EVENT_PRIVATE_DNS_VALIDATION_UPDATE,
                         new PrivateDnsValidationUpdate(netId,
-                                InetAddress.parseNumericAddress(ipAddress),
+                                InetAddresses.parseNumericAddress(ipAddress),
                                 hostname, validated)));
             } catch (IllegalArgumentException e) {
                 loge("Error parsing ip address in validation event");
@@ -2020,7 +2054,7 @@
             // TODO: Move the Dns Event to NetworkMonitor. NetdEventListenerService only allow one
             // callback from each caller type. Need to re-factor NetdEventListenerService to allow
             // multiple NetworkMonitor registrants.
-            if (nai != null && nai.satisfies(mDefaultRequest)) {
+            if (nai != null && nai.satisfies(mDefaultRequest.mRequests.get(0))) {
                 nai.networkMonitor().notifyDnsResponse(returnCode);
             }
         }
@@ -2114,8 +2148,8 @@
 
     private boolean isUidBlockedByRules(int uid, int uidRules, boolean isNetworkMetered,
             boolean isBackgroundRestricted) {
-        return NetworkPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules,
-                isNetworkMetered, isBackgroundRestricted);
+        return mPolicyManager.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered,
+                isBackgroundRestricted);
     }
 
     /**
@@ -2350,7 +2384,7 @@
                 intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
             }
             try {
-                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL, options);
+                mUserAllContext.sendStickyBroadcast(intent, options);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2698,9 +2732,9 @@
                 pw.println(nai.requestAt(i).toString());
             }
             pw.decreaseIndent();
-            pw.println("Lingered:");
+            pw.println("Inactivity Timers:");
             pw.increaseIndent();
-            nai.dumpLingerTimers(pw);
+            nai.dumpInactivityTimers(pw);
             pw.decreaseIndent();
             pw.decreaseIndent();
         }
@@ -2728,7 +2762,7 @@
     @VisibleForTesting
     NetworkRequestInfo[] requestsSortedById() {
         NetworkRequestInfo[] requests = new NetworkRequestInfo[0];
-        requests = mNetworkRequests.values().toArray(requests);
+        requests = getNrisFromGlobalRequests().toArray(requests);
         // Sort the array based off the NRI containing the min requestId in its requests.
         Arrays.sort(requests,
                 Comparator.comparingInt(nri -> Collections.min(nri.mRequests,
@@ -3295,27 +3329,27 @@
     }
 
     /**
-     * Updates the linger state from the network requests inside the NAI.
+     * Updates the inactivity state from the network requests inside the NAI.
      * @param nai the agent info to update
      * @param now the timestamp of the event causing this update
-     * @return whether the network was lingered as a result of this update
+     * @return whether the network was inactive as a result of this update
      */
-    private boolean updateLingerState(@NonNull final NetworkAgentInfo nai, final long now) {
-        // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm.
-        // 2. If the network was lingering and there are now requests, unlinger it.
+    private boolean updateInactivityState(@NonNull final NetworkAgentInfo nai, final long now) {
+        // 1. Update the inactivity timer. If it's changed, reschedule or cancel the alarm.
+        // 2. If the network was inactive and there are now requests, unset inactive.
         // 3. If this network is unneeded (which implies it is not lingering), and there is at least
-        //    one lingered request, start lingering.
-        nai.updateLingerTimer();
-        if (nai.isLingering() && nai.numForegroundNetworkRequests() > 0) {
-            if (DBG) log("Unlingering " + nai.toShortString());
-            nai.unlinger();
+        //    one lingered request, set inactive.
+        nai.updateInactivityTimer();
+        if (nai.isInactive() && nai.numForegroundNetworkRequests() > 0) {
+            if (DBG) log("Unsetting inactive " + nai.toShortString());
+            nai.unsetInactive();
             logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER);
-        } else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) {
+        } else if (unneeded(nai, UnneededFor.LINGER) && nai.getInactivityExpiry() > 0) {
             if (DBG) {
-                final int lingerTime = (int) (nai.getLingerExpiry() - now);
-                log("Lingering " + nai.toShortString() + " for " + lingerTime + "ms");
+                final int lingerTime = (int) (nai.getInactivityExpiry() - now);
+                log("Setting inactive " + nai.toShortString() + " for " + lingerTime + "ms");
             }
-            nai.linger();
+            nai.setInactive();
             logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER);
             return true;
         }
@@ -3329,7 +3363,6 @@
                 if (VDBG) log("NetworkFactory connected");
                 // Finish setting up the full connection
                 NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo);
-                npi.completeConnection();
                 sendAllRequestsToProvider(npi);
             } else {
                 loge("Error connecting NetworkFactory");
@@ -3431,25 +3464,33 @@
         propagateUnderlyingNetworkCapabilities(nai.network);
         // Remove all previously satisfied requests.
         for (int i = 0; i < nai.numNetworkRequests(); i++) {
-            NetworkRequest request = nai.requestAt(i);
+            final NetworkRequest request = nai.requestAt(i);
             final NetworkRequestInfo nri = mNetworkRequests.get(request);
-            final NetworkAgentInfo currentNetwork = nri.mSatisfier;
+            final NetworkAgentInfo currentNetwork = nri.getSatisfier();
             if (currentNetwork != null
                     && currentNetwork.network.getNetId() == nai.network.getNetId()) {
-                nri.mSatisfier = null;
-                sendUpdatedScoreToFactories(request, null);
+                // uid rules for this network will be removed in destroyNativeNetwork(nai).
+                nri.setSatisfier(null, null);
+                if (request.isRequest()) {
+                    sendUpdatedScoreToFactories(request, null);
+                }
+
+                if (mDefaultRequest == nri) {
+                    // TODO : make battery stats aware that since 2013 multiple interfaces may be
+                    //  active at the same time. For now keep calling this with the default
+                    //  network, because while incorrect this is the closest to the old (also
+                    //  incorrect) behavior.
+                    mNetworkActivityTracker.updateDataActivityTracking(
+                            null /* newNetwork */, nai);
+                    notifyLockdownVpn(nai);
+                    ensureNetworkTransitionWakelock(nai.toShortString());
+                }
             }
         }
-        nai.clearLingerState();
-        // TODO: this loop, and the mLegacyTypeTracker.remove just below it, seem redundant given
-        // there's a full rematch right after. Currently, deleting it breaks tests that check for
-        // the default network disconnecting. Find out why, fix the rematch code, and delete this.
-        if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
-            mDefaultNetworkNai = null;
-            mNetworkActivityTracker.updateDataActivityTracking(null /* newNetwork */, nai);
-            notifyLockdownVpn(nai);
-            ensureNetworkTransitionWakelock(nai.toShortString());
-        }
+        nai.clearInactivityState();
+        // TODO: mLegacyTypeTracker.remove seems redundant given there's a full rematch right after.
+        //  Currently, deleting it breaks tests that check for the default network disconnecting.
+        //  Find out why, fix the rematch code, and delete this.
         mLegacyTypeTracker.remove(nai, wasDefault);
         rematchAllNetworksAndRequests();
         mLingerMonitor.noteDisconnect(nai);
@@ -3458,10 +3499,9 @@
             // (routing rules, DNS, etc).
             // This may be slow as it requires a lot of netd shelling out to ip and
             // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
-            // after we've rematched networks with requests which should make a potential
-            // fallback network the default or requested a new network from the
-            // NetworkProviders, so network traffic isn't interrupted for an unnecessarily
-            // long time.
+            // after we've rematched networks with requests (which might change the default
+            // network or service a new request from an app), so network traffic isn't interrupted
+            // for an unnecessarily long time.
             destroyNativeNetwork(nai);
             mDnsManager.removeNetwork(nai.network);
         }
@@ -3514,42 +3554,60 @@
         return null;
     }
 
-    private void handleRegisterNetworkRequestWithIntent(Message msg) {
+    private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) {
         final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
-
-        NetworkRequestInfo existingRequest = findExistingNetworkRequestInfo(nri.mPendingIntent);
+        // handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests.
+        ensureNotMultilayerRequest(nri, "handleRegisterNetworkRequestWithIntent");
+        final NetworkRequestInfo existingRequest =
+                findExistingNetworkRequestInfo(nri.mPendingIntent);
         if (existingRequest != null) { // remove the existing request.
-            if (DBG) log("Replacing " + existingRequest.request + " with "
-                    + nri.request + " because their intents matched.");
-            handleReleaseNetworkRequest(existingRequest.request, getCallingUid(),
+            if (DBG) {
+                log("Replacing " + existingRequest.mRequests.get(0) + " with "
+                        + nri.mRequests.get(0) + " because their intents matched.");
+            }
+            handleReleaseNetworkRequest(existingRequest.mRequests.get(0), getCallingUid(),
                     /* callOnUnavailable */ false);
         }
         handleRegisterNetworkRequest(nri);
     }
 
-    private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
+    private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) {
         ensureRunningOnConnectivityServiceThread();
-        mNetworkRequests.put(nri.request, nri);
         mNetworkRequestInfoLogs.log("REGISTER " + nri);
-        if (nri.request.isListen()) {
-            for (NetworkAgentInfo network : mNetworkAgentInfos) {
-                if (nri.request.networkCapabilities.hasSignalStrength() &&
-                        network.satisfiesImmutableCapabilitiesOf(nri.request)) {
-                    updateSignalStrengthThresholds(network, "REGISTER", nri.request);
+        for (final NetworkRequest req : nri.mRequests) {
+            mNetworkRequests.put(req, nri);
+            if (req.isListen()) {
+                for (final NetworkAgentInfo network : mNetworkAgentInfos) {
+                    if (req.networkCapabilities.hasSignalStrength()
+                            && network.satisfiesImmutableCapabilitiesOf(req)) {
+                        updateSignalStrengthThresholds(network, "REGISTER", req);
+                    }
                 }
             }
         }
         rematchAllNetworksAndRequests();
-        if (nri.request.isRequest() && nri.mSatisfier == null) {
-            sendUpdatedScoreToFactories(nri.request, null);
+        // If the nri is satisfied, return as its score has already been sent if needed.
+        if (nri.isBeingSatisfied()) {
+            return;
+        }
+
+        // As this request was not satisfied on rematch and thus never had any scores sent to the
+        // factories, send null now for each request of type REQUEST.
+        for (final NetworkRequest req : nri.mRequests) {
+            if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
         }
     }
 
-    private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent,
-            int callingUid) {
-        NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
+    private void handleReleaseNetworkRequestWithIntent(@NonNull final PendingIntent pendingIntent,
+            final int callingUid) {
+        final NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent);
         if (nri != null) {
-            handleReleaseNetworkRequest(nri.request, callingUid, /* callOnUnavailable */ false);
+            // handleReleaseNetworkRequestWithIntent() paths don't apply to multilayer requests.
+            ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequestWithIntent");
+            handleReleaseNetworkRequest(
+                    nri.mRequests.get(0),
+                    callingUid,
+                    /* callOnUnavailable */ false);
         }
     }
 
@@ -3576,7 +3634,7 @@
                 return true;
         }
 
-        if (!nai.everConnected || nai.isVPN() || nai.isLingering() || numRequests > 0) {
+        if (!nai.everConnected || nai.isVPN() || nai.isInactive() || numRequests > 0) {
             return false;
         }
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
@@ -3603,6 +3661,11 @@
             return false;
         }
         for (final NetworkRequest req : nri.mRequests) {
+            // This multilayer listen request is satisfied therefore no further requests need to be
+            // evaluated deeming this network not a potential satisfier.
+            if (req.isListen() && nri.getActiveRequest() == req) {
+                return false;
+            }
             // As non-multilayer listen requests have already returned, the below would only happen
             // for a multilayer request therefore continue to the next request if available.
             if (req.isListen()) {
@@ -3623,7 +3686,7 @@
                         // 2. Unvalidated WiFi will not be reaped when validated cellular
                         //    is currently satisfying the request.  This is desirable when
                         //    WiFi ends up validating and out scoring cellular.
-                        || nri.mSatisfier.getCurrentScore()
+                        || nri.getSatisfier().getCurrentScore()
                         < candidate.getCurrentScoreAsValidated();
                 return isNetworkNeeded;
             }
@@ -3648,30 +3711,45 @@
         return nri;
     }
 
-    private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
-        ensureRunningOnConnectivityServiceThread();
-        if (mNetworkRequests.get(nri.request) == null) {
-            return;
+    private void ensureNotMultilayerRequest(@NonNull final NetworkRequestInfo nri,
+            final String callingMethod) {
+        if (nri.isMultilayerRequest()) {
+            throw new IllegalStateException(
+                    callingMethod + " does not support multilayer requests.");
         }
-        if (nri.mSatisfier != null) {
-            return;
-        }
-        if (VDBG || (DBG && nri.request.isRequest())) {
-            log("releasing " + nri.request + " (timeout)");
-        }
-        handleRemoveNetworkRequest(nri);
-        callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
     }
 
-    private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid,
-            boolean callOnUnavailable) {
+    private void handleTimedOutNetworkRequest(@NonNull final NetworkRequestInfo nri) {
+        ensureRunningOnConnectivityServiceThread();
+        // handleTimedOutNetworkRequest() is part of the requestNetwork() flow which works off of a
+        // single NetworkRequest and thus does not apply to multilayer requests.
+        ensureNotMultilayerRequest(nri, "handleTimedOutNetworkRequest");
+        if (mNetworkRequests.get(nri.mRequests.get(0)) == null) {
+            return;
+        }
+        if (nri.isBeingSatisfied()) {
+            return;
+        }
+        if (VDBG || (DBG && nri.mRequests.get(0).isRequest())) {
+            log("releasing " + nri.mRequests.get(0) + " (timeout)");
+        }
+        handleRemoveNetworkRequest(nri);
+        callCallbackForRequest(
+                nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+    }
+
+    private void handleReleaseNetworkRequest(@NonNull final NetworkRequest request,
+            final int callingUid,
+            final boolean callOnUnavailable) {
         final NetworkRequestInfo nri =
                 getNriForAppRequest(request, callingUid, "release NetworkRequest");
         if (nri == null) {
             return;
         }
-        if (VDBG || (DBG && nri.request.isRequest())) {
-            log("releasing " + nri.request + " (release request)");
+        // handleReleaseNetworkRequest() paths don't apply to multilayer requests.
+        ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequest");
+        if (VDBG || (DBG && request.isRequest())) {
+            log("releasing " + request + " (release request)");
         }
         handleRemoveNetworkRequest(nri);
         if (callOnUnavailable) {
@@ -3679,42 +3757,88 @@
         }
     }
 
-    private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
+    private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) {
         ensureRunningOnConnectivityServiceThread();
 
         nri.unlinkDeathRecipient();
-        mNetworkRequests.remove(nri.request);
-
+        for (final NetworkRequest req : nri.mRequests) {
+            mNetworkRequests.remove(req);
+            if (req.isListen()) {
+                removeListenRequestFromNetworks(req);
+            }
+        }
         mNetworkRequestCounter.decrementCount(nri.mUid);
-
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
-        if (nri.request.isRequest()) {
-            boolean wasKept = false;
-            final NetworkAgentInfo nai = nri.mSatisfier;
-            if (nai != null) {
-                boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
-                nai.removeRequest(nri.request.requestId);
-                if (VDBG || DDBG) {
-                    log(" Removing from current network " + nai.toShortString()
-                            + ", leaving " + nai.numNetworkRequests() + " requests.");
-                }
-                // If there are still lingered requests on this network, don't tear it down,
-                // but resume lingering instead.
-                final long now = SystemClock.elapsedRealtime();
-                if (updateLingerState(nai, now)) {
-                    notifyNetworkLosing(nai, now);
-                }
-                if (unneeded(nai, UnneededFor.TEARDOWN)) {
-                    if (DBG) log("no live requests for " + nai.toShortString() + "; disconnecting");
-                    teardownUnneededNetwork(nai);
-                } else {
-                    wasKept = true;
-                }
-                nri.mSatisfier = null;
-                if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
-                    // Went from foreground to background.
-                    updateCapabilitiesForNetwork(nai);
-                }
+
+        if (null != nri.getActiveRequest()) {
+            if (!nri.getActiveRequest().isListen()) {
+                removeSatisfiedNetworkRequestFromNetwork(nri);
+            } else {
+                nri.setSatisfier(null, null);
+            }
+        }
+
+        cancelNpiRequests(nri);
+    }
+
+    private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) {
+        for (final NetworkRequest req : nri.mRequests) {
+            cancelNpiRequest(req);
+        }
+    }
+
+    private void cancelNpiRequest(@NonNull final NetworkRequest req) {
+        if (req.isRequest()) {
+            for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+                npi.cancelRequest(req);
+            }
+        }
+    }
+
+    private void removeListenRequestFromNetworks(@NonNull final NetworkRequest req) {
+        // listens don't have a singular affected Network. Check all networks to see
+        // if this listen request applies and remove it.
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            nai.removeRequest(req.requestId);
+            if (req.networkCapabilities.hasSignalStrength()
+                    && nai.satisfiesImmutableCapabilitiesOf(req)) {
+                updateSignalStrengthThresholds(nai, "RELEASE", req);
+            }
+        }
+    }
+
+    /**
+     * Remove a NetworkRequestInfo's satisfied request from its 'satisfier' (NetworkAgentInfo) and
+     * manage the necessary upkeep (linger, teardown networks, etc.) when doing so.
+     * @param nri the NetworkRequestInfo to disassociate from its current NetworkAgentInfo
+     */
+    private void removeSatisfiedNetworkRequestFromNetwork(@NonNull final NetworkRequestInfo nri) {
+        boolean wasKept = false;
+        final NetworkAgentInfo nai = nri.getSatisfier();
+        if (nai != null) {
+            final int requestLegacyType = nri.getActiveRequest().legacyType;
+            final boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
+            nai.removeRequest(nri.getActiveRequest().requestId);
+            if (VDBG || DDBG) {
+                log(" Removing from current network " + nai.toShortString()
+                        + ", leaving " + nai.numNetworkRequests() + " requests.");
+            }
+            // If there are still lingered requests on this network, don't tear it down,
+            // but resume lingering instead.
+            final long now = SystemClock.elapsedRealtime();
+            if (updateInactivityState(nai, now)) {
+                notifyNetworkLosing(nai, now);
+            }
+            if (unneeded(nai, UnneededFor.TEARDOWN)) {
+                if (DBG) log("no live requests for " + nai.toShortString() + "; disconnecting");
+                teardownUnneededNetwork(nai);
+            } else {
+                wasKept = true;
+            }
+            nri.setSatisfier(null, null);
+            if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
+                // Went from foreground to background.
+                updateCapabilitiesForNetwork(nai);
             }
 
             // Maintain the illusion.  When this request arrived, we might have pretended
@@ -3722,15 +3846,15 @@
             // connected.  Now that this request has gone away, we might have to pretend
             // that the network disconnected.  LegacyTypeTracker will generate that
             // phantom disconnect for this type.
-            if (nri.request.legacyType != TYPE_NONE && nai != null) {
+            if (requestLegacyType != TYPE_NONE) {
                 boolean doRemove = true;
                 if (wasKept) {
                     // check if any of the remaining requests for this network are for the
                     // same legacy type - if so, don't remove the nai
                     for (int i = 0; i < nai.numNetworkRequests(); i++) {
                         NetworkRequest otherRequest = nai.requestAt(i);
-                        if (otherRequest.legacyType == nri.request.legacyType &&
-                                otherRequest.isRequest()) {
+                        if (otherRequest.legacyType == requestLegacyType
+                                && otherRequest.isRequest()) {
                             if (DBG) log(" still have other legacy request - leaving");
                             doRemove = false;
                         }
@@ -3738,21 +3862,7 @@
                 }
 
                 if (doRemove) {
-                    mLegacyTypeTracker.remove(nri.request.legacyType, nai, false);
-                }
-            }
-
-            for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
-                npi.cancelRequest(nri.request);
-            }
-        } else {
-            // listens don't have a singular affectedNetwork.  Check all networks to see
-            // if this listen request applies and remove it.
-            for (NetworkAgentInfo nai : mNetworkAgentInfos) {
-                nai.removeRequest(nri.request.requestId);
-                if (nri.request.networkCapabilities.hasSignalStrength() &&
-                        nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
-                    updateSignalStrengthThresholds(nai, "RELEASE", nri.request);
+                    mLegacyTypeTracker.remove(requestLegacyType, nai, false);
                 }
             }
         }
@@ -4177,7 +4287,7 @@
 
     @Override
     public NetworkRequest getDefaultRequest() {
-        return mDefaultRequest;
+        return mDefaultRequest.mRequests.get(0);
     }
 
     private class InternalHandler extends Handler {
@@ -4755,7 +4865,7 @@
         }
         synchronized (mVpns) {
             throwIfLockdownEnabled();
-            mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
+            mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress);
         }
     }
 
@@ -4780,35 +4890,36 @@
      *
      * <p>Must be called on the handler thread.
      */
-    private VpnInfo[] getAllVpnInfo() {
+    private UnderlyingNetworkInfo[] getAllVpnInfo() {
         ensureRunningOnConnectivityServiceThread();
         synchronized (mVpns) {
             if (mLockdownEnabled) {
-                return new VpnInfo[0];
+                return new UnderlyingNetworkInfo[0];
             }
         }
-        List<VpnInfo> infoList = new ArrayList<>();
+        List<UnderlyingNetworkInfo> infoList = new ArrayList<>();
         for (NetworkAgentInfo nai : mNetworkAgentInfos) {
-            VpnInfo info = createVpnInfo(nai);
+            UnderlyingNetworkInfo info = createVpnInfo(nai);
             if (info != null) {
                 infoList.add(info);
             }
         }
-        return infoList.toArray(new VpnInfo[infoList.size()]);
+        return infoList.toArray(new UnderlyingNetworkInfo[infoList.size()]);
     }
 
     /**
      * @return VPN information for accounting, or null if we can't retrieve all required
      *         information, e.g underlying ifaces.
      */
-    private VpnInfo createVpnInfo(NetworkAgentInfo nai) {
+    private UnderlyingNetworkInfo createVpnInfo(NetworkAgentInfo nai) {
         if (!nai.isVPN()) return null;
 
         Network[] underlyingNetworks = nai.declaredUnderlyingNetworks;
         // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
         // the underlyingNetworks list.
         if (underlyingNetworks == null) {
-            NetworkAgentInfo defaultNai = getDefaultNetwork();
+            final NetworkAgentInfo defaultNai = getDefaultNetworkForUid(
+                    nai.networkCapabilities.getOwnerUid());
             if (defaultNai != null) {
                 underlyingNetworks = new Network[] { defaultNai.network };
             }
@@ -4830,16 +4941,14 @@
 
         if (interfaces.isEmpty()) return null;
 
-        VpnInfo info = new VpnInfo();
-        info.ownerUid = nai.networkCapabilities.getOwnerUid();
-        info.vpnIface = nai.linkProperties.getInterfaceName();
         // Must be non-null or NetworkStatsService will crash.
         // Cannot happen in production code because Vpn only registers the NetworkAgent after the
         // tun or ipsec interface is created.
-        if (info.vpnIface == null) return null;
-        info.underlyingIfaces = interfaces.toArray(new String[0]);
+        // TODO: Remove this check.
+        if (nai.linkProperties.getInterfaceName() == null) return null;
 
-        return info;
+        return new UnderlyingNetworkInfo(nai.networkCapabilities.getOwnerUid(),
+                nai.linkProperties.getInterfaceName(), interfaces);
     }
 
     /**
@@ -4861,8 +4970,10 @@
         }
     }
 
-    private Network[] underlyingNetworksOrDefault(Network[] underlyingNetworks) {
-        final Network defaultNetwork = getNetwork(getDefaultNetwork());
+    // TODO This needs to be the default network that applies to the NAI.
+    private Network[] underlyingNetworksOrDefault(final int ownerUid,
+            Network[] underlyingNetworks) {
+        final Network defaultNetwork = getNetwork(getDefaultNetworkForUid(ownerUid));
         if (underlyingNetworks == null && defaultNetwork != null) {
             // null underlying networks means to track the default.
             underlyingNetworks = new Network[] { defaultNetwork };
@@ -4875,7 +4986,8 @@
         // TODO: support more than one level of underlying networks, either via a fixed-depth search
         // (e.g., 2 levels of underlying networks), or via loop detection, or....
         if (!nai.supportsUnderlyingNetworks()) return false;
-        final Network[] underlying = underlyingNetworksOrDefault(nai.declaredUnderlyingNetworks);
+        final Network[] underlying = underlyingNetworksOrDefault(
+                nai.networkCapabilities.getOwnerUid(), nai.declaredUnderlyingNetworks);
         return ArrayUtils.contains(underlying, network);
     }
 
@@ -5333,27 +5445,21 @@
     private static class NetworkProviderInfo {
         public final String name;
         public final Messenger messenger;
-        private final AsyncChannel mAsyncChannel;
         private final IBinder.DeathRecipient mDeathRecipient;
         public final int providerId;
 
         NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
-                int providerId, IBinder.DeathRecipient deathRecipient) {
+                int providerId, @NonNull IBinder.DeathRecipient deathRecipient) {
             this.name = name;
             this.messenger = messenger;
             this.providerId = providerId;
-            mAsyncChannel = asyncChannel;
             mDeathRecipient = deathRecipient;
 
-            if ((mAsyncChannel == null) == (mDeathRecipient == null)) {
-                throw new AssertionError("Must pass exactly one of asyncChannel or deathRecipient");
+            if (mDeathRecipient == null) {
+                throw new AssertionError("Must pass a deathRecipient");
             }
         }
 
-        boolean isLegacyNetworkFactory() {
-            return mAsyncChannel != null;
-        }
-
         void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) {
             try {
                 messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
@@ -5364,38 +5470,19 @@
         }
 
         void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
-            if (isLegacyNetworkFactory()) {
-                mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
-                        servingProviderId, request);
-            } else {
-                sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
+            sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
                             servingProviderId, request);
-            }
         }
 
         void cancelRequest(NetworkRequest request) {
-            if (isLegacyNetworkFactory()) {
-                mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, request);
-            } else {
-                sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request);
-            }
+            sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request);
         }
 
         void connect(Context context, Handler handler) {
-            if (isLegacyNetworkFactory()) {
-                mAsyncChannel.connect(context, handler, messenger);
-            } else {
-                try {
-                    messenger.getBinder().linkToDeath(mDeathRecipient, 0);
-                } catch (RemoteException e) {
-                    mDeathRecipient.binderDied();
-                }
-            }
-        }
-
-        void completeConnection() {
-            if (isLegacyNetworkFactory()) {
-                mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+            try {
+                messenger.getBinder().linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                mDeathRecipient.binderDied();
             }
         }
     }
@@ -5415,18 +5502,39 @@
 
     /**
      * Tracks info about the requester.
-     * Also used to notice when the calling process dies so we can self-expire
+     * Also used to notice when the calling process dies so as to self-expire
      */
     @VisibleForTesting
     protected class NetworkRequestInfo implements IBinder.DeathRecipient {
+        // The requests to be satisfied in priority order. Non-multilayer requests will only have a
+        // single NetworkRequest in mRequests.
         final List<NetworkRequest> mRequests;
-        final NetworkRequest request;
 
-        // The network currently satisfying this request, or null if none. Must only be touched
-        // on the handler thread. This only makes sense for network requests and not for listens,
-        // as defined by NetworkRequest#isRequest(). For listens, this is always null.
+        // mSatisfier and mActiveRequest rely on one another therefore set them together.
+        void setSatisfier(
+                @Nullable final NetworkAgentInfo satisfier,
+                @Nullable final NetworkRequest activeRequest) {
+            mSatisfier = satisfier;
+            mActiveRequest = activeRequest;
+        }
+
+        // The network currently satisfying this NRI. Only one request in an NRI can have a
+        // satisfier. For non-multilayer requests, only non-listen requests can have a satisfier.
         @Nullable
-        NetworkAgentInfo mSatisfier;
+        private NetworkAgentInfo mSatisfier;
+        NetworkAgentInfo getSatisfier() {
+            return mSatisfier;
+        }
+
+        // The request in mRequests assigned to a network agent. This is null if none of the
+        // requests in mRequests can be satisfied. This member has the constraint of only being
+        // accessible on the handler thread.
+        @Nullable
+        private NetworkRequest mActiveRequest;
+        NetworkRequest getActiveRequest() {
+            return mActiveRequest;
+        }
+
         final PendingIntent mPendingIntent;
         boolean mPendingIntentSent;
         private final IBinder mBinder;
@@ -5434,8 +5542,19 @@
         final int mUid;
         final Messenger messenger;
 
+        /**
+         * Get the list of UIDs this nri applies to.
+         */
+        @NonNull
+        private Set<UidRange> getUids() {
+            // networkCapabilities.getUids() returns a defensive copy.
+            // multilayer requests will all have the same uids so return the first one.
+            final Set<UidRange> uids = null == mRequests.get(0).networkCapabilities.getUids()
+                    ? new ArraySet<>() : mRequests.get(0).networkCapabilities.getUids();
+            return uids;
+        }
+
         NetworkRequestInfo(NetworkRequest r, PendingIntent pi) {
-            request = r;
             mRequests = initializeRequests(r);
             ensureAllNetworkRequestsHaveType(mRequests);
             mPendingIntent = pi;
@@ -5449,7 +5568,6 @@
         NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) {
             super();
             messenger = m;
-            request = r;
             mRequests = initializeRequests(r);
             ensureAllNetworkRequestsHaveType(mRequests);
             mBinder = binder;
@@ -5469,6 +5587,13 @@
             this(r, null);
         }
 
+        // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer
+        // set to the mNoServiceNetwork in which case mActiveRequest will be null thus returning
+        // false.
+        boolean isBeingSatisfied() {
+            return (null != mSatisfier && null != mActiveRequest);
+        }
+
         boolean isMultilayerRequest() {
             return mRequests.size() > 1;
         }
@@ -5479,20 +5604,6 @@
             return Collections.unmodifiableList(tempRequests);
         }
 
-        private NetworkRequest getSatisfiedRequest() {
-            if (mSatisfier == null) {
-                return null;
-            }
-
-            for (NetworkRequest req : mRequests) {
-                if (mSatisfier.isSatisfyingRequest(req.requestId)) {
-                    return req;
-                }
-            }
-
-            return null;
-        }
-
         void unlinkDeathRecipient() {
             if (mBinder != null) {
                 mBinder.unlinkToDeath(this, 0);
@@ -5508,7 +5619,9 @@
 
         @Override
         public String toString() {
-            return "uid/pid:" + mUid + "/" + mPid + " " + mRequests
+            return "uid/pid:" + mUid + "/" + mPid + " active request Id: "
+                    + (mActiveRequest == null ? null : mActiveRequest.requestId)
+                    + " " + mRequests
                     + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
         }
     }
@@ -5539,6 +5652,10 @@
     private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
         final SortedSet<Integer> thresholds = new TreeSet<>();
         synchronized (nai) {
+            // mNetworkRequests may contain the same value multiple times in case of
+            // multilayer requests. It won't matter in this case because the thresholds
+            // will then be the same and be deduplicated as they enter the `thresholds` set.
+            // TODO : have mNetworkRequests be a Set<NetworkRequestInfo> or the like.
             for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
                 for (final NetworkRequest req : nri.mRequests) {
                     if (req.networkCapabilities.hasSignalStrength()
@@ -5578,7 +5695,9 @@
         if (ns == null) {
             return;
         }
-        MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns);
+        if (ns instanceof MatchAllNetworkSpecifier) {
+            throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted");
+        }
     }
 
     private void ensureValid(NetworkCapabilities nc) {
@@ -5625,6 +5744,9 @@
                 networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid);
                 enforceAccessPermission();
                 break;
+            case BACKGROUND_REQUEST:
+                enforceNetworkStackOrSettingsPermission();
+                // Fall-through since other checks are the same with normal requests.
             case REQUEST:
                 networkCapabilities = new NetworkCapabilities(networkCapabilities);
                 enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
@@ -5855,15 +5977,6 @@
                 EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(), 0, networkRequest));
     }
 
-    @Override
-    public int registerNetworkFactory(Messenger messenger, String name) {
-        enforceNetworkFactoryPermission();
-        NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(),
-                nextNetworkProviderId(), null /* deathRecipient */);
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
-        return npi.providerId;
-    }
-
     private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
         if (mNetworkProviderInfos.containsKey(npi.messenger)) {
             // Avoid creating duplicates. even if an app makes a direct AIDL call.
@@ -5877,10 +5990,7 @@
         if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
         mNetworkProviderInfos.put(npi.messenger, npi);
         npi.connect(mContext, mTrackerHandler);
-        if (!npi.isLegacyNetworkFactory()) {
-            // Legacy NetworkFactories get their requests when their AsyncChannel connects.
-            sendAllRequestsToProvider(npi);
-        }
+        sendAllRequestsToProvider(npi);
     }
 
     @Override
@@ -5899,11 +6009,6 @@
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
     }
 
-    @Override
-    public void unregisterNetworkFactory(Messenger messenger) {
-        unregisterNetworkProvider(messenger);
-    }
-
     private void handleUnregisterNetworkProvider(Messenger messenger) {
         NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);
         if (npi == null) {
@@ -5914,13 +6019,19 @@
     }
 
     @Override
-    public void declareNetworkRequestUnfulfillable(NetworkRequest request) {
+    public void declareNetworkRequestUnfulfillable(@NonNull final NetworkRequest request) {
         if (request.hasTransport(TRANSPORT_TEST)) {
             enforceNetworkFactoryOrTestNetworksPermission();
         } else {
             enforceNetworkFactoryPermission();
         }
-        mHandler.post(() -> handleReleaseNetworkRequest(request, mDeps.getCallingUid(), true));
+        final NetworkRequestInfo nri = mNetworkRequests.get(request);
+        if (nri != null) {
+            // declareNetworkRequestUnfulfillable() paths don't apply to multilayer requests.
+            ensureNotMultilayerRequest(nri, "declareNetworkRequestUnfulfillable");
+            mHandler.post(() -> handleReleaseNetworkRequest(
+                    nri.mRequests.get(0), mDeps.getCallingUid(), true));
+        }
     }
 
     // NOTE: Accessed on multiple threads, must be synchronized on itself.
@@ -5945,11 +6056,33 @@
     @GuardedBy("mBlockedAppUids")
     private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
 
+    // The always-on request for an Internet-capable network that apps without a specific default
+    // fall back to.
     @NonNull
-    private final NetworkRequest mDefaultRequest;
-    // The NetworkAgentInfo currently satisfying the default request, if any.
-    @Nullable
-    private volatile NetworkAgentInfo mDefaultNetworkNai = null;
+    private final NetworkRequestInfo mDefaultRequest;
+    // Collection of NetworkRequestInfo's used for default networks.
+    @NonNull
+    private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>();
+
+    private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) {
+        return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri);
+    }
+
+    /**
+     * Determine if an nri is a managed default request that disallows default networking.
+     * @param nri the request to evaluate
+     * @return true if device-default networking is disallowed
+     */
+    private boolean isDefaultBlocked(@NonNull final NetworkRequestInfo nri) {
+        // Check if this nri is a managed default that supports the default network at its
+        // lowest priority request.
+        final NetworkRequest defaultNetworkRequest = mDefaultRequest.mRequests.get(0);
+        final NetworkCapabilities lowestPriorityNetCap =
+                nri.mRequests.get(nri.mRequests.size() - 1).networkCapabilities;
+        return isPerAppDefaultRequest(nri)
+                && !(defaultNetworkRequest.networkCapabilities.equalRequestableCapabilities(
+                        lowestPriorityNetCap));
+    }
 
     // Request used to optionally keep mobile data active even when higher
     // priority networks like Wi-Fi are active.
@@ -5962,8 +6095,37 @@
     // Request used to optionally keep vehicle internal network always active
     private final NetworkRequest mDefaultVehicleRequest;
 
+    // TODO replace with INetd.DUMMY_NET_ID when available.
+    private static final int NO_SERVICE_NET_ID = 51;
+    // Sentinel NAI used to direct apps with default networks that should have no connectivity to a
+    // network with no service. This NAI should never be matched against, nor should any public API
+    // ever return the associated network. For this reason, this NAI is not in the list of available
+    // NAIs. It is used in computeNetworkReassignment() to be set as the satisfier for non-device
+    // default requests that don't support using the device default network which will ultimately
+    // allow ConnectivityService to use this no-service network when calling makeDefaultForApps().
+    @VisibleForTesting
+    final NetworkAgentInfo mNoServiceNetwork;
+
+    // The NetworkAgentInfo currently satisfying the default request, if any.
     private NetworkAgentInfo getDefaultNetwork() {
-        return mDefaultNetworkNai;
+        return mDefaultRequest.mSatisfier;
+    }
+
+    private NetworkAgentInfo getDefaultNetworkForUid(final int uid) {
+        for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
+            // Currently, all network requests will have the same uids therefore checking the first
+            // one is sufficient. If/when uids are tracked at the nri level, this can change.
+            final Set<UidRange> uids = nri.mRequests.get(0).networkCapabilities.getUids();
+            if (null == uids) {
+                continue;
+            }
+            for (final UidRange range : uids) {
+                if (range.contains(uid)) {
+                    return nri.getSatisfier();
+                }
+            }
+        }
+        return getDefaultNetwork();
     }
 
     @Nullable
@@ -6050,8 +6212,6 @@
 
         LinkProperties lp = new LinkProperties(linkProperties);
 
-        // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
-        // satisfies mDefaultRequest.
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
         final NetworkAgentInfo nai = new NetworkAgentInfo(na,
                 new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
@@ -6108,7 +6268,7 @@
         nai.networkAgentPortalData = lp.getCaptivePortalData();
     }
 
-    private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp,
+    private void updateLinkProperties(NetworkAgentInfo networkAgent, @NonNull LinkProperties newLp,
             @NonNull LinkProperties oldLp) {
         int netId = networkAgent.network.getNetId();
 
@@ -6117,8 +6277,7 @@
         // the LinkProperties for the network are accurate.
         networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
 
-        updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities,
-                networkAgent.networkInfo.getType());
+        updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
 
         // update filtering rules, need to happen after the interface update so netd knows about the
         // new interface (the interface name -> index map becomes initialized)
@@ -6257,7 +6416,7 @@
 
     private void updateInterfaces(final @Nullable LinkProperties newLp,
             final @Nullable LinkProperties oldLp, final int netId,
-            final @Nullable NetworkCapabilities caps, final int legacyType) {
+            final @NonNull NetworkCapabilities caps) {
         final CompareResult<String> interfaceDiff = new CompareResult<>(
                 oldLp != null ? oldLp.getAllInterfaceNames() : null,
                 newLp != null ? newLp.getAllInterfaceNames() : null);
@@ -6268,7 +6427,7 @@
                     if (DBG) log("Adding iface " + iface + " to network " + netId);
                     mNetd.networkAddInterface(netId, iface);
                     wakeupModifyInterface(iface, caps, true);
-                    bs.noteNetworkInterfaceType(iface, legacyType);
+                    bs.noteNetworkInterfaceForTransports(iface, caps.getTransportTypes());
                 } catch (Exception e) {
                     loge("Exception adding interface: " + e);
                 }
@@ -6475,7 +6634,8 @@
     @VisibleForTesting
     void applyUnderlyingCapabilities(@Nullable Network[] underlyingNetworks,
             @NonNull NetworkCapabilities agentCaps, @NonNull NetworkCapabilities newNc) {
-        underlyingNetworks = underlyingNetworksOrDefault(underlyingNetworks);
+        underlyingNetworks = underlyingNetworksOrDefault(
+                agentCaps.getOwnerUid(), underlyingNetworks);
         int[] transportTypes = agentCaps.getTransportTypes();
         int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
         int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
@@ -6540,6 +6700,7 @@
      * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal,
      * and foreground status).
      */
+    @NonNull
     private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) {
         // Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
          // Don't complain for VPNs since they're not driven by requests and there is no risk of
@@ -6596,6 +6757,25 @@
         return newNc;
     }
 
+    private void updateNetworkInfoForRoamingAndSuspended(NetworkAgentInfo nai,
+            NetworkCapabilities prevNc, NetworkCapabilities newNc) {
+        final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+        final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+        if (prevSuspended != suspended) {
+            // TODO (b/73132094) : remove this call once the few users of onSuspended and
+            // onResumed have been removed.
+            notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED
+                    : ConnectivityManager.CALLBACK_RESUMED);
+        }
+        if (prevSuspended != suspended || prevRoaming != roaming) {
+            // updateNetworkInfo will mix in the suspended info from the capabilities and
+            // take appropriate action for the network having possibly changed state.
+            updateNetworkInfo(nai, nai.networkInfo);
+        }
+    }
+
     /**
      * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically:
      *
@@ -6627,25 +6807,13 @@
             // on this network. We might have been called by rematchNetworkAndRequests when a
             // network changed foreground state.
             processListenRequests(nai);
-            final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
-            final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
-            final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
-            final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
-            if (prevSuspended != suspended || prevRoaming != roaming) {
-                // TODO (b/73132094) : remove this call once the few users of onSuspended and
-                // onResumed have been removed.
-                notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED
-                        : ConnectivityManager.CALLBACK_RESUMED);
-                // updateNetworkInfo will mix in the suspended info from the capabilities and
-                // take appropriate action for the network having possibly changed state.
-                updateNetworkInfo(nai, nai.networkInfo);
-            }
         } else {
             // If the requestable capabilities have changed or the score changed, we can't have been
             // called by rematchNetworkAndRequests, so it's safe to start a rematch.
             rematchAllNetworksAndRequests();
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
+        updateNetworkInfoForRoamingAndSuspended(nai, prevNc, newNc);
 
         final boolean oldMetered = prevNc.isMetered();
         final boolean newMetered = newNc.isMetered();
@@ -6839,12 +7007,45 @@
     private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
         for (int i = 0; i < nai.numNetworkRequests(); i++) {
             NetworkRequest nr = nai.requestAt(i);
-            // Don't send listening requests to factories. b/17393458
-            if (nr.isListen()) continue;
+            // Don't send listening or track default request to factories. b/17393458
+            if (!nr.isRequest()) continue;
             sendUpdatedScoreToFactories(nr, nai);
         }
     }
 
+    private void sendUpdatedScoreToFactories(
+            @NonNull final NetworkReassignment.RequestReassignment event) {
+        // If a request of type REQUEST is now being satisfied by a new network.
+        if (null != event.mNewNetworkRequest && event.mNewNetworkRequest.isRequest()) {
+            sendUpdatedScoreToFactories(event.mNewNetworkRequest, event.mNewNetwork);
+        }
+
+        // If a previously satisfied request of type REQUEST is no longer being satisfied.
+        if (null != event.mOldNetworkRequest && event.mOldNetworkRequest.isRequest()
+                && event.mOldNetworkRequest != event.mNewNetworkRequest) {
+            sendUpdatedScoreToFactories(event.mOldNetworkRequest, null);
+        }
+
+        cancelMultilayerLowerPriorityNpiRequests(event.mNetworkRequestInfo);
+    }
+
+    /**
+     *  Cancel with all NPIs the given NRI's multilayer requests that are a lower priority than
+     *  its currently satisfied active request.
+     * @param nri the NRI to cancel lower priority requests for.
+     */
+    private void cancelMultilayerLowerPriorityNpiRequests(
+            @NonNull final NetworkRequestInfo nri) {
+        if (!nri.isMultilayerRequest() || null == nri.mActiveRequest) {
+            return;
+        }
+
+        final int indexOfNewRequest = nri.mRequests.indexOf(nri.mActiveRequest);
+        for (int i = indexOfNewRequest + 1; i < nri.mRequests.size(); i++) {
+            cancelNpiRequest(nri.mRequests.get(i));
+        }
+    }
+
     private void sendUpdatedScoreToFactories(@NonNull NetworkRequest networkRequest,
             @Nullable NetworkAgentInfo nai) {
         final int score;
@@ -6865,21 +7066,35 @@
     }
 
     /** Sends all current NetworkRequests to the specified factory. */
-    private void sendAllRequestsToProvider(NetworkProviderInfo npi) {
+    private void sendAllRequestsToProvider(@NonNull final NetworkProviderInfo npi) {
         ensureRunningOnConnectivityServiceThread();
-        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-            if (nri.request.isListen()) continue;
-            NetworkAgentInfo nai = nri.mSatisfier;
-            final int score;
-            final int serial;
-            if (nai != null) {
-                score = nai.getCurrentScore();
-                serial = nai.factorySerialNumber;
-            } else {
-                score = 0;
-                serial = NetworkProvider.ID_NONE;
+        for (final NetworkRequestInfo nri : getNrisFromGlobalRequests()) {
+            for (final NetworkRequest req : nri.mRequests) {
+                if (!req.isRequest() && nri.getActiveRequest() == req) {
+                    break;
+                }
+                if (!req.isRequest()) {
+                    continue;
+                }
+                // Only set the nai for the request it is satisfying.
+                final NetworkAgentInfo nai =
+                        nri.getActiveRequest() == req ? nri.getSatisfier() : null;
+                final int score;
+                final int serial;
+                if (null != nai) {
+                    score = nai.getCurrentScore();
+                    serial = nai.factorySerialNumber;
+                } else {
+                    score = 0;
+                    serial = NetworkProvider.ID_NONE;
+                }
+                npi.requestNetwork(req, score, serial);
+                // For multilayer requests, don't send lower priority requests if a higher priority
+                // request is already satisfied.
+                if (null != nai) {
+                    break;
+                }
             }
-            npi.requestNetwork(nri.request, score, serial);
         }
     }
 
@@ -6888,7 +7103,12 @@
         if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
             Intent intent = new Intent();
             intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network);
-            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.request);
+            // If apps could file multi-layer requests with PendingIntents, they'd need to know
+            // which of the layer is satisfied alongside with some ID for the request. Hence, if
+            // such an API is ever implemented, there is no doubt the right request to send in
+            // EXTRA_NETWORK_REQUEST is mActiveRequest, and whatever ID would be added would need to
+            // be sent as a separate extra.
+            intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.getActiveRequest());
             nri.mPendingIntentSent = true;
             sendIntent(nri.mPendingIntent, intent);
         }
@@ -6918,8 +7138,9 @@
         releasePendingNetworkRequestWithDelay(pendingIntent);
     }
 
-    private void callCallbackForRequest(NetworkRequestInfo nri,
-            NetworkAgentInfo networkAgent, int notificationType, int arg1) {
+    private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri,
+            @NonNull final NetworkAgentInfo networkAgent, final int notificationType,
+            final int arg1) {
         if (nri.messenger == null) {
             // Default request has no msgr. Also prevents callbacks from being invoked for
             // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks
@@ -6927,8 +7148,14 @@
             return;
         }
         Bundle bundle = new Bundle();
+        // In the case of multi-layer NRIs, the first request is not necessarily the one that
+        // is satisfied. This is vexing, but the ConnectivityManager code that receives this
+        // callback is only using the request as a token to identify the callback, so it doesn't
+        // matter too much at this point as long as the callback can be found.
+        // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects.
         // TODO: check if defensive copies of data is needed.
-        putParcelable(bundle, new NetworkRequest(nri.request));
+        final NetworkRequest nrForCallback = new NetworkRequest(nri.mRequests.get(0));
+        putParcelable(bundle, nrForCallback);
         Message msg = Message.obtain();
         if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
             putParcelable(bundle, networkAgent.network);
@@ -6941,7 +7168,7 @@
                 putParcelable(
                         bundle,
                         createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                                nc, nri.mUid, nri.request.getRequestorPackageName()));
+                                nc, nri.mUid, nrForCallback.getRequestorPackageName()));
                 putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
                         networkAgent.linkProperties, nri.mPid, nri.mUid));
                 // For this notification, arg1 contains the blocked status.
@@ -6960,7 +7187,7 @@
                 putParcelable(
                         bundle,
                         createWithLocationInfoSanitizedIfNecessaryWhenParceled(
-                                netCap, nri.mUid, nri.request.getRequestorPackageName()));
+                                netCap, nri.mUid, nrForCallback.getRequestorPackageName()));
                 break;
             }
             case ConnectivityManager.CALLBACK_IP_CHANGED: {
@@ -6979,12 +7206,12 @@
         try {
             if (VDBG) {
                 String notification = ConnectivityManager.getCallbackName(notificationType);
-                log("sending notification " + notification + " for " + nri.request);
+                log("sending notification " + notification + " for " + nrForCallback);
             }
             nri.messenger.send(msg);
         } catch (RemoteException e) {
             // may occur naturally in the race of binder death.
-            loge("RemoteException caught trying to send a callback msg for " + nri.request);
+            loge("RemoteException caught trying to send a callback msg for " + nrForCallback);
         }
     }
 
@@ -6996,8 +7223,8 @@
         if (nai.numRequestNetworkRequests() != 0) {
             for (int i = 0; i < nai.numNetworkRequests(); i++) {
                 NetworkRequest nr = nai.requestAt(i);
-                // Ignore listening requests.
-                if (nr.isListen()) continue;
+                // Ignore listening and track default requests.
+                if (!nr.isRequest()) continue;
                 loge("Dead network still had at least " + nr);
                 break;
             }
@@ -7014,42 +7241,132 @@
 
         // If we get here it means that the last linger timeout for this network expired. So there
         // must be no other active linger timers, and we must stop lingering.
-        oldNetwork.clearLingerState();
+        oldNetwork.clearInactivityState();
 
         if (unneeded(oldNetwork, UnneededFor.TEARDOWN)) {
             // Tear the network down.
             teardownUnneededNetwork(oldNetwork);
         } else {
-            // Put the network in the background.
+            // Put the network in the background if it doesn't satisfy any foreground request.
             updateCapabilitiesForNetwork(oldNetwork);
         }
     }
 
-    private void makeDefault(@Nullable final NetworkAgentInfo newNetwork) {
-        if (DBG) log("Switching to new default network: " + newNetwork);
+    private void processDefaultNetworkChanges(@NonNull final NetworkReassignment changes) {
+        boolean isDefaultChanged = false;
+        for (final NetworkRequestInfo defaultRequestInfo : mDefaultNetworkRequests) {
+            final NetworkReassignment.RequestReassignment reassignment =
+                    changes.getReassignment(defaultRequestInfo);
+            if (null == reassignment) {
+                continue;
+            }
+            // reassignment only contains those instances where the satisfying network changed.
+            isDefaultChanged = true;
+            // Notify system services of the new default.
+            makeDefault(defaultRequestInfo, reassignment.mOldNetwork, reassignment.mNewNetwork);
+        }
 
-        mDefaultNetworkNai = newNetwork;
+        if (isDefaultChanged) {
+            // Hold a wakelock for a short time to help apps in migrating to a new default.
+            scheduleReleaseNetworkTransitionWakelock();
+        }
+    }
 
+    private void makeDefault(@NonNull final NetworkRequestInfo nri,
+            @Nullable final NetworkAgentInfo oldDefaultNetwork,
+            @Nullable final NetworkAgentInfo newDefaultNetwork) {
+        if (DBG) {
+            log("Switching to new default network for: " + nri + " using " + newDefaultNetwork);
+        }
+
+        // Fix up the NetworkCapabilities of any networks that have this network as underlying.
+        if (newDefaultNetwork != null) {
+            propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network);
+        }
+
+        // Set an app level managed default and return since further processing only applies to the
+        // default network.
+        if (mDefaultRequest != nri) {
+            makeDefaultForApps(nri, oldDefaultNetwork, newDefaultNetwork);
+            return;
+        }
+
+        makeDefaultNetwork(newDefaultNetwork);
+
+        if (oldDefaultNetwork != null) {
+            mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
+        }
+        mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork);
+        notifyLockdownVpn(newDefaultNetwork);
+        handleApplyDefaultProxy(null != newDefaultNetwork
+                ? newDefaultNetwork.linkProperties.getHttpProxy() : null);
+        updateTcpBufferSizes(null != newDefaultNetwork
+                ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null);
+        notifyIfacesChangedForNetworkStats();
+
+        // Log 0 -> X and Y -> X default network transitions, where X is the new default.
+        final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null;
+        final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0;
+        final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated;
+        final LinkProperties lp = (newDefaultNetwork != null)
+                ? newDefaultNetwork.linkProperties : null;
+        final NetworkCapabilities nc = (newDefaultNetwork != null)
+                ? newDefaultNetwork.networkCapabilities : null;
+
+        final Network prevNetwork = (oldDefaultNetwork != null)
+                ? oldDefaultNetwork.network : null;
+        final int prevScore = (oldDefaultNetwork != null)
+                ? oldDefaultNetwork.getCurrentScore() : 0;
+        final LinkProperties prevLp = (oldDefaultNetwork != null)
+                ? oldDefaultNetwork.linkProperties : null;
+        final NetworkCapabilities prevNc = (oldDefaultNetwork != null)
+                ? oldDefaultNetwork.networkCapabilities : null;
+
+        mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc,
+                prevNetwork, prevScore, prevLp, prevNc);
+    }
+
+    private void makeDefaultForApps(@NonNull final NetworkRequestInfo nri,
+            @Nullable final NetworkAgentInfo oldDefaultNetwork,
+            @Nullable final NetworkAgentInfo newDefaultNetwork) {
         try {
-            if (null != newNetwork) {
-                mNetd.networkSetDefault(newNetwork.network.getNetId());
+            if (VDBG) {
+                log("Setting default network for " + nri
+                        + " using UIDs " + nri.getUids()
+                        + " with old network " + (oldDefaultNetwork != null
+                        ? oldDefaultNetwork.network().getNetId() : "null")
+                        + " and new network " + (newDefaultNetwork != null
+                        ? newDefaultNetwork.network().getNetId() : "null"));
+            }
+            if (nri.getUids().isEmpty()) {
+                throw new IllegalStateException("makeDefaultForApps called without specifying"
+                        + " any applications to set as the default." + nri);
+            }
+            if (null != newDefaultNetwork) {
+                mNetd.networkAddUidRanges(
+                        newDefaultNetwork.network.getNetId(),
+                        toUidRangeStableParcels(nri.getUids()));
+            }
+            if (null != oldDefaultNetwork) {
+                mNetd.networkRemoveUidRanges(
+                        oldDefaultNetwork.network.getNetId(),
+                        toUidRangeStableParcels(nri.getUids()));
+            }
+        } catch (RemoteException | ServiceSpecificException e) {
+            loge("Exception setting OEM network preference default network :" + e);
+        }
+    }
+
+    private void makeDefaultNetwork(@Nullable final NetworkAgentInfo newDefaultNetwork) {
+        try {
+            if (null != newDefaultNetwork) {
+                mNetd.networkSetDefault(newDefaultNetwork.network.getNetId());
             } else {
                 mNetd.networkClearDefault();
             }
         } catch (RemoteException | ServiceSpecificException e) {
             loge("Exception setting default network :" + e);
         }
-
-        notifyLockdownVpn(newNetwork);
-        handleApplyDefaultProxy(null != newNetwork
-                ? newNetwork.linkProperties.getHttpProxy() : null);
-        updateTcpBufferSizes(null != newNetwork
-                ? newNetwork.linkProperties.getTcpBufferSizes() : null);
-        notifyIfacesChangedForNetworkStats();
-        // Fix up the NetworkCapabilities of any networks that have this network as underlying.
-        if (newNetwork != null) {
-            propagateUnderlyingNetworkCapabilities(newNetwork.network);
-        }
     }
 
     private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
@@ -7060,19 +7377,25 @@
     }
 
     private void processNewlyLostListenRequests(@NonNull final NetworkAgentInfo nai) {
-        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-            NetworkRequest nr = nri.request;
+        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+            if (nri.isMultilayerRequest()) {
+                continue;
+            }
+            final NetworkRequest nr = nri.mRequests.get(0);
             if (!nr.isListen()) continue;
             if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) {
-                nai.removeRequest(nri.request.requestId);
+                nai.removeRequest(nr.requestId);
                 callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
             }
         }
     }
 
     private void processNewlySatisfiedListenRequests(@NonNull final NetworkAgentInfo nai) {
-        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-            NetworkRequest nr = nri.request;
+        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+            if (nri.isMultilayerRequest()) {
+                continue;
+            }
+            final NetworkRequest nr = nri.mRequests.get(0);
             if (!nr.isListen()) continue;
             if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {
                 nai.addRequest(nr);
@@ -7084,19 +7407,25 @@
     // An accumulator class to gather the list of changes that result from a rematch.
     private static class NetworkReassignment {
         static class RequestReassignment {
-            @NonNull public final NetworkRequestInfo mRequest;
+            @NonNull public final NetworkRequestInfo mNetworkRequestInfo;
+            @NonNull public final NetworkRequest mOldNetworkRequest;
+            @NonNull public final NetworkRequest mNewNetworkRequest;
             @Nullable public final NetworkAgentInfo mOldNetwork;
             @Nullable public final NetworkAgentInfo mNewNetwork;
-            RequestReassignment(@NonNull final NetworkRequestInfo request,
+            RequestReassignment(@NonNull final NetworkRequestInfo networkRequestInfo,
+                    @NonNull final NetworkRequest oldNetworkRequest,
+                    @NonNull final NetworkRequest newNetworkRequest,
                     @Nullable final NetworkAgentInfo oldNetwork,
                     @Nullable final NetworkAgentInfo newNetwork) {
-                mRequest = request;
+                mNetworkRequestInfo = networkRequestInfo;
+                mOldNetworkRequest = oldNetworkRequest;
+                mNewNetworkRequest = newNetworkRequest;
                 mOldNetwork = oldNetwork;
                 mNewNetwork = newNetwork;
             }
 
             public String toString() {
-                return mRequest.mRequests.get(0).requestId + " : "
+                return mNetworkRequestInfo.mRequests.get(0).requestId + " : "
                         + (null != mOldNetwork ? mOldNetwork.network.getNetId() : "null")
                         + " → " + (null != mNewNetwork ? mNewNetwork.network.getNetId() : "null");
             }
@@ -7114,7 +7443,7 @@
                 // sure this stays true, but without imposing this expensive check on all
                 // reassignments on all user devices.
                 for (final RequestReassignment existing : mReassignments) {
-                    if (existing.mRequest.equals(reassignment.mRequest)) {
+                    if (existing.mNetworkRequestInfo.equals(reassignment.mNetworkRequestInfo)) {
                         throw new IllegalStateException("Trying to reassign ["
                                 + reassignment + "] but already have ["
                                 + existing + "]");
@@ -7129,7 +7458,7 @@
         @Nullable
         private RequestReassignment getReassignment(@NonNull final NetworkRequestInfo nri) {
             for (final RequestReassignment event : getRequestReassignments()) {
-                if (nri == event.mRequest) return event;
+                if (nri == event.mNetworkRequestInfo) return event;
             }
             return null;
         }
@@ -7156,67 +7485,121 @@
     }
 
     private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri,
+            @NonNull final NetworkRequest previousRequest,
+            @NonNull final NetworkRequest newRequest,
             @Nullable final NetworkAgentInfo previousSatisfier,
             @Nullable final NetworkAgentInfo newSatisfier,
             final long now) {
-        if (newSatisfier != null) {
+        if (null != newSatisfier && mNoServiceNetwork != newSatisfier) {
             if (VDBG) log("rematch for " + newSatisfier.toShortString());
-            if (previousSatisfier != null) {
+            if (null != previousSatisfier && mNoServiceNetwork != previousSatisfier) {
                 if (VDBG || DDBG) {
                     log("   accepting network in place of " + previousSatisfier.toShortString());
                 }
-                previousSatisfier.removeRequest(nri.request.requestId);
-                previousSatisfier.lingerRequest(nri.request.requestId, now, mLingerDelayMs);
+                previousSatisfier.removeRequest(previousRequest.requestId);
+                previousSatisfier.lingerRequest(previousRequest.requestId, now, mLingerDelayMs);
             } else {
                 if (VDBG || DDBG) log("   accepting network in place of null");
             }
-            newSatisfier.unlingerRequest(nri.request.requestId);
-            if (!newSatisfier.addRequest(nri.request)) {
-                Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has "
-                        + nri.request);
+
+            // To prevent constantly CPU wake up for nascent timer, if a network comes up
+            // and immediately satisfies a request then remove the timer. This will happen for
+            // all networks except in the case of an underlying network for a VCN.
+            if (newSatisfier.isNascent()) {
+                newSatisfier.unlingerRequest(NetworkRequest.REQUEST_ID_NONE);
             }
-        } else {
+
+            newSatisfier.unlingerRequest(newRequest.requestId);
+            if (!newSatisfier.addRequest(newRequest)) {
+                Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has "
+                        + newRequest);
+            }
+        } else if (null != previousSatisfier) {
             if (DBG) {
                 log("Network " + previousSatisfier.toShortString() + " stopped satisfying"
-                        + " request " + nri.request.requestId);
+                        + " request " + previousRequest.requestId);
             }
-            previousSatisfier.removeRequest(nri.request.requestId);
+            previousSatisfier.removeRequest(previousRequest.requestId);
         }
-        nri.mSatisfier = newSatisfier;
+        nri.setSatisfier(newSatisfier, newRequest);
     }
 
+    /**
+     * This function is triggered when something can affect what network should satisfy what
+     * request, and it computes the network reassignment from the passed collection of requests to
+     * network match to the one that the system should now have. That data is encoded in an
+     * object that is a list of changes, each of them having an NRI, and old satisfier, and a new
+     * satisfier.
+     *
+     * After the reassignment is computed, it is applied to the state objects.
+     *
+     * @param networkRequests the nri objects to evaluate for possible network reassignment
+     * @return NetworkReassignment listing of proposed network assignment changes
+     */
     @NonNull
-    private NetworkReassignment computeNetworkReassignment() {
-        ensureRunningOnConnectivityServiceThread();
+    private NetworkReassignment computeNetworkReassignment(
+            @NonNull final Collection<NetworkRequestInfo> networkRequests) {
         final NetworkReassignment changes = new NetworkReassignment();
 
         // Gather the list of all relevant agents and sort them by score.
         final ArrayList<NetworkAgentInfo> nais = new ArrayList<>();
         for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
-            if (!nai.everConnected) continue;
+            if (!nai.everConnected) {
+                continue;
+            }
             nais.add(nai);
         }
 
-        for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
-            if (nri.request.isListen()) continue;
-            final NetworkAgentInfo bestNetwork = mNetworkRanker.getBestNetwork(nri.request, nais);
-            if (bestNetwork != nri.mSatisfier) {
+        for (final NetworkRequestInfo nri : networkRequests) {
+            // Non-multilayer listen requests can be ignored.
+            if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) {
+                continue;
+            }
+            NetworkAgentInfo bestNetwork = null;
+            NetworkRequest bestRequest = null;
+            for (final NetworkRequest req : nri.mRequests) {
+                bestNetwork = mNetworkRanker.getBestNetwork(req, nais);
+                // Stop evaluating as the highest possible priority request is satisfied.
+                if (null != bestNetwork) {
+                    bestRequest = req;
+                    break;
+                }
+            }
+            if (null == bestNetwork && isDefaultBlocked(nri)) {
+                // Remove default networking if disallowed for managed default requests.
+                bestNetwork = mNoServiceNetwork;
+            }
+            if (nri.getSatisfier() != bestNetwork) {
                 // bestNetwork may be null if no network can satisfy this request.
                 changes.addRequestReassignment(new NetworkReassignment.RequestReassignment(
-                        nri, nri.mSatisfier, bestNetwork));
+                        nri, nri.mActiveRequest, bestRequest, nri.getSatisfier(), bestNetwork));
             }
         }
         return changes;
     }
 
+    private Set<NetworkRequestInfo> getNrisFromGlobalRequests() {
+        return new HashSet<>(mNetworkRequests.values());
+    }
+
     /**
-     * Attempt to rematch all Networks with NetworkRequests.  This may result in Networks
+     * Attempt to rematch all Networks with all NetworkRequests.  This may result in Networks
      * being disconnected.
      */
     private void rematchAllNetworksAndRequests() {
+        rematchNetworksAndRequests(getNrisFromGlobalRequests());
+    }
+
+    /**
+     * Attempt to rematch all Networks with given NetworkRequests.  This may result in Networks
+     * being disconnected.
+     */
+    private void rematchNetworksAndRequests(
+            @NonNull final Set<NetworkRequestInfo> networkRequests) {
+        ensureRunningOnConnectivityServiceThread();
         // TODO: This may be slow, and should be optimized.
         final long now = SystemClock.elapsedRealtime();
-        final NetworkReassignment changes = computeNetworkReassignment();
+        final NetworkReassignment changes = computeNetworkReassignment(networkRequests);
         if (VDBG || DDBG) {
             log(changes.debugString());
         } else if (DBG) {
@@ -7241,50 +7624,14 @@
         // the linger status.
         for (final NetworkReassignment.RequestReassignment event :
                 changes.getRequestReassignments()) {
-            updateSatisfiersForRematchRequest(event.mRequest, event.mOldNetwork,
-                    event.mNewNetwork, now);
+            updateSatisfiersForRematchRequest(event.mNetworkRequestInfo,
+                    event.mOldNetworkRequest, event.mNewNetworkRequest,
+                    event.mOldNetwork, event.mNewNetwork,
+                    now);
         }
 
-        final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork();
-        final NetworkRequestInfo defaultRequestInfo = mNetworkRequests.get(mDefaultRequest);
-        final NetworkReassignment.RequestReassignment reassignment =
-                changes.getReassignment(defaultRequestInfo);
-        final NetworkAgentInfo newDefaultNetwork =
-                null != reassignment ? reassignment.mNewNetwork : oldDefaultNetwork;
-
-        if (oldDefaultNetwork != newDefaultNetwork) {
-            if (oldDefaultNetwork != null) {
-                mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork);
-            }
-            mNetworkActivityTracker.updateDataActivityTracking(
-                    newDefaultNetwork, oldDefaultNetwork);
-            // Notify system services of the new default.
-            makeDefault(newDefaultNetwork);
-
-            // Log 0 -> X and Y -> X default network transitions, where X is the new default.
-            final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null;
-            final int score = (newDefaultNetwork != null) ? newDefaultNetwork.getCurrentScore() : 0;
-            final boolean validated = newDefaultNetwork != null && newDefaultNetwork.lastValidated;
-            final LinkProperties lp = (newDefaultNetwork != null)
-                    ? newDefaultNetwork.linkProperties : null;
-            final NetworkCapabilities nc = (newDefaultNetwork != null)
-                    ? newDefaultNetwork.networkCapabilities : null;
-
-            final Network prevNetwork = (oldDefaultNetwork != null)
-                    ? oldDefaultNetwork.network : null;
-            final int prevScore = (oldDefaultNetwork != null)
-                    ? oldDefaultNetwork.getCurrentScore() : 0;
-            final LinkProperties prevLp = (oldDefaultNetwork != null)
-                    ? oldDefaultNetwork.linkProperties : null;
-            final NetworkCapabilities prevNc = (oldDefaultNetwork != null)
-                    ? oldDefaultNetwork.networkCapabilities : null;
-
-            mMetricsLog.logDefaultNetworkEvent(network, score, validated, lp, nc,
-                    prevNetwork, prevScore, prevLp, prevNc);
-
-            // Have a new default network, release the transition wakelock in
-            scheduleReleaseNetworkTransitionWakelock();
-        }
+        // Process default network changes if applicable.
+        processDefaultNetworkChanges(changes);
 
         // Notify requested networks are available after the default net is switched, but
         // before LegacyTypeTracker sends legacy broadcasts
@@ -7294,29 +7641,29 @@
             // trying to connect if they know they cannot match it.
             // TODO - this could get expensive if there are a lot of outstanding requests for this
             // network. Think of a way to reduce this. Push netid->request mapping to each factory?
-            sendUpdatedScoreToFactories(event.mRequest.request, event.mNewNetwork);
+            sendUpdatedScoreToFactories(event);
 
             if (null != event.mNewNetwork) {
-                notifyNetworkAvailable(event.mNewNetwork, event.mRequest);
+                notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
             } else {
-                callCallbackForRequest(event.mRequest, event.mOldNetwork,
+                callCallbackForRequest(event.mNetworkRequestInfo, event.mOldNetwork,
                         ConnectivityManager.CALLBACK_LOST, 0);
             }
         }
 
-        // Update the linger state before processing listen callbacks, because the background
-        // computation depends on whether the network is lingering. Don't send the LOSING callbacks
+        // Update the inactivity state before processing listen callbacks, because the background
+        // computation depends on whether the network is inactive. Don't send the LOSING callbacks
         // just yet though, because they have to be sent after the listens are processed to keep
         // backward compatibility.
-        final ArrayList<NetworkAgentInfo> lingeredNetworks = new ArrayList<>();
+        final ArrayList<NetworkAgentInfo> inactiveNetworks = new ArrayList<>();
         for (final NetworkAgentInfo nai : nais) {
-            // Rematching may have altered the linger state of some networks, so update all linger
-            // timers. updateLingerState reads the state from the network agent and does nothing
-            // if the state has not changed : the source of truth is controlled with
-            // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been
-            // called while rematching the individual networks above.
-            if (updateLingerState(nai, now)) {
-                lingeredNetworks.add(nai);
+            // Rematching may have altered the inactivity state of some networks, so update all
+            // inactivity timers. updateInactivityState reads the state from the network agent
+            // and does nothing if the state has not changed : the source of truth is controlled
+            // with NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which
+            // have been called while rematching the individual networks above.
+            if (updateInactivityState(nai, now)) {
+                inactiveNetworks.add(nai);
             }
         }
 
@@ -7333,16 +7680,20 @@
             processNewlySatisfiedListenRequests(nai);
         }
 
-        for (final NetworkAgentInfo nai : lingeredNetworks) {
+        for (final NetworkAgentInfo nai : inactiveNetworks) {
+            // For nascent networks, if connecting with no foreground request, skip broadcasting
+            // LOSING for backward compatibility. This is typical when mobile data connected while
+            // wifi connected with mobile data always-on enabled.
+            if (nai.isNascent()) continue;
             notifyNetworkLosing(nai, now);
         }
 
-        updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais);
+        updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais);
 
         // Tear down all unneeded networks.
         for (NetworkAgentInfo nai : mNetworkAgentInfos) {
             if (unneeded(nai, UnneededFor.TEARDOWN)) {
-                if (nai.getLingerExpiry() > 0) {
+                if (nai.getInactivityExpiry() > 0) {
                     // This network has active linger timers and no requests, but is not
                     // lingering. Linger it.
                     //
@@ -7350,7 +7701,7 @@
                     // and became unneeded due to another network improving its score to the
                     // point where this network will no longer be able to satisfy any requests
                     // even if it validates.
-                    if (updateLingerState(nai, now)) {
+                    if (updateInactivityState(nai, now)) {
                         notifyNetworkLosing(nai, now);
                     }
                 } else {
@@ -7380,9 +7731,15 @@
     }
 
     private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
-            @Nullable final NetworkAgentInfo oldDefaultNetwork,
-            @Nullable final NetworkAgentInfo newDefaultNetwork,
+            @NonNull final NetworkReassignment changes,
             @NonNull final Collection<NetworkAgentInfo> nais) {
+        final NetworkReassignment.RequestReassignment reassignmentOfDefault =
+                changes.getReassignment(mDefaultRequest);
+        final NetworkAgentInfo oldDefaultNetwork =
+                null != reassignmentOfDefault ? reassignmentOfDefault.mOldNetwork : null;
+        final NetworkAgentInfo newDefaultNetwork =
+                null != reassignmentOfDefault ? reassignmentOfDefault.mNewNetwork : null;
+
         if (oldDefaultNetwork != newDefaultNetwork) {
             // Maintain the illusion : since the legacy API only understands one network at a time,
             // if the default network changed, apps should see a disconnected broadcast for the
@@ -7396,7 +7753,8 @@
                 // network doesn't satisfy the default request any more because it lost a
                 // capability.
                 mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0;
-                mLegacyTypeTracker.add(newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
+                mLegacyTypeTracker.add(
+                        newDefaultNetwork.networkInfo.getType(), newDefaultNetwork);
                 // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast
                 // to reflect the NetworkInfo of this new network. This broadcast has to be sent
                 // after the disconnect broadcasts above, but before the broadcasts sent by the
@@ -7448,7 +7806,7 @@
     private void updateInetCondition(NetworkAgentInfo nai) {
         // Don't bother updating until we've graduated to validated at least once.
         if (!nai.everValidated) return;
-        // For now only update icons for default connection.
+        // For now only update icons for the default connection.
         // TODO: Update WiFi and cellular icons separately. b/17237507
         if (!isDefaultNetwork(nai)) return;
 
@@ -7567,6 +7925,15 @@
             // doing.
             updateSignalStrengthThresholds(networkAgent, "CONNECT", null);
 
+            // Before first rematching networks, put an inactivity timer without any request, this
+            // allows {@code updateInactivityState} to update the state accordingly and prevent
+            // tearing down for any {@code unneeded} evaluation in this period.
+            // Note that the timer will not be rescheduled since the expiry time is
+            // fixed after connection regardless of the network satisfying other requests or not.
+            // But it will be removed as soon as the network satisfies a request for the first time.
+            networkAgent.lingerRequest(NetworkRequest.REQUEST_ID_NONE,
+                    SystemClock.elapsedRealtime(), mNascentDelayMs);
+
             // Consider network even though it is not yet validated.
             rematchAllNetworksAndRequests();
 
@@ -7620,7 +7987,7 @@
 
     // Notify the requests on this NAI that the network is now lingered.
     private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) {
-        final int lingerTime = (int) (nai.getLingerExpiry() - now);
+        final int lingerTime = (int) (nai.getInactivityExpiry() - now);
         notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
     }
 
@@ -7718,8 +8085,8 @@
                 intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo());
             }
             NetworkAgentInfo newDefaultAgent = null;
-            if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
-                newDefaultAgent = getDefaultNetwork();
+            if (nai.isSatisfyingRequest(mDefaultRequest.mRequests.get(0).requestId)) {
+                newDefaultAgent = mDefaultRequest.getSatisfier();
                 if (newDefaultAgent != null) {
                     intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
                             newDefaultAgent.networkInfo);
@@ -7766,10 +8133,15 @@
      */
     private Network[] getDefaultNetworks() {
         ensureRunningOnConnectivityServiceThread();
-        ArrayList<Network> defaultNetworks = new ArrayList<>();
-        NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+        final ArrayList<Network> defaultNetworks = new ArrayList<>();
+        final Set<Integer> activeNetIds = new ArraySet<>();
+        for (final NetworkRequestInfo nri : mDefaultNetworkRequests) {
+            if (nri.isBeingSatisfied()) {
+                activeNetIds.add(nri.getSatisfier().network().netId);
+            }
+        }
         for (NetworkAgentInfo nai : mNetworkAgentInfos) {
-            if (nai.everConnected && (nai == defaultNetwork || nai.isVPN())) {
+            if (nai.everConnected && (activeNetIds.contains(nai.network().netId) || nai.isVPN())) {
                 defaultNetworks.add(nai.network);
             }
         }
@@ -7788,10 +8160,10 @@
             activeIface = activeLinkProperties.getInterfaceName();
         }
 
-        final VpnInfo[] vpnInfos = getAllVpnInfo();
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos = getAllVpnInfo();
         try {
-            mStatsService.forceUpdateIfaces(
-                    getDefaultNetworks(), getAllNetworkState(), activeIface, vpnInfos);
+            mStatsService.forceUpdateIfaces(getDefaultNetworks(), getAllNetworkState(), activeIface,
+                    underlyingNetworkInfos);
         } catch (Exception ignored) {
         }
     }
@@ -7819,7 +8191,6 @@
         int user = UserHandle.getUserId(mDeps.getCallingUid());
         final boolean success;
         synchronized (mVpns) {
-            throwIfLockdownEnabled();
             success = mVpns.get(user).setUnderlyingNetworks(networks);
         }
         return success;
@@ -8051,6 +8422,7 @@
         return getVpnIfOwner(mDeps.getCallingUid());
     }
 
+    // TODO: stop calling into Vpn.java and get this information from data in this class.
     @GuardedBy("mVpns")
     private Vpn getVpnIfOwner(int uid) {
         final int user = UserHandle.getUserId(uid);
@@ -8059,7 +8431,7 @@
         if (vpn == null) {
             return null;
         } else {
-            final VpnInfo info = vpn.getVpnInfo();
+            final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo();
             return (info == null || info.ownerUid != uid) ? null : vpn;
         }
     }
@@ -8101,7 +8473,7 @@
             throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
         }
 
-        final int uid = InetDiagMessage.getConnectionOwnerUid(connectionInfo.protocol,
+        final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol,
                 connectionInfo.local, connectionInfo.remote);
 
         /* Filter out Uids not associated with the VPN. */
@@ -8752,6 +9124,7 @@
             }
         }
     }
+
     /**
      * Registers {@link QosSocketFilter} with {@link IQosCallback}.
      *
@@ -8801,4 +9174,10 @@
     public void unregisterQosCallback(@NonNull final IQosCallback callback) {
         mQosCallbackTracker.unregisterCallback(callback);
     }
+
+    @Override
+    public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) {
+        // TODO http://b/176495594 track multiple default networks with networkPreferences
+        if (DBG) log("setOemNetworkPreference() called with: " + preference.toString());
+    }
 }
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index f2b63a6..88ce220 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -22,7 +22,6 @@
 import android.gsi.GsiProgress;
 import android.gsi.IGsiService;
 import android.gsi.IGsiServiceCallback;
-import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -30,7 +29,7 @@
 import android.os.UserHandle;
 import android.os.image.IDynamicSystemService;
 import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
 import android.util.Slog;
 
 import java.io.File;
@@ -88,16 +87,17 @@
         String path = SystemProperties.get("os.aot.path");
         if (path.isEmpty()) {
             final int userId = UserHandle.myUserId();
-            final StorageVolume[] volumes =
-                    StorageManager.getVolumeList(userId, StorageManager.FLAG_FOR_WRITE);
-            for (StorageVolume volume : volumes) {
-                if (volume.isEmulated()) continue;
-                if (!volume.isRemovable()) continue;
-                if (!Environment.MEDIA_MOUNTED.equals(volume.getState())) continue;
-                File sdCard = volume.getPathFile();
-                if (sdCard.isDirectory()) {
-                    path = new File(sdCard, dsuSlot).getPath();
-                    break;
+            final StorageManager sm = mContext.getSystemService(StorageManager.class);
+            for (VolumeInfo volume : sm.getVolumes()) {
+                if (volume.getType() != volume.TYPE_PUBLIC) {
+                    continue;
+                }
+                if (!volume.isMountedWritable()) {
+                    continue;
+                }
+                File sd_internal = volume.getInternalPathForUser(userId);
+                if (sd_internal != null) {
+                    path = new File(sd_internal, dsuSlot).getPath();
                 }
             }
             if (path.isEmpty()) {
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index b595eb1..9f91dd6 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package com.android.server
 
@@ -173,6 +173,10 @@
 3120 pm_critical_info (msg|3)
 # Disk usage stats for verifying quota correctness
 3121 pm_package_stats (manual_time|2|3),(quota_time|2|3),(manual_data|2|2),(quota_data|2|2),(manual_cache|2|2),(quota_cache|2|2)
+# Snapshot statistics
+3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(quick_rebuilds|1|1),(max_build_time|1|3),(cumm_build_time|1|3)
+# Snapshot rebuild instance
+3131 pm_snapshot_rebuild (build_time|1|3),(elapsed|1|3)
 
 # ---------------------------
 # InputMethodManagerService.java
diff --git a/services/core/java/com/android/server/LockGuard.java b/services/core/java/com/android/server/LockGuard.java
index 5ce16c4..b894f34 100644
--- a/services/core/java/com/android/server/LockGuard.java
+++ b/services/core/java/com/android/server/LockGuard.java
@@ -22,8 +22,6 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
-import com.android.internal.os.BackgroundThread;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
@@ -74,8 +72,9 @@
     public static final int INDEX_PACKAGES = 3;
     public static final int INDEX_STORAGE = 4;
     public static final int INDEX_WINDOW = 5;
-    public static final int INDEX_ACTIVITY = 6;
-    public static final int INDEX_DPMS = 7;
+    public static final int INDEX_PROC = 6;
+    public static final int INDEX_ACTIVITY = 7;
+    public static final int INDEX_DPMS = 8;
 
     private static Object[] sKnownFixed = new Object[INDEX_DPMS + 1];
 
@@ -229,6 +228,7 @@
             case INDEX_PACKAGES: return "PACKAGES";
             case INDEX_STORAGE: return "STORAGE";
             case INDEX_WINDOW: return "WINDOW";
+            case INDEX_PROC: return "PROCESS";
             case INDEX_ACTIVITY: return "ACTIVITY";
             case INDEX_DPMS: return "DPMS";
             default: return Integer.toString(index);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 8b506ba..41903fc 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -372,10 +372,12 @@
      * even from a previous boot.
      */
     public void unregisterHealthObserver(PackageHealthObserver observer) {
-        synchronized (mLock) {
-            mAllObservers.remove(observer.getName());
-        }
-        syncState("unregistering observer: " + observer.getName());
+        mLongTaskHandler.post(() -> {
+            synchronized (mLock) {
+                mAllObservers.remove(observer.getName());
+            }
+            syncState("unregistering observer: " + observer.getName());
+        });
     }
 
     /**
@@ -982,7 +984,11 @@
                     if (!DeviceConfig.NAMESPACE_ROLLBACK.equals(properties.getNamespace())) {
                         return;
                     }
-                    updateConfigs();
+                    try {
+                        updateConfigs();
+                    } catch (Exception ignore) {
+                        Slog.w(TAG, "Failed to reload device config changes");
+                    }
                 });
     }
 
@@ -990,7 +996,8 @@
      * Health check is enabled or disabled after reading the flags
      * from DeviceConfig.
      */
-    private void updateConfigs() {
+    @VisibleForTesting
+    void updateConfigs() {
         synchronized (mLock) {
             mTriggerFailureCount = DeviceConfig.getInt(
                     DeviceConfig.NAMESPACE_ROLLBACK,
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index cd6a9fb..7d65156 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -48,6 +48,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
@@ -482,14 +483,21 @@
         }
     }
 
-    private @Nullable VolumeInfo findStorageForUuid(String volumeUuid) {
+    private @Nullable VolumeInfo findStorageForUuidAsUser(String volumeUuid,
+            @UserIdInt int userId) {
         final StorageManager storage = mContext.getSystemService(StorageManager.class);
         if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
-            return storage.findVolumeById(VolumeInfo.ID_EMULATED_INTERNAL + ";" + 0);
+            return storage.findVolumeById(VolumeInfo.ID_EMULATED_INTERNAL + ";" + userId);
         } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
             return storage.getPrimaryPhysicalVolume();
         } else {
-            return storage.findEmulatedForPrivate(storage.findVolumeByUuid(volumeUuid));
+            VolumeInfo info = storage.findVolumeByUuid(volumeUuid);
+            if (info == null) {
+                Slog.w(TAG, "findStorageForUuidAsUser cannot find volumeUuid:" + volumeUuid);
+                return null;
+            }
+            String emulatedUuid = info.getId().replace("private", "emulated") + ";" + userId;
+            return storage.findVolumeById(emulatedUuid);
         }
     }
 
@@ -2605,8 +2613,9 @@
                 return;
 
             } else {
-                from = findStorageForUuid(mPrimaryStorageUuid);
-                to = findStorageForUuid(volumeUuid);
+                int currentUserId = mCurrentUserId;
+                from = findStorageForUuidAsUser(mPrimaryStorageUuid, currentUserId);
+                to = findStorageForUuidAsUser(volumeUuid, currentUserId);
 
                 if (from == null) {
                     Slog.w(TAG, "Failing move due to missing from volume " + mPrimaryStorageUuid);
@@ -3136,6 +3145,12 @@
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
 
         if (isFsEncrypted) {
+            // When a user has secure lock screen, require secret to actually unlock.
+            // This check is mostly in place for emulation mode.
+            if (StorageManager.isFileEncryptedEmulatedOnly() &&
+                mLockPatternUtils.isSecure(userId) && ArrayUtils.isEmpty(secret)) {
+                throw new IllegalStateException("Secret required to unlock secure user " + userId);
+            }
             try {
                 mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
                         encodeBytes(secret));
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index e8687e5..96f832d 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -32,6 +32,7 @@
 import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
+import android.net.NetworkStack;
 import android.net.RouteInfo;
 import android.net.StringNetworkSpecifier;
 import android.net.TestNetworkInterface;
@@ -48,6 +49,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.NetworkStackConstants;
 
 import java.io.UncheckedIOException;
 import java.net.Inet4Address;
@@ -242,6 +245,7 @@
         nc.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
         nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
         nc.setNetworkSpecifier(new StringNetworkSpecifier(iface));
         nc.setAdministratorUids(administratorUids);
         if (!isMetered) {
@@ -277,10 +281,12 @@
 
         // Add global routes (but as non-default, non-internet providing network)
         if (allowIPv4) {
-            lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null, iface));
+            lp.addRoute(new RouteInfo(new IpPrefix(
+                    NetworkStackConstants.IPV4_ADDR_ANY, 0), null, iface));
         }
         if (allowIPv6) {
-            lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null, iface));
+            lp.addRoute(new RouteInfo(new IpPrefix(
+                    NetworkStackConstants.IPV6_ADDR_ANY, 0), null, iface));
         }
 
         final TestNetworkAgent agent = new TestNetworkAgent(context, looper, nc, lp,
@@ -316,10 +322,10 @@
         }
 
         try {
-            // This requires NETWORK_STACK privileges.
             final long token = Binder.clearCallingIdentity();
             try {
-                mNMS.setInterfaceUp(iface);
+                NetworkStack.checkNetworkStackPermission(mContext);
+                NetdUtils.setInterfaceUp(mNetd, iface);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 76db019..4dce59f 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -25,9 +25,14 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.wifi.WifiInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -61,6 +66,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -285,8 +291,16 @@
         public Vcn newVcn(
                 @NonNull VcnContext vcnContext,
                 @NonNull ParcelUuid subscriptionGroup,
-                @NonNull VcnConfig config) {
-            return new Vcn(vcnContext, subscriptionGroup, config);
+                @NonNull VcnConfig config,
+                @NonNull TelephonySubscriptionSnapshot snapshot,
+                @NonNull VcnSafemodeCallback safemodeCallback) {
+            return new Vcn(vcnContext, subscriptionGroup, config, snapshot, safemodeCallback);
+        }
+
+        /** Gets the subId indicated by the given {@link WifiInfo}. */
+        public int getSubIdForWifiInfo(@NonNull WifiInfo wifiInfo) {
+            // TODO(b/178501049): use the subId indicated by WifiInfo#getSubscriptionId
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         }
     }
 
@@ -371,6 +385,7 @@
                 // delay)
                 for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
                     final VcnConfig config = mConfigs.get(entry.getKey());
+
                     if (config == null
                             || !snapshot.packageHasPermissionsForSubscriptionGroup(
                                     entry.getKey(), config.getProvisioningPackageName())) {
@@ -384,10 +399,13 @@
                                 // correct instance is torn down. This could happen as a result of a
                                 // Carrier App manually removing/adding a VcnConfig.
                                 if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
-                                    mVcns.remove(uuidToTeardown).teardownAsynchronously();
+                                    stopVcnLocked(uuidToTeardown);
                                 }
                             }
                         }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+                    } else {
+                        // If this VCN's status has not changed, update it with the new snapshot
+                        entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
                     }
                 }
             }
@@ -395,14 +413,44 @@
     }
 
     @GuardedBy("mLock")
+    private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) {
+        final Vcn vcnToTeardown = mVcns.remove(uuidToTeardown);
+        if (vcnToTeardown == null) {
+            return;
+        }
+
+        vcnToTeardown.teardownAsynchronously();
+
+        // Now that the VCN is removed, notify all registered listeners to refresh their
+        // UnderlyingNetworkPolicy.
+        notifyAllPolicyListenersLocked();
+    }
+
+    @GuardedBy("mLock")
+    private void notifyAllPolicyListenersLocked() {
+        for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) {
+            Binder.withCleanCallingIdentity(() -> policyListener.mListener.onPolicyChanged());
+        }
+    }
+
+    @GuardedBy("mLock")
     private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) {
         Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup);
 
         // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active
         //                    VCN.
 
-        final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config);
+        final VcnSafemodeCallbackImpl safemodeCallback =
+                new VcnSafemodeCallbackImpl(subscriptionGroup);
+
+        final Vcn newInstance =
+                mDeps.newVcn(
+                        mVcnContext, subscriptionGroup, config, mLastSnapshot, safemodeCallback);
         mVcns.put(subscriptionGroup, newInstance);
+
+        // Now that a new VCN has started, notify all registered listeners to refresh their
+        // UnderlyingNetworkPolicy.
+        notifyAllPolicyListenersLocked();
     }
 
     @GuardedBy("mLock")
@@ -465,9 +513,7 @@
             synchronized (mLock) {
                 mConfigs.remove(subscriptionGroup);
 
-                if (mVcns.containsKey(subscriptionGroup)) {
-                    mVcns.remove(subscriptionGroup).teardownAsynchronously();
-                }
+                stopVcnLocked(subscriptionGroup);
 
                 writeConfigsToDiskLocked();
             }
@@ -497,7 +543,7 @@
         }
     }
 
-    /** Get current configuration list for testing purposes */
+    /** Get current VCNs for testing purposes */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public Map<ParcelUuid, Vcn> getAllVcns() {
         synchronized (mLock) {
@@ -527,7 +573,7 @@
             @NonNull IVcnUnderlyingNetworkPolicyListener listener) {
         requireNonNull(listener, "listener was null");
 
-        mContext.enforceCallingPermission(
+        mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.NETWORK_FACTORY,
                 "Must have permission NETWORK_FACTORY to register a policy listener");
 
@@ -561,4 +607,82 @@
             }
         }
     }
+
+    /**
+     * Gets the UnderlyingNetworkPolicy as determined by the provided NetworkCapabilities and
+     * LinkProperties.
+     */
+    @NonNull
+    @Override
+    public VcnUnderlyingNetworkPolicy getUnderlyingNetworkPolicy(
+            @NonNull NetworkCapabilities networkCapabilities,
+            @NonNull LinkProperties linkProperties) {
+        requireNonNull(networkCapabilities, "networkCapabilities was null");
+        requireNonNull(linkProperties, "linkProperties was null");
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.NETWORK_FACTORY,
+                "Must have permission NETWORK_FACTORY or be the SystemServer to get underlying"
+                        + " Network policies");
+
+        // Defensive copy in case this call is in-process and the given NetworkCapabilities mutates
+        networkCapabilities = new NetworkCapabilities(networkCapabilities);
+
+        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                && networkCapabilities.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
+            TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                    (TelephonyNetworkSpecifier) networkCapabilities.getNetworkSpecifier();
+            subId = telephonyNetworkSpecifier.getSubscriptionId();
+        } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+                && networkCapabilities.getTransportInfo() instanceof WifiInfo) {
+            WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
+            subId = mDeps.getSubIdForWifiInfo(wifiInfo);
+        }
+
+        boolean isVcnManagedNetwork = false;
+        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            synchronized (mLock) {
+                ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId);
+
+                Vcn vcn = mVcns.get(subGroup);
+                if (vcn != null && vcn.isActive()) {
+                    isVcnManagedNetwork = true;
+                }
+            }
+        }
+        if (isVcnManagedNetwork) {
+            networkCapabilities.removeCapability(
+                    NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+        }
+
+        return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities);
+    }
+
+    /** Callback for signalling when a Vcn has entered Safemode. */
+    public interface VcnSafemodeCallback {
+        /** Called by a Vcn to signal that it has entered Safemode. */
+        void onEnteredSafemode();
+    }
+
+    /** VcnSafemodeCallback is used by Vcns to notify VcnManagementService on entering Safemode. */
+    private class VcnSafemodeCallbackImpl implements VcnSafemodeCallback {
+        @NonNull private final ParcelUuid mSubGroup;
+
+        private VcnSafemodeCallbackImpl(@NonNull final ParcelUuid subGroup) {
+            mSubGroup = Objects.requireNonNull(subGroup, "Missing subGroup");
+        }
+
+        @Override
+        public void onEnteredSafemode() {
+            synchronized (mLock) {
+                // Ignore if this subscription group doesn't exist anymore
+                if (!mVcns.containsKey(mSubGroup)) {
+                    return;
+                }
+
+                notifyAllPolicyListenersLocked();
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java
index eca1dfa..6a816af 100644
--- a/services/core/java/com/android/server/VibratorManagerService.java
+++ b/services/core/java/com/android/server/VibratorManagerService.java
@@ -22,12 +22,20 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.vibrator.IVibrator;
+import android.os.BatteryStats;
+import android.os.Binder;
 import android.os.CombinedVibrationEffect;
+import android.os.ExternalVibration;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IExternalVibratorService;
 import android.os.IVibratorManagerService;
+import android.os.IVibratorStateListener;
 import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.Trace;
@@ -37,12 +45,17 @@
 import android.os.VibratorInfo;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.DumpUtils;
+import com.android.server.vibrator.InputDeviceDelegate;
 import com.android.server.vibrator.Vibration;
 import com.android.server.vibrator.VibrationScaler;
 import com.android.server.vibrator.VibrationSettings;
+import com.android.server.vibrator.VibrationThread;
 import com.android.server.vibrator.VibratorController;
 
 import libcore.util.NativeAllocationRegistry;
@@ -50,7 +63,11 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -83,18 +100,32 @@
         }
     }
 
+    // Used to generate globally unique vibration ids.
+    private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback
+
     private final Object mLock = new Object();
     private final Context mContext;
+    private final PowerManager.WakeLock mWakeLock;
+    private final IBatteryStats mBatteryStatsService;
     private final Handler mHandler;
     private final AppOpsManager mAppOps;
     private final NativeWrapper mNativeWrapper;
+    private final VibratorManagerRecords mVibratorManagerRecords;
     private final int[] mVibratorIds;
     private final SparseArray<VibratorController> mVibrators;
+    private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks();
     @GuardedBy("mLock")
     private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
+    @GuardedBy("mLock")
+    private VibrationThread mCurrentVibration;
+    @GuardedBy("mLock")
+    private VibrationThread mNextVibration;
+    @GuardedBy("mLock")
+    private ExternalVibrationHolder mCurrentExternalVibration;
 
     private VibrationSettings mVibrationSettings;
     private VibrationScaler mVibrationScaler;
+    private InputDeviceDelegate mInputDeviceDelegate;
 
     static native long nativeInit();
 
@@ -109,8 +140,19 @@
         mNativeWrapper = injector.getNativeWrapper();
         mNativeWrapper.init();
 
+        int dumpLimit = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_previousVibrationsDumpLimit);
+        mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+
+        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                BatteryStats.SERVICE_NAME));
+
         mAppOps = mContext.getSystemService(AppOpsManager.class);
 
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
+        mWakeLock.setReferenceCounted(true);
+
         int[] vibratorIds = mNativeWrapper.getVibratorIds();
         if (vibratorIds == null) {
             mVibratorIds = new int[0];
@@ -134,6 +176,7 @@
         try {
             mVibrationSettings = new VibrationSettings(mContext, mHandler);
             mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
+            mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
 
             mVibrationSettings.addListener(this::updateServiceState);
 
@@ -145,6 +188,11 @@
     }
 
     @Override // Binder call
+    public int[] getVibratorIds() {
+        return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+    }
+
+    @Override // Binder call
     @Nullable
     public VibratorInfo getVibratorInfo(int vibratorId) {
         VibratorController controller = mVibrators.get(vibratorId);
@@ -152,8 +200,37 @@
     }
 
     @Override // Binder call
-    public int[] getVibratorIds() {
-        return Arrays.copyOf(mVibratorIds, mVibratorIds.length);
+    public boolean isVibrating(int vibratorId) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+                "isVibrating");
+        VibratorController controller = mVibrators.get(vibratorId);
+        return controller != null && controller.isVibrating();
+    }
+
+    @Override // Binder call
+    public boolean registerVibratorStateListener(int vibratorId, IVibratorStateListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+                "registerVibratorStateListener");
+        VibratorController controller = mVibrators.get(vibratorId);
+        if (controller == null) {
+            return false;
+        }
+        return controller.registerVibratorStateListener(listener);
+    }
+
+    @Override // Binder call
+    public boolean unregisterVibratorStateListener(int vibratorId,
+            IVibratorStateListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_VIBRATOR_STATE,
+                "unregisterVibratorStateListener");
+        VibratorController controller = mVibrators.get(vibratorId);
+        if (controller == null) {
+            return false;
+        }
+        return controller.unregisterVibratorStateListener(listener);
     }
 
     @Override // Binder call
@@ -161,9 +238,10 @@
             @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attrs) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "setAlwaysOnEffect");
         try {
-            if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
-                throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
-            }
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.VIBRATE_ALWAYS_ON,
+                    "setAlwaysOnEffect");
+
             if (effect == null) {
                 synchronized (mLock) {
                     mAlwaysOnEffects.delete(alwaysOnId);
@@ -200,12 +278,113 @@
     @Override // Binder call
     public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
             @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        throw new UnsupportedOperationException("Not implemented");
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
+        try {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
+
+            if (token == null) {
+                Slog.e(TAG, "token must not be null");
+                return;
+            }
+            enforceUpdateAppOpsStatsPermission(uid);
+            if (!isEffectValid(effect)) {
+                return;
+            }
+            effect = fixupVibrationEffect(effect);
+            attrs = fixupVibrationAttributes(attrs);
+            Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
+                    uid, opPkg, reason);
+
+            synchronized (mLock) {
+                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
+                if (ignoreStatus != null) {
+                    endVibrationLocked(vib, ignoreStatus);
+                    return;
+                }
+
+                VibrationThread vibThread = new VibrationThread(vib, mVibrators, mWakeLock,
+                        mBatteryStatsService, mVibrationCallbacks);
+
+                ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vibThread);
+                if (ignoreStatus != null) {
+                    endVibrationLocked(vib, ignoreStatus);
+                    return;
+                }
+
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    if (mCurrentVibration != null) {
+                        mCurrentVibration.cancel();
+                    }
+                    Vibration.Status status = startVibrationLocked(vibThread);
+                    if (status != Vibration.Status.RUNNING) {
+                        endVibrationLocked(vib, status);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
     }
 
     @Override // Binder call
     public void cancelVibrate(IBinder token) {
-        throw new UnsupportedOperationException("Not implemented");
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
+        try {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.VIBRATE,
+                    "cancelVibrate");
+
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Canceling vibration.");
+                }
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mNextVibration = null;
+                    if (mCurrentVibration != null
+                            && mCurrentVibration.getVibration().token == token) {
+                        mCurrentVibration.cancel();
+                    }
+                    if (mCurrentExternalVibration != null) {
+                        mCurrentExternalVibration.end(Vibration.Status.CANCELLED);
+                        mVibratorManagerRecords.record(mCurrentExternalVibration);
+                        mCurrentExternalVibration.externalVibration.mute();
+                        mCurrentExternalVibration = null;
+                        // TODO(b/167946816): set external control to false
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+        final long ident = Binder.clearCallingIdentity();
+
+        boolean isDumpProto = false;
+        for (String arg : args) {
+            if (arg.equals("--proto")) {
+                isDumpProto = true;
+            }
+        }
+        try {
+            if (isDumpProto) {
+                mVibratorManagerRecords.dumpProto(fd);
+            } else {
+                mVibratorManagerRecords.dumpText(pw);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 
     @Override
@@ -214,11 +393,24 @@
         new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver);
     }
 
-    private void updateServiceState() {
+    @VisibleForTesting
+    void updateServiceState() {
         synchronized (mLock) {
+            boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators(
+                    mVibrationSettings.shouldVibrateInputDevices());
+
             for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
                 updateAlwaysOnLocked(mAlwaysOnEffects.valueAt(i));
             }
+
+            if (mCurrentVibration == null) {
+                return;
+            }
+
+            if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode(
+                    mCurrentVibration.getVibration().attrs.getUsage())) {
+                mCurrentVibration.cancel();
+            }
         }
     }
 
@@ -230,9 +422,9 @@
             if (vibrator == null) {
                 continue;
             }
-            Vibration.Status ignoredStatus = shouldIgnoreVibrationLocked(
+            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
                     vib.uid, vib.opPkg, vib.attrs);
-            if (ignoredStatus == null) {
+            if (ignoreStatus == null) {
                 effect = mVibrationScaler.scale(effect, vib.attrs.getUsage());
             } else {
                 // Vibration should not run, use null effect to remove registered effect.
@@ -242,6 +434,135 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private Vibration.Status startVibrationLocked(VibrationThread vibThread) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
+        try {
+            Vibration vib = vibThread.getVibration();
+            vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
+
+            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
+                    vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
+
+            if (inputDevicesAvailable) {
+                return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
+            }
+
+            if (mCurrentVibration == null) {
+                return startVibrationThreadLocked(vibThread);
+            }
+
+            mNextVibration = vibThread;
+            return Vibration.Status.RUNNING;
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private Vibration.Status startVibrationThreadLocked(VibrationThread vibThread) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
+        try {
+            Vibration vib = vibThread.getVibration();
+            int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
+            switch (mode) {
+                case AppOpsManager.MODE_ALLOWED:
+                    Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+                    mCurrentVibration = vibThread;
+                    mCurrentVibration.start();
+                    return Vibration.Status.RUNNING;
+                case AppOpsManager.MODE_ERRORED:
+                    Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
+                    return Vibration.Status.IGNORED_ERROR_APP_OPS;
+                default:
+                    return Vibration.Status.IGNORED_APP_OPS;
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void endVibrationLocked(Vibration vib, Vibration.Status status) {
+        vib.end(status);
+        mVibratorManagerRecords.record(vib);
+    }
+
+    @GuardedBy("mLock")
+    private void reportFinishedVibrationLocked(Vibration.Status status) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
+        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+        try {
+            Vibration vib = mCurrentVibration.getVibration();
+            mCurrentVibration = null;
+            endVibrationLocked(vib, status);
+            finishAppOpModeLocked(vib.uid, vib.opPkg);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    private void onVibrationComplete(int vibratorId, long vibrationId) {
+        synchronized (mLock) {
+            if (mCurrentVibration != null && mCurrentVibration.getVibration().id == vibrationId) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+                            + " complete, notifying thread");
+                }
+                mCurrentVibration.vibratorComplete(vibratorId);
+            }
+        }
+    }
+
+    /**
+     * Check if given vibration should be ignored in favour of one of the vibrations currently
+     * running on the same vibrators.
+     *
+     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private Vibration.Status shouldIgnoreVibrationForCurrentLocked(VibrationThread vibThread) {
+        if (vibThread.getVibration().isRepeating()) {
+            // Repeating vibrations always take precedence.
+            return null;
+        }
+        if (mCurrentVibration != null && mCurrentVibration.getVibration().isRepeating()) {
+            if (DEBUG) {
+                Slog.d(TAG, "Ignoring incoming vibration in favor of previous alarm vibration");
+            }
+            return Vibration.Status.IGNORED_FOR_ALARM;
+        }
+        return null;
+    }
+
+    /**
+     * Check if given vibration should be ignored by this service.
+     *
+     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored.
+     * @see #shouldIgnoreVibrationLocked(int, String, VibrationAttributes)
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private Vibration.Status shouldIgnoreVibrationLocked(Vibration vib) {
+        // If something has external control of the vibrator, assume that it's more important.
+        if (mCurrentExternalVibration != null) {
+            if (DEBUG) {
+                Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+            }
+            return Vibration.Status.IGNORED_FOR_EXTERNAL;
+        }
+
+        if (!mVibrationSettings.shouldVibrateForUid(vib.uid, vib.attrs.getUsage())) {
+            Slog.e(TAG, "Ignoring incoming vibration as process with"
+                    + " uid= " + vib.uid + " is background,"
+                    + " attrs= " + vib.attrs);
+            return Vibration.Status.IGNORED_BACKGROUND;
+        }
+
+        return shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
+    }
+
     /**
      * Check if a vibration with given {@code uid}, {@code opPkg} and {@code attrs} should be
      * ignored by this service.
@@ -271,7 +592,7 @@
             return Vibration.Status.IGNORED_RINGTONE;
         }
 
-        int mode = getAppOpMode(uid, opPkg, attrs);
+        int mode = checkAppOpModeLocked(uid, opPkg, attrs);
         if (mode != AppOpsManager.MODE_ALLOWED) {
             if (mode == AppOpsManager.MODE_ERRORED) {
                 // We might be getting calls from within system_server, so we don't actually
@@ -290,21 +611,49 @@
      * Check which mode should be set for a vibration with given {@code uid}, {@code opPkg} and
      * {@code attrs}. This will return one of the AppOpsManager.MODE_*.
      */
-    private int getAppOpMode(int uid, String opPkg, VibrationAttributes attrs) {
+    @GuardedBy("mLock")
+    private int checkAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
         int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
                 attrs.getAudioUsage(), uid, opPkg);
-        if (mode == AppOpsManager.MODE_ALLOWED) {
-            mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg);
-        }
-        if (mode == AppOpsManager.MODE_IGNORED
-                && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+        int fixedMode = fixupAppOpModeLocked(mode, attrs);
+        if (mode != fixedMode && fixedMode == AppOpsManager.MODE_ALLOWED) {
             // If we're just ignoring the vibration op then this is set by DND and we should ignore
             // if we're asked to bypass. AppOps won't be able to record this operation, so make
             // sure we at least note it in the logs for debugging.
             Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid);
-            mode = AppOpsManager.MODE_ALLOWED;
         }
-        return mode;
+        return fixedMode;
+    }
+
+    /** Start an operation in {@link AppOpsManager}, if allowed. */
+    @GuardedBy("mLock")
+    private int startAppOpModeLocked(int uid, String opPkg, VibrationAttributes attrs) {
+        return fixupAppOpModeLocked(
+                mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, opPkg), attrs);
+    }
+
+    /**
+     * Finish a previously started operation in {@link AppOpsManager}. This will be a noop if no
+     * operation with same uid was previously started.
+     */
+    @GuardedBy("mLock")
+    private void finishAppOpModeLocked(int uid, String opPkg) {
+        mAppOps.finishOp(AppOpsManager.OP_VIBRATE, uid, opPkg);
+    }
+
+    /**
+     * Enforces {@link android.Manifest.permission#UPDATE_APP_OPS_STATS} to incoming UID if it's
+     * different from the calling UID.
+     */
+    private void enforceUpdateAppOpsStatsPermission(int uid) {
+        if (uid == Binder.getCallingUid()) {
+            return;
+        }
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
     /**
@@ -330,6 +679,49 @@
     }
 
     /**
+     * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link
+     * VibrationSettings#getFallbackEffect}.
+     */
+    private CombinedVibrationEffect fixupVibrationEffect(CombinedVibrationEffect effect) {
+        if (effect instanceof CombinedVibrationEffect.Mono) {
+            return CombinedVibrationEffect.createSynced(
+                    fixupVibrationEffect(((CombinedVibrationEffect.Mono) effect).getEffect()));
+        } else if (effect instanceof CombinedVibrationEffect.Stereo) {
+            CombinedVibrationEffect.SyncedCombination combination =
+                    CombinedVibrationEffect.startSynced();
+            SparseArray<VibrationEffect> effects =
+                    ((CombinedVibrationEffect.Stereo) effect).getEffects();
+            for (int i = 0; i < effects.size(); i++) {
+                combination.addVibrator(effects.keyAt(i), fixupVibrationEffect(effects.valueAt(i)));
+            }
+            return combination.combine();
+        } else if (effect instanceof CombinedVibrationEffect.Sequential) {
+            CombinedVibrationEffect.SequentialCombination combination =
+                    CombinedVibrationEffect.startSequential();
+            List<CombinedVibrationEffect> effects =
+                    ((CombinedVibrationEffect.Sequential) effect).getEffects();
+            for (CombinedVibrationEffect e : effects) {
+                combination.addNext(fixupVibrationEffect(e));
+            }
+            return combination.combine();
+        }
+        return effect;
+    }
+
+    private VibrationEffect fixupVibrationEffect(VibrationEffect effect) {
+        if (effect instanceof VibrationEffect.Prebaked
+                && ((VibrationEffect.Prebaked) effect).shouldFallback()) {
+            VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+            VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId());
+            if (fallback != null) {
+                return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(),
+                        fallback);
+            }
+        }
+        return effect;
+    }
+
+    /**
      * Return new {@link VibrationAttributes} that only applies flags that this user has permissions
      * to use.
      */
@@ -388,6 +780,19 @@
         }
     }
 
+    /**
+     * Check given mode, one of the AppOpsManager.MODE_*, against {@link VibrationAttributes} to
+     * allow bypassing {@link AppOpsManager} checks.
+     */
+    @GuardedBy("mLock")
+    private int fixupAppOpModeLocked(int mode, VibrationAttributes attrs) {
+        if (mode == AppOpsManager.MODE_IGNORED
+                && attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)) {
+            return AppOpsManager.MODE_ALLOWED;
+        }
+        return mode;
+    }
+
     private boolean hasPermission(String permission) {
         return mContext.checkCallingOrSelfPermission(permission)
                 == PackageManager.PERMISSION_GRANTED;
@@ -428,6 +833,42 @@
     }
 
     /**
+     * Implementation of {@link VibrationThread.VibrationCallbacks} that controls synced vibrations
+     * and reports them when finished.
+     */
+    private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks {
+
+        @Override
+        public void prepareSyncedVibration(int requiredCapabilities, int[] vibratorIds) {
+            // TODO(b/167946816): call IVibratorManager to prepare
+        }
+
+        @Override
+        public void triggerSyncedVibration(long vibrationId) {
+            // TODO(b/167946816): call IVibratorManager to trigger
+        }
+
+        @Override
+        public void onVibrationEnded(long vibrationId, Vibration.Status status) {
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration " + vibrationId + " thread finished with status " + status);
+            }
+            synchronized (mLock) {
+                if (mCurrentVibration != null
+                        && mCurrentVibration.getVibration().id == vibrationId) {
+                    reportFinishedVibrationLocked(status);
+
+                    if (mNextVibration != null) {
+                        VibrationThread vibThread = mNextVibration;
+                        mNextVibration = null;
+                        startVibrationThreadLocked(vibThread);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
      * Implementation of {@link VibratorController.OnVibrationCompleteListener} with a weak
      * reference to this service.
      */
@@ -443,7 +884,7 @@
         public void onComplete(int vibratorId, long vibrationId) {
             VibratorManagerService service = mServiceRef.get();
             if (service != null) {
-                // TODO(b/159207608): finish vibration if all vibrators finished for this vibration
+                service.onVibrationComplete(vibratorId, vibrationId);
             }
         }
     }
@@ -469,6 +910,41 @@
         }
     }
 
+    /** Holder for a {@link ExternalVibration}. */
+    private final class ExternalVibrationHolder {
+
+        public final ExternalVibration externalVibration;
+        public int scale;
+
+        private final long mStartTimeDebug;
+        private long mEndTimeDebug;
+        private Vibration.Status mStatus;
+
+        private ExternalVibrationHolder(ExternalVibration externalVibration) {
+            this.externalVibration = externalVibration;
+            this.scale = IExternalVibratorService.SCALE_NONE;
+            mStartTimeDebug = System.currentTimeMillis();
+            mStatus = Vibration.Status.RUNNING;
+        }
+
+        public void end(Vibration.Status status) {
+            if (mStatus != Vibration.Status.RUNNING) {
+                // Vibration already ended, keep first ending status set and ignore this one.
+                return;
+            }
+            mStatus = status;
+            mEndTimeDebug = System.currentTimeMillis();
+        }
+
+        public Vibration.DebugInfo getDebugInfo() {
+            return new Vibration.DebugInfo(
+                    mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null,
+                    scale, externalVibration.getVibrationAttributes(),
+                    externalVibration.getUid(), externalVibration.getPackage(),
+                    /* reason= */ null, mStatus);
+        }
+    }
+
     /** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
     @VisibleForTesting
     public static class NativeWrapper {
@@ -494,8 +970,158 @@
         }
     }
 
+    /** Keep records of vibrations played and provide debug information for this service. */
+    private final class VibratorManagerRecords {
+        @GuardedBy("mLock")
+        private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations =
+                new SparseArray<>();
+        @GuardedBy("mLock")
+        private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations =
+                new LinkedList<>();
+        private final int mPreviousVibrationsLimit;
+
+        private VibratorManagerRecords(int limit) {
+            mPreviousVibrationsLimit = limit;
+        }
+
+        @GuardedBy("mLock")
+        void record(Vibration vib) {
+            int usage = vib.attrs.getUsage();
+            if (!mPreviousVibrations.contains(usage)) {
+                mPreviousVibrations.put(usage, new LinkedList<>());
+            }
+            record(mPreviousVibrations.get(usage), vib.getDebugInfo());
+        }
+
+        @GuardedBy("mLock")
+        void record(ExternalVibrationHolder vib) {
+            record(mPreviousExternalVibrations, vib.getDebugInfo());
+        }
+
+        @GuardedBy("mLock")
+        void record(LinkedList<Vibration.DebugInfo> records, Vibration.DebugInfo info) {
+            if (records.size() > mPreviousVibrationsLimit) {
+                records.removeFirst();
+            }
+            records.addLast(info);
+        }
+
+        void dumpText(PrintWriter pw) {
+            pw.println("Vibrator Manager Service:");
+            synchronized (mLock) {
+                pw.println("  mVibratorControllers:");
+                for (int i = 0; i < mVibrators.size(); i++) {
+                    pw.println("    " + mVibrators.valueAt(i));
+                }
+                pw.println();
+                pw.println("  mCurrentVibration:");
+                pw.println("    " + mCurrentVibration == null
+                        ? null : mCurrentVibration.getVibration().getDebugInfo());
+                pw.println("  mNextVibration:");
+                pw.println("    " + mNextVibration == null
+                        ? null : mNextVibration.getVibration().getDebugInfo());
+                pw.println("  mCurrentExternalVibration:");
+                pw.println("    " + mCurrentExternalVibration == null
+                        ? null : mCurrentExternalVibration.getDebugInfo());
+                pw.println();
+                pw.println("  mVibrationSettings=" + mVibrationSettings);
+                for (int i = 0; i < mPreviousVibrations.size(); i++) {
+                    pw.println();
+                    pw.print("  Previous vibrations for usage ");
+                    pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
+                    pw.println(":");
+                    for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
+                        pw.println("    " + info);
+                    }
+                }
+
+                pw.println("  Previous external vibrations:");
+                for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+                    pw.println("    " + info);
+                }
+            }
+        }
+
+        synchronized void dumpProto(FileDescriptor fd) {
+            final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+            synchronized (mLock) {
+                mVibrationSettings.dumpProto(proto);
+                if (mCurrentVibration != null) {
+                    mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+                            VibratorServiceDumpProto.CURRENT_VIBRATION);
+                }
+                if (mCurrentExternalVibration != null) {
+                    mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+                            VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
+                }
+
+                boolean isVibrating = false;
+                boolean isUnderExternalControl = false;
+                for (int i = 0; i < mVibrators.size(); i++) {
+                    isVibrating |= mVibrators.valueAt(i).isVibrating();
+                    isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
+                }
+                proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating);
+                proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
+                        isUnderExternalControl);
+
+                for (Vibration.DebugInfo info : mPreviousVibrations.get(
+                        VibrationAttributes.USAGE_RINGTONE)) {
+                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
+                }
+
+                for (Vibration.DebugInfo info : mPreviousVibrations.get(
+                        VibrationAttributes.USAGE_NOTIFICATION)) {
+                    info.dumpProto(proto,
+                            VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS);
+                }
+
+                for (Vibration.DebugInfo info : mPreviousVibrations.get(
+                        VibrationAttributes.USAGE_ALARM)) {
+                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS);
+                }
+
+                for (Vibration.DebugInfo info : mPreviousVibrations.get(
+                        VibrationAttributes.USAGE_UNKNOWN)) {
+                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS);
+                }
+
+                for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
+                    info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+                }
+            }
+            proto.flush();
+        }
+    }
+
     /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
     private final class VibratorManagerShellCommand extends ShellCommand {
+        public static final String SHELL_PACKAGE_NAME = "com.android.shell";
+
+        private final class CommonOptions {
+            public boolean force = false;
+            public String description = "Shell command";
+
+            CommonOptions() {
+                String nextArg;
+                while ((nextArg = peekNextArg()) != null) {
+                    switch (nextArg) {
+                        case "-f":
+                            getNextArgRequired(); // consume the -f argument;
+                            force = true;
+                            break;
+                        case "-d":
+                            getNextArgRequired(); // consume the -d argument;
+                            description = getNextArgRequired();
+                            break;
+                        default:
+                            // Not a common option, finish reading.
+                            return;
+                    }
+                }
+            }
+        }
 
         private final IBinder mToken;
 
@@ -505,10 +1131,27 @@
 
         @Override
         public int onCommand(String cmd) {
-            if ("list".equals(cmd)) {
-                return runListVibrators();
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onCommand " + cmd);
+            try {
+                if ("list".equals(cmd)) {
+                    return runListVibrators();
+                }
+                if ("synced".equals(cmd)) {
+                    return runMono();
+                }
+                if ("combined".equals(cmd)) {
+                    return runStereo();
+                }
+                if ("sequential".equals(cmd)) {
+                    return runSequential();
+                }
+                if ("cancel".equals(cmd)) {
+                    return runCancel();
+                }
+                return handleDefaultCommands(cmd);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
-            return handleDefaultCommands(cmd);
         }
 
         private int runListVibrators() {
@@ -525,6 +1168,157 @@
             }
         }
 
+        private int runMono() {
+            CommonOptions commonOptions = new CommonOptions();
+            VibrationEffect effect = nextEffect();
+            if (effect == null) {
+                return 0;
+            }
+
+            CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
+            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combinedEffect, attrs,
+                    commonOptions.description, mToken);
+            return 0;
+        }
+
+        private int runStereo() {
+            CommonOptions commonOptions = new CommonOptions();
+            CombinedVibrationEffect.SyncedCombination combination =
+                    CombinedVibrationEffect.startSynced();
+            while ("-v".equals(getNextOption())) {
+                int vibratorId = Integer.parseInt(getNextArgRequired());
+                VibrationEffect effect = nextEffect();
+                if (effect != null) {
+                    combination.addVibrator(vibratorId, effect);
+                }
+            }
+            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
+                    commonOptions.description, mToken);
+            return 0;
+        }
+
+        private int runSequential() {
+            CommonOptions commonOptions = new CommonOptions();
+
+            CombinedVibrationEffect.SequentialCombination combination =
+                    CombinedVibrationEffect.startSequential();
+            while ("-v".equals(getNextOption())) {
+                int vibratorId = Integer.parseInt(getNextArgRequired());
+                int delay = 0;
+                if ("-w".equals(getNextOption())) {
+                    delay = Integer.parseInt(getNextArgRequired());
+                }
+                VibrationEffect effect = nextEffect();
+                if (effect != null) {
+                    combination.addNext(vibratorId, effect, delay);
+                }
+            }
+            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
+            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
+                    commonOptions.description, mToken);
+            return 0;
+        }
+
+        private int runCancel() {
+            cancelVibrate(mToken);
+            return 0;
+        }
+
+        @Nullable
+        private VibrationEffect nextEffect() {
+            String effectType = getNextArgRequired();
+            if ("oneshot".equals(effectType)) {
+                return nextOneShot();
+            }
+            if ("waveform".equals(effectType)) {
+                return nextWaveform();
+            }
+            if ("prebaked".equals(effectType)) {
+                return nextPrebaked();
+            }
+            if ("composed".equals(effectType)) {
+                return nextComposed();
+            }
+            return null;
+        }
+
+        private VibrationEffect nextOneShot() {
+            boolean hasAmplitude = "-a".equals(getNextOption());
+            long duration = Long.parseLong(getNextArgRequired());
+            int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired())
+                    : VibrationEffect.DEFAULT_AMPLITUDE;
+            return VibrationEffect.createOneShot(duration, amplitude);
+        }
+
+        private VibrationEffect nextWaveform() {
+            boolean hasAmplitudes = false;
+            int repeat = -1;
+
+            String nextOption = getNextOption();
+            while (nextOption != null) {
+                if ("-a".equals(nextOption)) {
+                    hasAmplitudes = true;
+                } else if ("-r".equals(nextOption)) {
+                    repeat = Integer.parseInt(getNextArgRequired());
+                }
+                nextOption = getNextOption();
+            }
+            List<Long> durations = new ArrayList<>();
+            List<Integer> amplitudes = new ArrayList<>();
+
+            String nextArg;
+            while ((nextArg = peekNextArg()) != null && !"-v".equals(nextArg)) {
+                durations.add(Long.parseLong(getNextArgRequired()));
+                if (hasAmplitudes) {
+                    amplitudes.add(Integer.parseInt(getNextArgRequired()));
+                }
+            }
+
+            long[] durationArray = durations.stream().mapToLong(Long::longValue).toArray();
+            if (!hasAmplitudes) {
+                return VibrationEffect.createWaveform(durationArray, repeat);
+            }
+
+            int[] amplitudeArray = amplitudes.stream().mapToInt(Integer::intValue).toArray();
+            return VibrationEffect.createWaveform(durationArray, amplitudeArray, repeat);
+        }
+
+        private VibrationEffect nextPrebaked() {
+            boolean shouldFallback = "-b".equals(getNextOption());
+            int effectId = Integer.parseInt(getNextArgRequired());
+            return VibrationEffect.get(effectId, shouldFallback);
+        }
+
+        private VibrationEffect nextComposed() {
+            VibrationEffect.Composition composition = VibrationEffect.startComposition();
+            String nextArg;
+            while ((nextArg = peekNextArg()) != null) {
+                int delay = 0;
+                if ("-w".equals(nextArg)) {
+                    getNextArgRequired(); // consume the -w option
+                    delay = Integer.parseInt(getNextArgRequired());
+                } else if ("-v".equals(nextArg)) {
+                    // Starting next vibrator, this composed effect if finished.
+                    break;
+                }
+                int primitiveId = Integer.parseInt(getNextArgRequired());
+                composition.addPrimitive(primitiveId, /* scale= */ 1f, delay);
+            }
+            return composition.compose();
+        }
+
+        private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
+            final int flags =
+                    commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0;
+            return new VibrationAttributes.Builder()
+                    .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED)
+                    // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects.
+                    .setUsage(VibrationAttributes.USAGE_TOUCH)
+                    .build();
+        }
+
         @Override
         public void onHelp() {
             try (PrintWriter pw = getOutPrintWriter();) {
@@ -535,6 +1329,49 @@
                 pw.println("  list");
                 pw.println("    Prints the id of device vibrators. This does not include any ");
                 pw.println("    connected input device.");
+                pw.println("  synced [options] <effect>");
+                pw.println("    Vibrates effect on all vibrators in sync.");
+                pw.println("  combined [options] (-v <vibrator-id> <effect>)...");
+                pw.println("    Vibrates different effects on each vibrator in sync.");
+                pw.println("  sequential [options] (-v <vibrator-id> [-w <delay>] <effect>)...");
+                pw.println("    Vibrates different effects on each vibrator in sequence.");
+                pw.println("  cancel");
+                pw.println("    Cancels any active vibration");
+                pw.println("");
+                pw.println("Effect commands:");
+                pw.println("  oneshot [-a] <duration> [<amplitude>]");
+                pw.println("    Vibrates for duration milliseconds; ignored when device is on ");
+                pw.println("    DND (Do Not Disturb) mode; touch feedback strength user setting ");
+                pw.println("    will be used to scale amplitude.");
+                pw.println("    If -a is provided, the command accepts a second argument for ");
+                pw.println("    amplitude, in a scale of 1-255.");
+                pw.println("  waveform [-r <index>] [-a] (<duration> [<amplitude>])...");
+                pw.println("    Vibrates for durations and amplitudes in list; ignored when ");
+                pw.println("    device is on DND (Do Not Disturb) mode; touch feedback strength ");
+                pw.println("    user setting will be used to scale amplitude.");
+                pw.println("    If -r is provided, the waveform loops back to the specified");
+                pw.println("    index (e.g. 0 loops from the beginning)");
+                pw.println("    If -a is provided, the command accepts duration-amplitude pairs;");
+                pw.println("    otherwise, it accepts durations only and alternates off/on");
+                pw.println("    Duration is in milliseconds; amplitude is a scale of 1-255.");
+                pw.println("  prebaked [-b] <effect-id>");
+                pw.println("    Vibrates with prebaked effect; ignored when device is on DND ");
+                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
+                pw.println("    will be used to scale amplitude.");
+                pw.println("    If -b is provided, the prebaked fallback effect will be played if");
+                pw.println("    the device doesn't support the given effect-id.");
+                pw.println("  composed [-w <delay>] <primitive-id>...");
+                pw.println("    Vibrates with a composed effect; ignored when device is on DND ");
+                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
+                pw.println("    will be used to scale primitive intensities.");
+                pw.println("    If -w is provided, the next primitive will be played after the ");
+                pw.println("    specified wait time in milliseconds.");
+                pw.println("");
+                pw.println("Common Options:");
+                pw.println("  -f");
+                pw.println("    Force. Ignore Do Not Disturb setting.");
+                pw.println("  -d <description>");
+                pw.println("    Add description to the vibration.");
                 pw.println("");
             }
         }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 3e80709..026eb63 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -102,7 +102,10 @@
     private VibrationScaler mVibrationScaler;
     private InputDeviceDelegate mInputDeviceDelegate;
 
-    private volatile VibrationThread mThread;
+    @GuardedBy("mLock")
+    private VibrationThread mThread;
+    @GuardedBy("mLock")
+    private VibrationThread mNextVibrationThread;
 
     @GuardedBy("mLock")
     private Vibration mCurrentVibration;
@@ -132,6 +135,10 @@
                 if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
                     mThread = null;
                     reportFinishVibrationLocked(status);
+                    if (mNextVibrationThread != null) {
+                        startVibrationThreadLocked(mNextVibrationThread);
+                        mNextVibrationThread = null;
+                    }
                 }
             }
         }
@@ -258,18 +265,14 @@
     @VisibleForTesting
     public void onVibrationComplete(int vibratorId, long vibrationId) {
         synchronized (mLock) {
-            if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) {
+            if (mCurrentVibration != null && mCurrentVibration.id == vibrationId
+                    && mThread != null) {
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread");
                 }
-                if (mThread != null) {
-                    // Let the thread playing the vibration handle the callback, since it might be
-                    // expecting the vibrator to turn off multiple times during a single vibration.
-                    mThread.vibratorComplete(vibratorId);
-                } else {
-                    // No vibration is playing in the thread, but clean up service just in case.
-                    doCancelVibrateLocked(Vibration.Status.FINISHED);
-                }
+                // Let the thread playing the vibration handle the callback, since it might be
+                // expecting the vibrator to turn off multiple times during a single vibration.
+                mThread.vibratorComplete(vibratorId);
             }
         }
     }
@@ -462,8 +465,10 @@
                 try {
                     doCancelVibrateLocked(Vibration.Status.CANCELLED);
                     startVibrationLocked(vib);
+                    boolean isNextVibration = mNextVibrationThread != null
+                            && vib.equals(mNextVibrationThread.getVibration());
 
-                    if (!vib.hasEnded() && mCurrentVibration.id != vib.id) {
+                    if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) {
                         // Vibration was unexpectedly ignored: add to list for debugging
                         endVibrationLocked(vib, Vibration.Status.IGNORED);
                     }
@@ -532,6 +537,7 @@
                 }
                 final long ident = Binder.clearCallingIdentity();
                 try {
+                    mNextVibrationThread = null;
                     doCancelVibrateLocked(Vibration.Status.CANCELLED);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -546,16 +552,14 @@
         try {
             if (mThread != null) {
                 mThread.cancel();
-                mThread = null;
             }
+            mInputDeviceDelegate.cancelVibrateIfAvailable();
             if (mCurrentExternalVibration != null) {
                 endVibrationLocked(mCurrentExternalVibration, status);
                 mCurrentExternalVibration.externalVibration.mute();
                 mCurrentExternalVibration = null;
                 mVibratorController.setExternalControl(false);
             }
-            doVibratorOff();
-            reportFinishVibrationLocked(status);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
@@ -579,28 +583,30 @@
     private void startVibrationInnerLocked(Vibration vib) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked");
         try {
-            // Set current vibration before starting it, so callback will work.
-            mCurrentVibration = vib;
-            VibrationEffect effect = getEffect(vib);
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
             boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                     vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs);
             if (inputDevicesAvailable) {
-                // The set current vibration is no longer being played by this service, so drop it.
-                mCurrentVibration = null;
                 endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
+            } else if (mThread == null) {
+                startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock,
+                        mBatteryStatsService, mVibrationCallbacks));
             } else {
-                // mThread better be null here. doCancelVibrate should always be
-                // called before startVibrationInnerLocked
-                mThread = new VibrationThread(vib, mVibratorController, mWakeLock,
+                mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock,
                         mBatteryStatsService, mVibrationCallbacks);
-                mThread.start();
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
+    @GuardedBy("mLock")
+    private void startVibrationThreadLocked(VibrationThread thread) {
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+        mCurrentVibration = thread.getVibration();
+        mThread = thread;
+        mThread.start();
+    }
+
     /** Scale the vibration effect by the intensity as appropriate based its intent. */
     private void applyVibrationIntensityScalingLocked(Vibration vib) {
         vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage()));
@@ -665,13 +671,14 @@
 
     @GuardedBy("mLock")
     private void reportFinishVibrationLocked(Vibration.Status status) {
-        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
         try {
             if (mCurrentVibration != null) {
                 endVibrationLocked(mCurrentVibration, status);
                 mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid,
                         mCurrentVibration.opPkg);
+
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 mCurrentVibration = null;
             }
         } finally {
@@ -697,21 +704,6 @@
         }
     }
 
-    private void doVibratorOff() {
-        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff");
-        try {
-            if (DEBUG) {
-                Slog.d(TAG, "Turning vibrator off.");
-            }
-            boolean inputDevicesAvailable = mInputDeviceDelegate.cancelVibrateIfAvailable();
-            if (!inputDevicesAvailable) {
-                mVibratorController.off();
-            }
-        } finally {
-            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-        }
-    }
-
     private boolean isSystemHapticFeedback(Vibration vib) {
         if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
             return false;
@@ -780,18 +772,7 @@
             proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating());
             proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
                     mVibratorController.isUnderExternalControl());
-            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
-                    mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH));
-            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
-                    mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH));
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
-                    mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-            proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
-                    mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION));
-            proto.write(VibratorServiceDumpProto.RING_INTENSITY,
-                    mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE));
-            proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
-                    mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE));
+            mVibrationSettings.dumpProto(proto);
 
             for (Vibration.DebugInfo info : mPreviousRingVibrations) {
                 info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS);
@@ -844,6 +825,7 @@
                     // haptic feedback as part of the transition.  So we don't cancel
                     // system vibrations.
                     if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) {
+                        mNextVibrationThread = null;
                         doCancelVibrateLocked(Vibration.Status.CANCELLED);
                     }
                 }
@@ -910,6 +892,8 @@
                 return IExternalVibratorService.SCALE_MUTE;
             }
 
+            VibrationThread cancelingVibration = null;
+            int scale;
             synchronized (mLock) {
                 if (mCurrentExternalVibration != null
                         && mCurrentExternalVibration.externalVibration.equals(vib)) {
@@ -920,11 +904,9 @@
                 if (mCurrentExternalVibration == null) {
                     // If we're not under external control right now, then cancel any normal
                     // vibration that may be playing and ready the vibrator for external control.
-                    if (DEBUG) {
-                        Slog.d(TAG, "Vibrator going under external control.");
-                    }
+                    mNextVibrationThread = null;
                     doCancelVibrateLocked(Vibration.Status.CANCELLED);
-                    mVibratorController.setExternalControl(true);
+                    cancelingVibration = mThread;
                 } else {
                     endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
                 }
@@ -941,11 +923,24 @@
                 vib.linkToDeath(mCurrentExternalDeathRecipient);
                 mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
                         vib.getVibrationAttributes().getUsage());
-                if (DEBUG) {
-                    Slog.e(TAG, "Playing external vibration: " + vib);
-                }
-                return mCurrentExternalVibration.scale;
+                scale = mCurrentExternalVibration.scale;
             }
+            if (cancelingVibration != null) {
+                try {
+                    cancelingVibration.join();
+                } catch (InterruptedException e) {
+                    Slog.w("Interrupted while waiting current vibration to be cancelled before "
+                            + "starting external vibration", e);
+                }
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Vibrator going under external control.");
+            }
+            mVibratorController.setExternalControl(true);
+            if (DEBUG) {
+                Slog.e(TAG, "Playing external vibration: " + vib);
+            }
+            return scale;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index ba1eda9..5b50431 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -31,6 +31,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceDebugInfo;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -110,7 +111,6 @@
     };
 
     public static final List<String> HAL_INTERFACES_OF_INTEREST = Arrays.asList(
-            "android.hardware.audio@2.0::IDevicesFactory",
             "android.hardware.audio@4.0::IDevicesFactory",
             "android.hardware.audio@5.0::IDevicesFactory",
             "android.hardware.audio@6.0::IDevicesFactory",
@@ -136,6 +136,11 @@
             "android.system.suspend@1.0::ISystemSuspend"
     );
 
+    public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
+            "android.hardware.light.ILights/",
+            "android.hardware.power.stats.IPowerStats/",
+    };
+
     private static Watchdog sWatchdog;
 
     private final Thread mThread;
@@ -527,12 +532,11 @@
         return builder.toString();
     }
 
-    private static ArrayList<Integer> getInterestingHalPids() {
+    private static void addInterestingHidlPids(HashSet<Integer> pids) {
         try {
             IServiceManager serviceManager = IServiceManager.getService();
             ArrayList<IServiceManager.InstanceDebugInfo> dump =
                     serviceManager.debugDump();
-            HashSet<Integer> pids = new HashSet<>();
             for (IServiceManager.InstanceDebugInfo info : dump) {
                 if (info.pid == IServiceManager.PidConstant.NO_PID) {
                     continue;
@@ -544,24 +548,37 @@
 
                 pids.add(info.pid);
             }
-            return new ArrayList<Integer>(pids);
         } catch (RemoteException e) {
-            return new ArrayList<Integer>();
+            Log.w(TAG, e);
+        }
+    }
+
+    private static void addInterestingAidlPids(HashSet<Integer> pids) {
+        ServiceDebugInfo[] infos = ServiceManager.getServiceDebugInfo();
+        if (infos == null) return;
+
+        for (ServiceDebugInfo info : infos) {
+            for (String prefix : AIDL_INTERFACE_PREFIXES_OF_INTEREST) {
+                if (info.name.startsWith(prefix)) {
+                    pids.add(info.debugPid);
+                }
+            }
         }
     }
 
     static ArrayList<Integer> getInterestingNativePids() {
-        ArrayList<Integer> pids = getInterestingHalPids();
+        HashSet<Integer> pids = new HashSet<>();
+        addInterestingAidlPids(pids);
+        addInterestingHidlPids(pids);
 
         int[] nativePids = Process.getPidsForCommands(NATIVE_STACKS_OF_INTEREST);
         if (nativePids != null) {
-            pids.ensureCapacity(pids.size() + nativePids.length);
             for (int i : nativePids) {
                 pids.add(i);
             }
         }
 
-        return pids;
+        return new ArrayList<Integer>(pids);
     }
 
     private void run() {
@@ -715,7 +732,7 @@
                 WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
                 Slog.w(TAG, "*** GOODBYE!");
                 if (!Build.IS_USER && isCrashLoopFound()
-                        && !WatchdogProperties.is_fatal_ignore().orElse(false)) {
+                        && !WatchdogProperties.should_ignore_fatal_count().orElse(false)) {
                     breakCrashLoop();
                 }
                 Process.killProcess(Process.myPid());
@@ -794,7 +811,7 @@
     private boolean isCrashLoopFound() {
         int fatalCount = WatchdogProperties.fatal_count().orElse(0);
         long fatalWindowMs = TimeUnit.SECONDS.toMillis(
-                WatchdogProperties.fatal_window_second().orElse(0));
+                WatchdogProperties.fatal_window_seconds().orElse(0));
         if (fatalCount == 0 || fatalWindowMs == 0) {
             if (fatalCount != fatalWindowMs) {
                 Slog.w(TAG, String.format("sysprops '%s' and '%s' should be set or unset together",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index e476ca9..a4ff230 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -19,6 +19,9 @@
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
 import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
 import static android.os.Process.NFC_UID;
@@ -48,7 +51,6 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
-import android.app.ApplicationExitInfo;
 import android.app.BroadcastOptions;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
@@ -621,14 +623,14 @@
 
         final boolean callerFg;
         if (caller != null) {
-            final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+            final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
             if (callerApp == null) {
                 throw new SecurityException(
                         "Unable to find app for caller " + caller
                         + " (pid=" + callingPid
                         + ") when starting service " + service);
             }
-            callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
+            callerFg = callerApp.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_BACKGROUND;
         } else {
             callerFg = true;
         }
@@ -656,7 +658,7 @@
         // If we're starting indirectly (e.g. from PendingIntent), figure out whether
         // we're launching into an app in a background state.  This keys off of the same
         // idleness state tracking as e.g. O+ background service start policy.
-        final boolean bgLaunch = !mAm.isUidActiveLocked(r.appInfo.uid);
+        final boolean bgLaunch = !mAm.isUidActiveLOSP(r.appInfo.uid);
 
         // If the app has strict background restrictions, we treat any bg service
         // start analogously to the legacy-app forced-restrictions case, regardless
@@ -671,34 +673,29 @@
         }
 
         if (fgRequired) {
-            if (isFgsBgStart(r.mAllowStartForeground)) {
-                if (!r.mLoggedInfoAllowStartForeground) {
-                    Slog.wtf(TAG, "Background started FGS " + r.mInfoAllowStartForeground);
-                    r.mLoggedInfoAllowStartForeground = true;
+            logFgsBackgroundStart(r);
+            if (r.mAllowStartForeground == FGS_FEATURE_DENIED && isBgFgsRestrictionEnabled(r)) {
+                String msg = "startForegroundService() not allowed due to "
+                        + "mAllowStartForeground false: service "
+                        + r.shortInstanceName;
+                Slog.w(TAG, msg);
+                showFgsBgRestrictedNotificationLocked(r);
+                ApplicationInfo aInfo = null;
+                try {
+                    aInfo = AppGlobals.getPackageManager().getApplicationInfo(
+                            callingPackage, ActivityManagerService.STOCK_PM_FLAGS,
+                            userId);
+                } catch (android.os.RemoteException e) {
+                    // pm is in same process, this will never happen.
                 }
-                if (r.mAllowStartForeground == FGS_FEATURE_DENIED && isBgFgsRestrictionEnabled(r)) {
-                    String msg = "startForegroundService() not allowed due to "
-                            + "mAllowStartForeground false: service "
-                            + r.shortInstanceName;
-                    Slog.w(TAG, msg);
-                    showFgsBgRestrictedNotificationLocked(r);
-                    ApplicationInfo aInfo = null;
-                    try {
-                        aInfo = AppGlobals.getPackageManager().getApplicationInfo(
-                                callingPackage, ActivityManagerService.STOCK_PM_FLAGS,
-                                userId);
-                    } catch (android.os.RemoteException e) {
-                        // pm is in same process, this will never happen.
-                    }
-                    if (aInfo == null) {
-                        throw new SecurityException("startServiceLocked failed, "
-                                + "could not resolve client package " + callingPackage);
-                    }
-                    if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, aInfo.uid)) {
-                        throw new IllegalStateException(msg);
-                    }
-                    return null;
+                if (aInfo == null) {
+                    throw new SecurityException("startServiceLocked failed, "
+                            + "could not resolve client package " + callingPackage);
                 }
+                if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, aInfo.uid)) {
+                    throw new IllegalStateException(msg);
+                }
+                return null;
             }
         }
 
@@ -732,7 +729,7 @@
         if (forcedStandby || (!r.startRequested && !fgRequired)) {
             // Before going further -- if this app is not allowed to start services in the
             // background, then at this point we aren't going to let it period.
-            final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
+            final int allowed = mAm.getAppStartModeLOSP(r.appInfo.uid, r.packageName,
                     r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
             if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                 Slog.w(TAG, "Background start not allowed: service "
@@ -757,7 +754,7 @@
                 }
                 // This app knows it is in the new model where this operation is not
                 // allowed, so tell it what has happened.
-                UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(r.appInfo.uid);
+                UidRecord uidRec = mAm.mProcessList.getUidRecordLOSP(r.appInfo.uid);
                 return new ComponentName("?", "app is in background uid " + uidRec);
             }
         }
@@ -830,7 +827,7 @@
         if (!callerFg && !fgRequired && r.app == null
                 && mAm.mUserController.hasStartedUserState(r.userId)) {
             ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
-            if (proc == null || proc.getCurProcState() > ActivityManager.PROCESS_STATE_RECEIVER) {
+            if (proc == null || proc.mState.getCurProcState() > PROCESS_STATE_RECEIVER) {
                 // If this is not coming from a foreground caller, then we may want
                 // to delay the start if there are already other background services
                 // that are starting.  This is to avoid process start spam when lots
@@ -858,7 +855,7 @@
                 }
                 if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Not delaying: " + r);
                 addToStarting = true;
-            } else if (proc.getCurProcState() >= ActivityManager.PROCESS_STATE_SERVICE) {
+            } else if (proc.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_SERVICE) {
                 // We slightly loosen when we will enqueue this new service as a background
                 // starting service we are waiting for, to also include processes that are
                 // currently running other services or receivers.
@@ -867,9 +864,9 @@
                         "Not delaying, but counting as bg: " + r);
             } else if (DEBUG_DELAYED_STARTS) {
                 StringBuilder sb = new StringBuilder(128);
-                sb.append("Not potential delay (state=").append(proc.getCurProcState())
-                        .append(' ').append(proc.adjType);
-                String reason = proc.makeAdjReason();
+                sb.append("Not potential delay (state=").append(proc.mState.getCurProcState())
+                        .append(' ').append(proc.mState.getAdjType());
+                String reason = proc.mState.makeAdjReason();
                 if (reason != null) {
                     sb.append(' ');
                     sb.append(reason);
@@ -900,7 +897,7 @@
             String callingPackage, @Nullable String callingFeatureId, int callingUid,
             Intent service, boolean callerFg, final int userId,
             final boolean isBinding, final IServiceConnection connection) {
-        if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+        if (mAm.getPackageManagerInternal().isPermissionsReviewRequired(
                 r.packageName, r.userId)) {
 
             // Show a permission review UI only for starting/binding from a foreground app
@@ -932,7 +929,7 @@
                                         // binding request is still valid, so hook them up. We
                                         // proceed only if the caller cleared the review requirement
                                         // otherwise we unbind because the user didn't approve.
-                                        if (!mAm.getPackageManagerInternalLocked()
+                                        if (!mAm.getPackageManagerInternal()
                                                 .isPermissionsReviewRequired(r.packageName,
                                                     r.userId)) {
                                             try {
@@ -941,9 +938,14 @@
                                                         callerFg,
                                                         false /* whileRestarting */,
                                                         false /* permissionsReviewRequired */,
-                                                        false /* packageFrozen */);
+                                                        false /* packageFrozen */,
+                                                        true /* enqueueOomAdj */);
                                             } catch (RemoteException e) {
                                                 /* ignore - local call */
+                                            } finally {
+                                                /* Will be a no-op if nothing pending */
+                                                mAm.updateOomAdjPendingTargetsLocked(
+                                                        OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
                                             }
                                         } else {
                                             unbindServiceLocked(connection);
@@ -994,7 +996,7 @@
             int callingUid, int callingPid, boolean fgRequired, boolean callerFg, int userId,
             boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken,
             boolean isBinding, IServiceConnection connection) {
-        final PackageManagerInternal pm = mAm.getPackageManagerInternalLocked();
+        final PackageManagerInternal pm = mAm.getPackageManagerInternal();
         final boolean frozen = pm.isPackageFrozen(s.packageName, callingUid, s.userId);
         if (!frozen) {
             // Not frozen, it's okay to go
@@ -1025,9 +1027,14 @@
                             bringUpServiceLocked(s, serviceIntent.getFlags(), callerFg,
                                     false /* whileRestarting */,
                                     false /* permissionsReviewRequired */,
-                                    false /* packageFrozen */);
+                                    false /* packageFrozen */,
+                                    true /* enqueueOomAdj */);
                         } catch (TransactionTooLargeException e) {
                             /* ignore - local call */
+                        } finally {
+                            /* Will be a no-op if nothing pending */
+                            mAm.updateOomAdjPendingTargetsLocked(
+                                    OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
                         }
                     } else { // Starting a service
                         try {
@@ -1091,7 +1098,13 @@
         FrameworkStatsLog.write(FrameworkStatsLog.SERVICE_STATE_CHANGED, uid, packageName,
                 serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__START);
         mAm.mBatteryStatsService.noteServiceStartRunning(uid, packageName, serviceName);
-        String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false, false);
+        String error = bringUpServiceLocked(r, service.getFlags(), callerFg,
+                false /* whileRestarting */,
+                false /* permissionsReviewRequired */,
+                false /* packageFrozen */,
+                true /* enqueueOomAdj */);
+        /* Will be a no-op if nothing pending */
+        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
         if (error != null) {
             return new ComponentName("!!", error);
         }
@@ -1117,7 +1130,7 @@
         return r.name;
     }
 
-    private void stopServiceLocked(ServiceRecord service) {
+    private void stopServiceLocked(ServiceRecord service, boolean enqueueOomAdj) {
         if (service.delayed) {
             // If service isn't actually running, but is being held in the
             // delayed list, then we need to keep it started but note that it
@@ -1140,7 +1153,7 @@
         }
         service.callStart = false;
 
-        bringDownServiceIfNeededLocked(service, false, false);
+        bringDownServiceIfNeededLocked(service, false, false, enqueueOomAdj);
     }
 
     int stopServiceLocked(IApplicationThread caller, Intent service,
@@ -1148,7 +1161,7 @@
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "stopService: " + service
                 + " type=" + resolvedType);
 
-        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+        final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
         if (caller != null && callerApp == null) {
             throw new SecurityException(
                     "Unable to find app for caller " + caller
@@ -1163,7 +1176,7 @@
             if (r.record != null) {
                 final long origId = Binder.clearCallingIdentity();
                 try {
-                    stopServiceLocked(r.record);
+                    stopServiceLocked(r.record, false);
                 } finally {
                     Binder.restoreCallingIdentity(origId);
                 }
@@ -1184,7 +1197,7 @@
             for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
                 ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
                 if (service.appInfo.uid == uid && service.startRequested) {
-                    if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName,
+                    if (mAm.getAppStartModeLOSP(service.appInfo.uid, service.packageName,
                             service.appInfo.targetSdkVersion, -1, false, false, false)
                             != ActivityManager.APP_START_MODE_NORMAL) {
                         if (stopping == null) {
@@ -1213,11 +1226,15 @@
                 }
             }
             if (stopping != null) {
-                for (int i=stopping.size()-1; i>=0; i--) {
+                final int size = stopping.size();
+                for (int i = size - 1; i >= 0; i--) {
                     ServiceRecord service = stopping.get(i);
                     service.delayed = false;
                     services.ensureNotStartingBackgroundLocked(service);
-                    stopServiceLocked(service);
+                    stopServiceLocked(service, true);
+                }
+                if (size > 0) {
+                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
                 }
             }
         }
@@ -1226,7 +1243,7 @@
     void killMisbehavingService(ServiceRecord r,
             int appUid, int appPid, String localPackageName) {
         synchronized (mAm) {
-            stopServiceLocked(r);
+            stopServiceLocked(r, false);
             mAm.crashApplication(appUid, appPid, localPackageName, -1,
                     "Bad notification for startForeground", true /*force*/);
         }
@@ -1301,7 +1318,7 @@
             }
             r.callStart = false;
             final long origId = Binder.clearCallingIdentity();
-            bringDownServiceIfNeededLocked(r, false, false);
+            bringDownServiceIfNeededLocked(r, false, false, false);
             Binder.restoreCallingIdentity(origId);
             return true;
         }
@@ -1610,13 +1627,13 @@
     }
 
     void foregroundServiceProcStateChangedLocked(UidRecord uidRec) {
-        ServiceMap smap = mServiceMap.get(UserHandle.getUserId(uidRec.uid));
+        ServiceMap smap = mServiceMap.get(UserHandle.getUserId(uidRec.getUid()));
         if (smap != null) {
             boolean changed = false;
             for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) {
                 ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j);
-                if (active.mUid == uidRec.uid) {
-                    if (uidRec.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) {
+                if (active.mUid == uidRec.getUid()) {
+                    if (uidRec.getCurProcState() <= PROCESS_STATE_TOP) {
                         if (!active.mAppOnTop) {
                             active.mAppOnTop = true;
                             changed = true;
@@ -1635,7 +1652,7 @@
     }
 
     private boolean appIsTopLocked(int uid) {
-        return mAm.getUidState(uid) <= ActivityManager.PROCESS_STATE_TOP;
+        return mAm.getUidStateLocked(uid) <= PROCESS_STATE_TOP;
     }
 
     /**
@@ -1667,13 +1684,13 @@
                     default:
                         mAm.enforcePermission(
                                 android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
-                                r.app.pid, r.appInfo.uid, "startForeground");
+                                r.app.getPid(), r.appInfo.uid, "startForeground");
                 }
             } else {
                 if (r.appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
                     mAm.enforcePermission(
                             android.Manifest.permission.FOREGROUND_SERVICE,
-                            r.app.pid, r.appInfo.uid, "startForeground");
+                            r.app.getPid(), r.appInfo.uid, "startForeground");
                 }
 
                 int manifestType = r.serviceInfo.getForegroundServiceType();
@@ -1714,6 +1731,7 @@
                         ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
             }
 
+            final ProcessServiceRecord psr = r.app.mServices;
             try {
                 boolean ignoreForeground = false;
                 final int mode = mAm.getAppOpsManager().checkOpNoThrow(
@@ -1743,30 +1761,24 @@
                                     + r.shortInstanceName);
                     // Back off of any foreground expectations around this service, since we've
                     // just turned down its fg request.
-                    updateServiceForegroundLocked(r.app, false);
+                    updateServiceForegroundLocked(psr, false);
                     ignoreForeground = true;
                 }
 
                 if (!ignoreForeground) {
-                    if (isFgsBgStart(r.mAllowStartForeground)) {
-                        if (!r.mLoggedInfoAllowStartForeground) {
-                            Slog.wtf(TAG, "Background started FGS "
-                                    + r.mInfoAllowStartForeground);
-                            r.mLoggedInfoAllowStartForeground = true;
-                        }
-                        if (r.mAllowStartForeground == FGS_FEATURE_DENIED
-                                && isBgFgsRestrictionEnabled(r)) {
-                            final String msg = "Service.startForeground() not allowed due to "
-                                    + "mAllowStartForeground false: service "
-                                    + r.shortInstanceName;
-                            Slog.w(TAG, msg);
-                            showFgsBgRestrictedNotificationLocked(r);
-                            updateServiceForegroundLocked(r.app, true);
-                            ignoreForeground = true;
-                            if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
-                                    r.appInfo.uid)) {
-                                throw new IllegalStateException(msg);
-                            }
+                    logFgsBackgroundStart(r);
+                    if (r.mAllowStartForeground == FGS_FEATURE_DENIED
+                            && isBgFgsRestrictionEnabled(r)) {
+                        final String msg = "Service.startForeground() not allowed due to "
+                                + "mAllowStartForeground false: service "
+                                + r.shortInstanceName;
+                        Slog.w(TAG, msg);
+                        showFgsBgRestrictedNotificationLocked(r);
+                        updateServiceForegroundLocked(psr, true);
+                        ignoreForeground = true;
+                        if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID,
+                                r.appInfo.uid)) {
+                            throw new IllegalStateException(msg);
                         }
                     }
                 }
@@ -1793,10 +1805,12 @@
                                 active.mPackageName = r.packageName;
                                 active.mUid = r.appInfo.uid;
                                 active.mShownWhileScreenOn = mScreenOn;
-                                if (r.app != null && r.app.uidRecord != null) {
-                                    active.mAppOnTop = active.mShownWhileTop =
-                                            r.app.uidRecord.getCurProcState()
-                                                    <= ActivityManager.PROCESS_STATE_TOP;
+                                if (r.app != null) {
+                                    final UidRecord uidRec = r.app.getUidRecord();
+                                    if (uidRec != null) {
+                                        active.mAppOnTop = active.mShownWhileTop =
+                                                uidRec.getCurProcState() <= PROCESS_STATE_TOP;
+                                    }
                                 }
                                 active.mStartTime = active.mStartVisibleTime
                                         = SystemClock.elapsedRealtime();
@@ -1828,7 +1842,7 @@
                     }
                     postFgsNotificationLocked(r);
                     if (r.app != null) {
-                        updateServiceForegroundLocked(r.app, true);
+                        updateServiceForegroundLocked(psr, true);
                     }
                     getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r);
                     mAm.notifyPackageUse(r.serviceInfo.packageName,
@@ -1880,7 +1894,7 @@
                 mAm.updateForegroundServiceUsageStats(r.name, r.userId, false);
                 if (r.app != null) {
                     mAm.updateLruProcessLocked(r.app, false, null);
-                    updateServiceForegroundLocked(r.app, true);
+                    updateServiceForegroundLocked(r.app.mServices, true);
                 }
             }
             // Leave the time-to-display as already set: re-entering foreground mode will
@@ -2118,7 +2132,7 @@
         }
 
         private boolean isNotTop() {
-            return mProcessRecord.getCurProcState() != ActivityManager.PROCESS_STATE_TOP;
+            return mProcessRecord.mState.getCurProcState() != PROCESS_STATE_TOP;
         }
 
         private void incrementOpCount(int op, boolean allowed) {
@@ -2216,36 +2230,45 @@
         }
     }
 
-    private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) {
+    private void updateServiceForegroundLocked(ProcessServiceRecord psr, boolean oomAdj) {
         boolean anyForeground = false;
         int fgServiceTypes = 0;
-        for (int i = proc.numberOfRunningServices() - 1; i >= 0; i--) {
-            ServiceRecord sr = proc.getRunningServiceAt(i);
+        for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+            ServiceRecord sr = psr.getRunningServiceAt(i);
             if (sr.isForeground || sr.fgRequired) {
                 anyForeground = true;
                 fgServiceTypes |= sr.foregroundServiceType;
             }
         }
-        mAm.updateProcessForegroundLocked(proc, anyForeground, fgServiceTypes, oomAdj);
+        mAm.updateProcessForegroundLocked(psr.mApp, anyForeground, fgServiceTypes, oomAdj);
     }
 
-    private void updateWhitelistManagerLocked(ProcessRecord proc) {
-        proc.whitelistManager = false;
-        for (int i = proc.numberOfRunningServices() - 1; i >= 0; i--) {
-            ServiceRecord sr = proc.getRunningServiceAt(i);
+    private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
+        psr.mAllowlistManager = false;
+        for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+            ServiceRecord sr = psr.getRunningServiceAt(i);
             if (sr.whitelistManager) {
-                proc.whitelistManager = true;
+                psr.mAllowlistManager = true;
                 break;
             }
         }
     }
 
-    public void updateServiceConnectionActivitiesLocked(ProcessRecord clientProc) {
+    private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
+        final ProcessServiceRecord psr = service.app.mServices;
+        psr.stopService(service);
+        psr.updateBoundClientUids();
+        if (service.whitelistManager) {
+            updateAllowlistManagerLocked(psr);
+        }
+    }
+
+    void updateServiceConnectionActivitiesLocked(ProcessServiceRecord clientPsr) {
         ArraySet<ProcessRecord> updatedProcesses = null;
-        for (int i = 0; i < clientProc.connections.size(); i++) {
-            final ConnectionRecord conn = clientProc.connections.valueAt(i);
+        for (int i = 0; i < clientPsr.numberOfConnections(); i++) {
+            final ConnectionRecord conn = clientPsr.getConnectionAt(i);
             final ProcessRecord proc = conn.binding.service.app;
-            if (proc == null || proc == clientProc) {
+            if (proc == null || proc == clientPsr.mApp) {
                 continue;
             } else if (updatedProcesses == null) {
                 updatedProcesses = new ArraySet<>();
@@ -2253,11 +2276,11 @@
                 continue;
             }
             updatedProcesses.add(proc);
-            updateServiceClientActivitiesLocked(proc, null, false);
+            updateServiceClientActivitiesLocked(proc.mServices, null, false);
         }
     }
 
-    private boolean updateServiceClientActivitiesLocked(ProcessRecord proc,
+    private boolean updateServiceClientActivitiesLocked(ProcessServiceRecord psr,
             ConnectionRecord modCr, boolean updateLru) {
         if (modCr != null && modCr.binding.client != null) {
             if (!modCr.binding.client.hasActivities()) {
@@ -2268,14 +2291,14 @@
         }
 
         boolean anyClientActivities = false;
-        for (int i = proc.numberOfRunningServices() - 1; i >= 0 && !anyClientActivities; i--) {
-            ServiceRecord sr = proc.getRunningServiceAt(i);
+        for (int i = psr.numberOfRunningServices() - 1; i >= 0 && !anyClientActivities; i--) {
+            ServiceRecord sr = psr.getRunningServiceAt(i);
             ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = sr.getConnections();
             for (int conni = connections.size() - 1; conni >= 0 && !anyClientActivities; conni--) {
                 ArrayList<ConnectionRecord> clist = connections.valueAt(conni);
                 for (int cri=clist.size()-1; cri>=0; cri--) {
                     ConnectionRecord cr = clist.get(cri);
-                    if (cr.binding.client == null || cr.binding.client == proc) {
+                    if (cr.binding.client == null || cr.binding.client == psr.mApp) {
                         // Binding to ourself is not interesting.
                         continue;
                     }
@@ -2286,10 +2309,10 @@
                 }
             }
         }
-        if (anyClientActivities != proc.hasClientActivities()) {
-            proc.setHasClientActivities(anyClientActivities);
+        if (anyClientActivities != psr.hasClientActivities()) {
+            psr.setHasClientActivities(anyClientActivities);
             if (updateLru) {
-                mAm.updateLruProcessLocked(proc, anyClientActivities, null);
+                mAm.updateLruProcessLocked(psr.mApp, anyClientActivities, null);
             }
             return true;
         }
@@ -2305,7 +2328,7 @@
                 + " flags=0x" + Integer.toHexString(flags));
         final int callingPid = Binder.getCallingPid();
         final int callingUid = Binder.getCallingUid();
-        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+        final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
         if (callerApp == null) {
             throw new SecurityException(
                     "Unable to find app for caller " + caller
@@ -2377,7 +2400,8 @@
                     "BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND");
         }
 
-        final boolean callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
+        final boolean callerFg = callerApp.mState.getSetSchedGroup()
+                != ProcessList.SCHED_GROUP_BACKGROUND;
         final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
         final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
 
@@ -2431,7 +2455,7 @@
             }
 
             mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
-                    callerApp.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
+                    callerApp.mState.getCurProcState(), s.appInfo.uid, s.appInfo.longVersionCode,
                     s.instanceName, s.processName);
             // Once the apps have become associated, if one of them is caller is ephemeral
             // the target app should now be able to see the calling app
@@ -2449,10 +2473,11 @@
             if (activity != null) {
                 activity.addConnection(c);
             }
-            b.client.connections.add(c);
+            final ProcessServiceRecord clientPsr = b.client.mServices;
+            clientPsr.addConnection(c);
             c.startAssociationIfNeeded();
             if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
-                b.client.hasAboveClient = true;
+                clientPsr.setHasAboveClient(true);
             }
             if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) {
                 s.whitelistManager = true;
@@ -2466,7 +2491,7 @@
             }
 
             if (s.app != null) {
-                updateServiceClientActivitiesLocked(s.app, c, true);
+                updateServiceClientActivitiesLocked(s.app.mServices, c, true);
             }
             ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
             if (clist == null) {
@@ -2475,29 +2500,37 @@
             }
             clist.add(c);
 
+            boolean needOomAdj = false;
             if ((flags&Context.BIND_AUTO_CREATE) != 0) {
                 s.lastActivity = SystemClock.uptimeMillis();
+                needOomAdj = true;
                 if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
-                        permissionsReviewRequired, packageFrozen) != null) {
+                        permissionsReviewRequired, packageFrozen, true) != null) {
+                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
                     return 0;
                 }
             }
             setFgsRestrictionLocked(callingPackage, callingPid, callingUid, service, s, false);
 
             if (s.app != null) {
+                ProcessServiceRecord servicePsr = s.app.mServices;
                 if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
-                    s.app.treatLikeActivity = true;
+                    servicePsr.setTreatLikeActivity(true);
                 }
                 if (s.whitelistManager) {
-                    s.app.whitelistManager = true;
+                    servicePsr.mAllowlistManager = true;
                 }
                 // This could have made the service more important.
-                mAm.updateLruProcessLocked(s.app,
-                        (callerApp.hasActivitiesOrRecentTasks() && s.app.hasClientActivities())
-                                || (callerApp.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP
-                                        && (flags & Context.BIND_TREAT_LIKE_ACTIVITY) != 0),
+                mAm.updateLruProcessLocked(s.app, (callerApp.hasActivitiesOrRecentTasks()
+                            && servicePsr.hasClientActivities())
+                        || (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP
+                            && (flags & Context.BIND_TREAT_LIKE_ACTIVITY) != 0),
                         b.client);
-                mAm.updateOomAdjLocked(s.app, OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+                needOomAdj = true;
+                mAm.enqueueOomAdjTargetLocked(s.app);
+            }
+            if (needOomAdj) {
+                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
             }
 
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
@@ -2591,7 +2624,7 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false);
+                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -2611,14 +2644,15 @@
             final ServiceRecord srec = crec.binding.service;
             if (srec != null && (srec.serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
                 if (srec.app != null) {
+                    final ProcessServiceRecord psr = srec.app.mServices;
                     if (group > 0) {
-                        srec.app.connectionService = srec;
-                        srec.app.connectionGroup = group;
-                        srec.app.connectionImportance = importance;
+                        psr.setConnectionService(srec);
+                        psr.setConnectionGroup(group);
+                        psr.setConnectionImportance(importance);
                     } else {
-                        srec.app.connectionService = null;
-                        srec.app.connectionGroup = 0;
-                        srec.app.connectionImportance = 0;
+                        psr.setConnectionService(null);
+                        psr.setConnectionGroup(0);
+                        psr.setConnectionImportance(0);
                     }
                 } else {
                     if (group > 0) {
@@ -2647,7 +2681,7 @@
         try {
             while (clist.size() > 0) {
                 ConnectionRecord r = clist.get(0);
-                removeConnectionLocked(r, null, null);
+                removeConnectionLocked(r, null, null, true);
                 if (clist.size() > 0 && clist.get(0) == r) {
                     // In case it didn't get removed above, do it now.
                     Slog.wtf(TAG, "Connection " + r + " not removed for binder " + binder);
@@ -2656,15 +2690,14 @@
 
                 final ProcessRecord app = r.binding.service.app;
                 if (app != null) {
-                    if (app.whitelistManager) {
-                        updateWhitelistManagerLocked(app);
+                    final ProcessServiceRecord psr = app.mServices;
+                    if (psr.mAllowlistManager) {
+                        updateAllowlistManagerLocked(psr);
                     }
                     // This could have made the service less important.
                     if ((r.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
-                        app.treatLikeActivity = true;
-                        mAm.updateLruProcessLocked(app,
-                                app.hasClientActivities()
-                                || app.treatLikeActivity, null);
+                        psr.setTreatLikeActivity(true);
+                        mAm.updateLruProcessLocked(app, true, null);
                     }
                     mAm.enqueueOomAdjTargetLocked(app);
                 }
@@ -2698,7 +2731,7 @@
                         boolean inFg = false;
                         for (int i=b.apps.size()-1; i>=0; i--) {
                             ProcessRecord client = b.apps.valueAt(i).client;
-                            if (client != null && client.setSchedGroup
+                            if (client != null && client.mState.getSetSchedGroup()
                                     != ProcessList.SCHED_GROUP_BACKGROUND) {
                                 inFg = true;
                                 break;
@@ -2716,7 +2749,7 @@
                     }
                 }
 
-                serviceDoneExecutingLocked(r, inDestroying, false);
+                serviceDoneExecutingLocked(r, inDestroying, false, false);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -2788,12 +2821,24 @@
             r = smap.mServicesByIntent.get(filter);
             if (DEBUG_SERVICE && r != null) Slog.v(TAG_SERVICE, "Retrieved by intent: " + r);
         }
-        if (r != null && (r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
-                && !callingPackage.equals(r.packageName)) {
-            // If an external service is running within its own package, other packages
-            // should not bind to that instance.
-            r = null;
-            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Whoops, can't use existing external service");
+        if (r != null) {
+            // Compared to resolveService below, the ServiceRecord here is retrieved from
+            // ServiceMap so the package visibility doesn't apply to it. We need to filter it.
+            if (mAm.getPackageManagerInternal().filterAppAccess(r.packageName, callingUid,
+                    userId)) {
+                Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId
+                        + ": not found");
+                return null;
+            }
+            if ((r.serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) != 0
+                    && !callingPackage.equals(r.packageName)) {
+                // If an external service is running within its own package, other packages
+                // should not bind to that instance.
+                r = null;
+                if (DEBUG_SERVICE) {
+                    Slog.v(TAG_SERVICE, "Whoops, can't use existing external service");
+                }
+            }
         }
         if (r == null) {
             try {
@@ -2803,7 +2848,7 @@
                     flags |= PackageManager.MATCH_INSTANT;
                 }
                 // TODO: come back and remove this assumption to triage all services
-                ResolveInfo rInfo = mAm.getPackageManagerInternalLocked().resolveService(service,
+                ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service,
                         resolvedType, flags, userId, callingUid);
                 ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null;
                 if (sInfo == null) {
@@ -2876,7 +2921,7 @@
                         final long token = Binder.clearCallingIdentity();
                         try {
                             ResolveInfo rInfoForUserId0 =
-                                    mAm.getPackageManagerInternalLocked().resolveService(service,
+                                    mAm.getPackageManagerInternal().resolveService(service,
                                             resolvedType, flags, userId, callingUid);
                             if (rInfoForUserId0 == null) {
                                 Slog.w(TAG_SERVICE,
@@ -3000,7 +3045,7 @@
         // happen.)
         boolean timeoutNeeded = true;
         if ((mAm.mBootPhase < SystemService.PHASE_THIRD_PARTY_APPS_CAN_START)
-                && (r.app != null) && (r.app.pid == android.os.Process.myPid())) {
+                && (r.app != null) && (r.app.getPid() == ActivityManagerService.MY_PID)) {
 
             Slog.w(TAG, "Too early to start/bind service in system_server: Phase=" + mAm.mBootPhase
                     + " " + r.getComponentName());
@@ -3008,6 +3053,7 @@
         }
 
         long now = SystemClock.uptimeMillis();
+        ProcessServiceRecord psr;
         if (r.executeNesting == 0) {
             r.executeFg = fg;
             ServiceState stracker = r.getTracker();
@@ -3015,16 +3061,20 @@
                 stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
             }
             if (r.app != null) {
-                r.app.executingServices.add(r);
-                r.app.execServicesFg |= fg;
-                if (timeoutNeeded && r.app.executingServices.size() == 1) {
+                psr = r.app.mServices;
+                psr.startExecutingService(r);
+                psr.setExecServicesFg(psr.shouldExecServicesFg() || fg);
+                if (timeoutNeeded && psr.numberOfExecutingServices() == 1) {
                     scheduleServiceTimeoutLocked(r.app);
                 }
             }
-        } else if (r.app != null && fg && !r.app.execServicesFg) {
-            r.app.execServicesFg = true;
-            if (timeoutNeeded) {
-                scheduleServiceTimeoutLocked(r.app);
+        } else if (r.app != null && fg) {
+            psr = r.app.mServices;
+            if (!psr.shouldExecServicesFg()) {
+                psr.setExecServicesFg(true);
+                if (timeoutNeeded) {
+                    scheduleServiceTimeoutLocked(r.app);
+                }
             }
         }
         r.executeFg |= fg;
@@ -3034,7 +3084,7 @@
 
     private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
             boolean execInFg, boolean rebind) throws TransactionTooLargeException {
-        if (r.app == null || r.app.thread == null) {
+        if (r.app == null || r.app.getThread() == null) {
             // If service is not currently running, can't yet bind.
             return false;
         }
@@ -3043,9 +3093,9 @@
         if ((!i.requested || rebind) && i.apps.size() > 0) {
             try {
                 bumpServiceExecutingLocked(r, execInFg, "bind");
-                r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
-                r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
-                        r.app.getReportedProcState());
+                r.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
+                r.app.getThread().scheduleBindService(r, i.intent.getIntent(), rebind,
+                        r.app.mState.getReportedProcState());
                 if (!rebind) {
                     i.requested = true;
                 }
@@ -3055,13 +3105,13 @@
                 // Keep the executeNesting count accurate.
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
                 throw e;
             } catch (RemoteException e) {
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
                 return false;
             }
         }
@@ -3217,9 +3267,12 @@
         }
         try {
             bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true, false,
-                    false);
+                    false, true);
         } catch (TransactionTooLargeException e) {
             // Ignore, it's been logged and nothing upstack cares.
+        } finally {
+            /* Will be a no-op if nothing pending */
+            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
         }
     }
 
@@ -3262,9 +3315,10 @@
     }
 
     private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
-            boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen)
+            boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
+            boolean enqueueOomAdj)
             throws TransactionTooLargeException {
-        if (r.app != null && r.app.thread != null) {
+        if (r.app != null && r.app.getThread() != null) {
             sendServiceArgsLocked(r, execInFg, false);
             return null;
         }
@@ -3299,7 +3353,7 @@
                     + r.appInfo.uid + " for service "
                     + r.intent.getIntent() + ": user " + r.userId + " is stopped";
             Slog.w(TAG, msg);
-            bringDownServiceLocked(r);
+            bringDownServiceLocked(r, enqueueOomAdj);
             return msg;
         }
 
@@ -3322,19 +3376,26 @@
             app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
             if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
                         + " app=" + app);
-            if (app != null && app.thread != null) {
-                try {
-                    app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats);
-                    realStartServiceLocked(r, app, execInFg);
-                    return null;
-                } catch (TransactionTooLargeException e) {
-                    throw e;
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Exception when starting service " + r.shortInstanceName, e);
-                }
+            if (app != null) {
+                final IApplicationThread thread = app.getThread();
+                final int pid = app.getPid();
+                final UidRecord uidRecord = app.getUidRecord();
+                if (thread != null) {
+                    try {
+                        app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode,
+                                mAm.mProcessStats);
+                        realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
+                                enqueueOomAdj);
+                        return null;
+                    } catch (TransactionTooLargeException e) {
+                        throw e;
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Exception when starting service " + r.shortInstanceName, e);
+                    }
 
-                // If a dead object exception was thrown -- fall through to
-                // restart the application.
+                    // If a dead object exception was thrown -- fall through to
+                    // restart the application.
+                }
             }
         } else {
             // If this service runs in an isolated process, then each time
@@ -3359,14 +3420,14 @@
         if (app == null && !permissionsReviewRequired && !packageFrozen) {
             // TODO (chriswailes): Change the Zygote policy flags based on if the launch-for-service
             //  was initiated from a notification tap or not.
-            if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
-                    hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated, false)) == null) {
+            if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
+                        hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated, false)) == null) {
                 String msg = "Unable to launch app "
                         + r.appInfo.packageName + "/"
                         + r.appInfo.uid + " for service "
                         + r.intent.getIntent() + ": process is bad";
                 Slog.w(TAG, msg);
-                bringDownServiceLocked(r);
+                bringDownServiceLocked(r, enqueueOomAdj);
                 return msg;
             }
             if (isolated) {
@@ -3379,7 +3440,7 @@
                 Slog.v(TAG, "Whitelisting " + UserHandle.formatUid(r.appInfo.uid)
                         + " for fg-service launch");
             }
-            mAm.tempWhitelistUidLocked(r.appInfo.uid,
+            mAm.tempAllowlistUidLocked(r.appInfo.uid,
                     SERVICE_START_FOREGROUND_TIMEOUT, "fg-service-launch",
                     BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED);
         }
@@ -3394,7 +3455,7 @@
             if (r.startRequested) {
                 if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
                         "Applying delayed stop (in bring up): " + r);
-                stopServiceLocked(r);
+                stopServiceLocked(r, enqueueOomAdj);
             }
         }
 
@@ -3416,22 +3477,28 @@
      * The "start" here means bring up the instance in the client, and this method is called
      * from bindService() as well.
      */
-    private final void realStartServiceLocked(ServiceRecord r,
-            ProcessRecord app, boolean execInFg) throws RemoteException {
-        if (app.thread == null) {
+    private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,
+            IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,
+            boolean enqueueOomAdj) throws RemoteException {
+        if (thread == null) {
             throw new RemoteException();
         }
         if (DEBUG_MU)
             Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid
                     + ", ProcessRecord.uid = " + app.uid);
-        r.setProcess(app);
+        r.setProcess(app, thread, pid, uidRecord);
         r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
 
-        final boolean newService = app.startService(r);
+        final ProcessServiceRecord psr = app.mServices;
+        final boolean newService = psr.startService(r);
         bumpServiceExecutingLocked(r, execInFg, "create");
         mAm.updateLruProcessLocked(app, false, null);
-        updateServiceForegroundLocked(r.app, /* oomAdj= */ false);
-        mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        updateServiceForegroundLocked(psr, /* oomAdj= */ false);
+        if (enqueueOomAdj) {
+            mAm.enqueueOomAdjTargetLocked(app);
+        } else {
+            mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+        }
 
         boolean created = false;
         try {
@@ -3441,7 +3508,7 @@
                 nameTerm = lastPeriod >= 0 ? r.shortInstanceName.substring(lastPeriod)
                         : r.shortInstanceName;
                 EventLogTags.writeAmCreateService(
-                        r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
+                        r.userId, System.identityHashCode(r), nameTerm, r.app.uid, pid);
             }
 
             final int uid = r.appInfo.uid;
@@ -3452,10 +3519,10 @@
             mAm.mBatteryStatsService.noteServiceStartLaunch(uid, packageName, serviceName);
             mAm.notifyPackageUse(r.serviceInfo.packageName,
                                  PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
-            app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
-            app.thread.scheduleCreateService(r, r.serviceInfo,
+            app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
+            thread.scheduleCreateService(r, r.serviceInfo,
                     mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
-                    app.getReportedProcState());
+                    app.mState.getReportedProcState());
             r.postNotification();
             created = true;
         } catch (DeadObjectException e) {
@@ -3466,12 +3533,12 @@
             if (!created) {
                 // Keep the executeNesting count accurate.
                 final boolean inDestroying = mDestroyingServices.contains(r);
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
 
                 // Cleanup.
                 if (newService) {
-                    app.stopService(r);
-                    r.setProcess(null);
+                    psr.stopService(r);
+                    r.setProcess(null, null, 0, null);
                 }
 
                 // Retry.
@@ -3482,15 +3549,15 @@
         }
 
         if (r.whitelistManager) {
-            app.whitelistManager = true;
+            psr.mAllowlistManager = true;
         }
 
         requestServiceBindingsLocked(r, execInFg);
 
-        updateServiceClientActivitiesLocked(app, null, true);
+        updateServiceClientActivitiesLocked(psr, null, true);
 
         if (newService && created) {
-            app.addBoundClientUidsOfNewService(r);
+            psr.addBoundClientUidsOfNewService(r);
         }
 
         // If the service is in the started state, and there are no
@@ -3515,7 +3582,7 @@
             if (r.startRequested) {
                 if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
                         "Applying delayed stop (from start): " + r);
-                stopServiceLocked(r);
+                stopServiceLocked(r, enqueueOomAdj);
             }
         }
     }
@@ -3584,7 +3651,7 @@
         slice.setInlineCountLimit(4);
         Exception caughtException = null;
         try {
-            r.app.thread.scheduleServiceArgs(r, slice);
+            r.app.getThread().scheduleServiceArgs(r, slice);
         } catch (TransactionTooLargeException e) {
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large for " + args.size()
                     + " args, first: " + args.get(0).args);
@@ -3603,9 +3670,11 @@
         if (caughtException != null) {
             // Keep nesting count correct
             final boolean inDestroying = mDestroyingServices.contains(r);
-            for (int i = 0; i < args.size(); i++) {
-                serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+            for (int i = 0, size = args.size(); i < size; i++) {
+                serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
             }
+            /* Will be a no-op if nothing pending */
+            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
             if (caughtException instanceof TransactionTooLargeException) {
                 throw (TransactionTooLargeException)caughtException;
             }
@@ -3631,7 +3700,7 @@
     }
 
     private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
-            boolean hasConn) {
+            boolean hasConn, boolean enqueueOomAdj) {
         //Slog.i(TAG, "Bring down service:");
         //r.dump("  ");
 
@@ -3644,10 +3713,10 @@
             return;
         }
 
-        bringDownServiceLocked(r);
+        bringDownServiceLocked(r, enqueueOomAdj);
     }
 
-    private final void bringDownServiceLocked(ServiceRecord r) {
+    private void bringDownServiceLocked(ServiceRecord r, boolean enqueueOomAdj) {
         //Slog.i(TAG, "Bring down service:");
         //r.dump("  ");
 
@@ -3672,9 +3741,9 @@
             }
         }
 
+        boolean needOomAdj = false;
         // Tell the service that it has been unbound.
-        if (r.app != null && r.app.thread != null) {
-            boolean needOomAdj = false;
+        if (r.app != null && r.app.getThread() != null) {
             for (int i = r.bindings.size() - 1; i >= 0; i--) {
                 IntentBindRecord ibr = r.bindings.valueAt(i);
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down binding " + ibr
@@ -3685,21 +3754,17 @@
                         needOomAdj = true;
                         ibr.hasBound = false;
                         ibr.requested = false;
-                        r.app.thread.scheduleUnbindService(r,
+                        r.app.getThread().scheduleUnbindService(r,
                                 ibr.intent.getIntent());
                     } catch (Exception e) {
                         Slog.w(TAG, "Exception when unbinding service "
                                 + r.shortInstanceName, e);
                         needOomAdj = false;
-                        serviceProcessGoneLocked(r);
+                        serviceProcessGoneLocked(r, enqueueOomAdj);
                         break;
                     }
                 }
             }
-            if (needOomAdj) {
-                mAm.updateOomAdjLocked(r.app, true,
-                        OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
-            }
         }
 
         // Check to see if the service had been started as foreground, but being
@@ -3736,7 +3801,7 @@
         r.destroyTime = SystemClock.uptimeMillis();
         if (LOG_SERVICE_START_STOP) {
             EventLogTags.writeAmDestroyService(
-                    r.userId, System.identityHashCode(r), (r.app != null) ? r.app.pid : -1);
+                    r.userId, System.identityHashCode(r), (r.app != null) ? r.app.getPid() : -1);
         }
 
         final ServiceMap smap = getServiceMapLocked(r.userId);
@@ -3798,24 +3863,19 @@
         if (r.app != null) {
             mAm.mBatteryStatsService.noteServiceStopLaunch(r.appInfo.uid, r.name.getPackageName(),
                     r.name.getClassName());
-            r.app.stopService(r);
-            r.app.updateBoundClientUids();
-            if (r.whitelistManager) {
-                updateWhitelistManagerLocked(r.app);
-            }
-            if (r.app.thread != null) {
-                updateServiceForegroundLocked(r.app, false);
+            stopServiceAndUpdateAllowlistManagerLocked(r);
+            if (r.app.getThread() != null) {
+                updateServiceForegroundLocked(r.app.mServices, false);
                 try {
                     bumpServiceExecutingLocked(r, false, "destroy");
                     mDestroyingServices.add(r);
                     r.destroying = true;
-                    mAm.updateOomAdjLocked(r.app, true,
-                            OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
-                    r.app.thread.scheduleStopService(r);
+                    needOomAdj = true;
+                    r.app.getThread().scheduleStopService(r);
                 } catch (Exception e) {
                     Slog.w(TAG, "Exception when destroying service "
                             + r.shortInstanceName, e);
-                    serviceProcessGoneLocked(r);
+                    serviceProcessGoneLocked(r, enqueueOomAdj);
                 }
             } else {
                 if (DEBUG_SERVICE) Slog.v(
@@ -3826,6 +3886,13 @@
                 TAG_SERVICE, "Removed service that is not running: " + r);
         }
 
+        if (needOomAdj) {
+            if (enqueueOomAdj) {
+                mAm.enqueueOomAdjTargetLocked(r.app);
+            } else {
+                mAm.updateOomAdjLocked(r.app, true, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+            }
+        }
         if (r.bindings.size() > 0) {
             r.bindings.clear();
         }
@@ -3849,7 +3916,7 @@
     }
 
     void removeConnectionLocked(ConnectionRecord c, ProcessRecord skipApp,
-            ActivityServiceConnectionsHolder skipAct) {
+            ActivityServiceConnectionsHolder skipAct, boolean enqueueOomAdj) {
         IBinder binder = c.conn.asBinder();
         AppBindRecord b = c.binding;
         ServiceRecord s = b.service;
@@ -3866,16 +3933,17 @@
             c.activity.removeConnection(c);
         }
         if (b.client != skipApp) {
-            b.client.connections.remove(c);
+            final ProcessServiceRecord psr = b.client.mServices;
+            psr.removeConnection(c);
             if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
-                b.client.updateHasAboveClientLocked();
+                psr.updateHasAboveClientLocked();
             }
             // If this connection requested whitelist management, see if we should
             // now clear that state.
             if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) {
                 s.updateWhitelistManager();
                 if (!s.whitelistManager && s.app != null) {
-                    updateWhitelistManagerLocked(s.app);
+                    updateAllowlistManagerLocked(s.app.mServices);
                 }
             }
             // And do the same for bg activity starts ability.
@@ -3886,7 +3954,7 @@
                 s.updateIsAllowedBgFgsStartsByBinding();
             }
             if (s.app != null) {
-                updateServiceClientActivitiesLocked(s.app, c, true);
+                updateServiceClientActivitiesLocked(s.app.mServices, c, true);
             }
         }
         clist = mServiceConnections.get(binder);
@@ -3907,27 +3975,31 @@
         if (!c.serviceDead) {
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Disconnecting binding " + b.intent
                     + ": shouldUnbind=" + b.intent.hasBound);
-            if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
+            if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
                     && b.intent.hasBound) {
                 try {
                     bumpServiceExecutingLocked(s, false, "unbind");
                     if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
-                            && s.app.setProcState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+                            && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
                         // If this service's process is not already in the cached list,
                         // then update it in the LRU list here because this may be causing
                         // it to go down there and we want it to start out near the top.
                         mAm.updateLruProcessLocked(s.app, false, null);
                     }
-                    mAm.updateOomAdjLocked(s.app, true,
-                            OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    if (enqueueOomAdj) {
+                        mAm.enqueueOomAdjTargetLocked(s.app);
+                    } else {
+                        mAm.updateOomAdjLocked(s.app, true,
+                                OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                    }
                     b.intent.hasBound = false;
                     // Assume the client doesn't want to know about a rebind;
                     // we will deal with that later if it asks for one.
                     b.intent.doRebind = false;
-                    s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
+                    s.app.getThread().scheduleUnbindService(s, b.intent.intent.getIntent());
                 } catch (Exception e) {
                     Slog.w(TAG, "Exception when unbinding service " + s.shortInstanceName, e);
-                    serviceProcessGoneLocked(s);
+                    serviceProcessGoneLocked(s, enqueueOomAdj);
                 }
             }
 
@@ -3946,12 +4018,13 @@
                                 SystemClock.uptimeMillis());
                     }
                 }
-                bringDownServiceIfNeededLocked(s, true, hasAutoCreate);
+                bringDownServiceIfNeededLocked(s, true, hasAutoCreate, enqueueOomAdj);
             }
         }
     }
 
-    void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
+    void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res,
+            boolean enqueueOomAdj) {
         boolean inDestroying = mDestroyingServices.contains(r);
         if (r != null) {
             if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
@@ -4024,7 +4097,7 @@
                 }
             }
             final long origId = Binder.clearCallingIdentity();
-            serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+            serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
             Binder.restoreCallingIdentity(origId);
         } else {
             Slog.w(TAG, "Done executing unknown service from pid "
@@ -4032,7 +4105,7 @@
         }
     }
 
-    private void serviceProcessGoneLocked(ServiceRecord r) {
+    private void serviceProcessGoneLocked(ServiceRecord r, boolean enqueueOomAdj) {
         if (r.tracker != null) {
             int memFactor = mAm.mProcessStats.getMemFactorLocked();
             long now = SystemClock.uptimeMillis();
@@ -4041,11 +4114,11 @@
             r.tracker.setBound(false, memFactor, now);
             r.tracker.setStarted(false, memFactor, now);
         }
-        serviceDoneExecutingLocked(r, true, true);
+        serviceDoneExecutingLocked(r, true, true, enqueueOomAdj);
     }
 
     private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
-            boolean finishing) {
+            boolean finishing, boolean enqueueOomAdj) {
         if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
                 + ": nesting=" + r.executeNesting
                 + ", inDestroying=" + inDestroying + ", app=" + r.app);
@@ -4054,19 +4127,20 @@
         r.executeNesting--;
         if (r.executeNesting <= 0) {
             if (r.app != null) {
+                final ProcessServiceRecord psr = r.app.mServices;
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
                         "Nesting at 0 of " + r.shortInstanceName);
-                r.app.execServicesFg = false;
-                r.app.executingServices.remove(r);
-                if (r.app.executingServices.size() == 0) {
+                psr.setExecServicesFg(false);
+                psr.stopExecutingService(r);
+                if (psr.numberOfExecutingServices() == 0) {
                     if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING,
                             "No more executingServices of " + r.shortInstanceName);
                     mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
                 } else if (r.executeFg) {
                     // Need to re-evaluate whether the app still needs to be in the foreground.
-                    for (int i=r.app.executingServices.size()-1; i>=0; i--) {
-                        if (r.app.executingServices.valueAt(i).executeFg) {
-                            r.app.execServicesFg = true;
+                    for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
+                        if (psr.getExecutingServiceAt(i).executeFg) {
+                            psr.setExecServicesFg(true);
                             break;
                         }
                     }
@@ -4077,7 +4151,11 @@
                     mDestroyingServices.remove(r);
                     r.bindings.clear();
                 }
-                mAm.updateOomAdjLocked(r.app, true, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                if (enqueueOomAdj) {
+                    mAm.enqueueOomAdjTargetLocked(r.app);
+                } else {
+                    mAm.updateOomAdjLocked(r.app, true, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+                }
             }
             r.executeFg = false;
             if (r.tracker != null) {
@@ -4092,13 +4170,9 @@
             }
             if (finishing) {
                 if (r.app != null && !r.app.isPersistent()) {
-                    r.app.stopService(r);
-                    r.app.updateBoundClientUids();
-                    if (r.whitelistManager) {
-                        updateWhitelistManagerLocked(r.app);
-                    }
+                    stopServiceAndUpdateAllowlistManagerLocked(r);
                 }
-                r.setProcess(null);
+                r.setProcess(null, null, 0, null);
             }
         }
     }
@@ -4117,19 +4191,25 @@
                         continue;
                     }
 
+                    final IApplicationThread thread = proc.getThread();
+                    final int pid = proc.getPid();
+                    final UidRecord uidRecord = proc.getUidRecord();
                     mPendingServices.remove(i);
                     i--;
                     proc.addPackage(sr.appInfo.packageName, sr.appInfo.longVersionCode,
                             mAm.mProcessStats);
-                    realStartServiceLocked(sr, proc, sr.createdFromFg);
+                    realStartServiceLocked(sr, proc, thread, pid, uidRecord, sr.createdFromFg,
+                            true);
                     didSomething = true;
                     if (!isServiceNeededLocked(sr, false, false)) {
                         // We were waiting for this service to start, but it is actually no
                         // longer needed.  This could happen because bringDownServiceIfNeeded
                         // won't bring down a service that is pending...  so now the pending
                         // is done, so let's drop it.
-                        bringDownServiceLocked(sr);
+                        bringDownServiceLocked(sr, true);
                     }
+                    /* Will be a no-op if nothing pending */
+                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception in new application when starting service "
@@ -4157,7 +4237,8 @@
     }
 
     void processStartTimedOutLocked(ProcessRecord proc) {
-        for (int i=0; i<mPendingServices.size(); i++) {
+        boolean needOomAdj = false;
+        for (int i = 0, size = mPendingServices.size(); i < size; i++) {
             ServiceRecord sr = mPendingServices.get(i);
             if ((proc.uid == sr.appInfo.uid
                     && proc.processName.equals(sr.processName))
@@ -4165,10 +4246,15 @@
                 Slog.w(TAG, "Forcing bringing down service: " + sr);
                 sr.isolatedProc = null;
                 mPendingServices.remove(i);
+                size = mPendingServices.size();
                 i--;
-                bringDownServiceLocked(sr);
+                needOomAdj = true;
+                bringDownServiceLocked(sr, true);
             }
         }
+        if (needOomAdj) {
+            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+        }
     }
 
     private boolean collectPackageServicesLocked(String packageName, Set<String> filterByClasses,
@@ -4188,13 +4274,9 @@
                 didSomething = true;
                 Slog.i(TAG, "  Force stopping service " + service);
                 if (service.app != null && !service.app.isPersistent()) {
-                    service.app.stopService(service);
-                    service.app.updateBoundClientUids();
-                    if (service.whitelistManager) {
-                        updateWhitelistManagerLocked(service.app);
-                    }
+                    stopServiceAndUpdateAllowlistManagerLocked(service);
                 }
-                service.setProcess(null);
+                service.setProcess(null, null, 0, null);
                 service.isolatedProc = null;
                 if (mTmpCollectionResults == null) {
                     mTmpCollectionResults = new ArrayList<>();
@@ -4237,8 +4319,12 @@
         }
 
         if (mTmpCollectionResults != null) {
-            for (int i = mTmpCollectionResults.size() - 1; i >= 0; i--) {
-                bringDownServiceLocked(mTmpCollectionResults.get(i));
+            final int size = mTmpCollectionResults.size();
+            for (int i = size - 1; i >= 0; i--) {
+                bringDownServiceLocked(mTmpCollectionResults.get(i), true);
+            }
+            if (size > 0) {
+                mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
             }
             mTmpCollectionResults.clear();
         }
@@ -4279,16 +4365,18 @@
         }
 
         // Take care of any running services associated with the app.
+        boolean needOomAdj = false;
         for (int i = services.size() - 1; i >= 0; i--) {
             ServiceRecord sr = services.get(i);
             if (sr.startRequested) {
                 if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) {
                     Slog.i(TAG, "Stopping service " + sr.shortInstanceName + ": remove task");
-                    stopServiceLocked(sr);
+                    needOomAdj = true;
+                    stopServiceLocked(sr, true);
                 } else {
                     sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
                             sr.getLastStartId(), baseIntent, null, 0));
-                    if (sr.app != null && sr.app.thread != null) {
+                    if (sr.app != null && sr.app.getThread() != null) {
                         // We always run in the foreground, since this is called as
                         // part of the "remove task" UI operation.
                         try {
@@ -4300,16 +4388,20 @@
                 }
             }
         }
+        if (needOomAdj) {
+            mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+        }
     }
 
     final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
+        final ProcessServiceRecord psr = app.mServices;
         // Report disconnected services.
         if (false) {
             // XXX we are letting the client link to the service for
             // death notifications.
-            int numberOfRunningServices = app.numberOfRunningServices();
+            int numberOfRunningServices = psr.numberOfRunningServices();
             for (int sIndex = 0; sIndex < numberOfRunningServices; sIndex++) {
-                ServiceRecord r = app.getRunningServiceAt(sIndex);
+                ServiceRecord r = psr.getRunningServiceAt(sIndex);
                 ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
                 for (int conni = connections.size() - 1; conni >= 0; conni--) {
                     ArrayList<ConnectionRecord> cl = connections.valueAt(conni);
@@ -4331,25 +4423,25 @@
         }
 
         // Clean up any connections this application has to other services.
-        for (int i = app.connections.size() - 1; i >= 0; i--) {
-            ConnectionRecord r = app.connections.valueAt(i);
-            removeConnectionLocked(r, app, null);
+        for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
+            ConnectionRecord r = psr.getConnectionAt(i);
+            removeConnectionLocked(r, app, null, true);
         }
-        updateServiceConnectionActivitiesLocked(app);
-        app.connections.clear();
+        updateServiceConnectionActivitiesLocked(psr);
+        psr.removeAllConnections();
 
-        app.whitelistManager = false;
+        psr.mAllowlistManager = false;
 
         // Clear app state from services.
-        for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
-            ServiceRecord sr = app.getRunningServiceAt(i);
+        for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+            ServiceRecord sr = psr.getRunningServiceAt(i);
             mAm.mBatteryStatsService.noteServiceStopLaunch(sr.appInfo.uid, sr.name.getPackageName(),
                     sr.name.getClassName());
             if (sr.app != app && sr.app != null && !sr.app.isPersistent()) {
-                sr.app.stopService(sr);
-                sr.app.updateBoundClientUids();
+                sr.app.mServices.stopService(sr);
+                sr.app.mServices.updateBoundClientUids();
             }
-            sr.setProcess(null);
+            sr.setProcess(null, null, 0, null);
             sr.isolatedProc = null;
             sr.executeNesting = 0;
             sr.forceClearTracker();
@@ -4371,7 +4463,7 @@
                 for (int appi=b.apps.size()-1; appi>=0; appi--) {
                     final ProcessRecord proc = b.apps.keyAt(appi);
                     // If the process is already gone, skip it.
-                    if (proc.killedByAm || proc.thread == null) {
+                    if (proc.isKilledByAm() || proc.getThread() == null) {
                         continue;
                     }
                     // Only do this for processes that have an auto-create binding;
@@ -4379,7 +4471,7 @@
                     // service to restart.
                     final AppBindRecord abind = b.apps.valueAt(appi);
                     boolean hasCreate = false;
-                    for (int conni=abind.connections.size()-1; conni>=0; conni--) {
+                    for (int conni = abind.connections.size() - 1; conni >= 0; conni--) {
                         ConnectionRecord conn = abind.connections.valueAt(conni);
                         if ((conn.flags&(Context.BIND_AUTO_CREATE|Context.BIND_ALLOW_OOM_MANAGEMENT
                                 |Context.BIND_WAIVE_PRIORITY)) == Context.BIND_AUTO_CREATE) {
@@ -4391,13 +4483,15 @@
                         continue;
                     }
                     // XXX turned off for now until we have more time to get a better policy.
-                    if (false && proc != null && !proc.isPersistent() && proc.thread != null
-                            && proc.pid != 0 && proc.pid != ActivityManagerService.MY_PID
-                            && proc.setProcState >= ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
-                        proc.kill("bound to service " + sr.shortInstanceName
+                    /*
+                    if (false && proc != null && !proc.isPersistent() && proc.getThread() != null
+                            && proc.getPid() != 0 && proc.getPid() != ActivityManagerService.MY_PID
+                            && proc.mState.getSetProcState() >= PROCESS_STATE_LAST_ACTIVITY) {
+                        proc.killLocked("bound to service " + sr.shortInstanceName
                                 + " in dying proc " + (app != null ? app.processName : "??"),
                                 ApplicationExitInfo.REASON_OTHER, true);
                     }
+                    */
                 }
             }
         }
@@ -4405,14 +4499,14 @@
         ServiceMap smap = getServiceMapLocked(app.userId);
 
         // Now do remaining service cleanup.
-        for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
-            ServiceRecord sr = app.getRunningServiceAt(i);
+        for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+            ServiceRecord sr = psr.getRunningServiceAt(i);
 
             // Unless the process is persistent, this process record is going away,
             // so make sure the service is cleaned out of it.
             if (!app.isPersistent()) {
-                app.stopService(sr);
-                app.updateBoundClientUids();
+                psr.stopService(sr);
+                psr.updateBoundClientUids();
             }
 
             // Sanity check: if the service listed for the app is not one
@@ -4434,11 +4528,11 @@
                 Slog.w(TAG, "Service crashed " + sr.crashCount
                         + " times, stopping: " + sr);
                 EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
-                        sr.userId, sr.crashCount, sr.shortInstanceName, app.pid);
-                bringDownServiceLocked(sr);
+                        sr.userId, sr.crashCount, sr.shortInstanceName, sr.app.getPid());
+                bringDownServiceLocked(sr, true);
             } else if (!allowRestart
                     || !mAm.mUserController.isUserRunning(sr.userId, 0)) {
-                bringDownServiceLocked(sr);
+                bringDownServiceLocked(sr, true);
             } else {
                 final boolean scheduled = scheduleServiceRestartLocked(sr, true /* allowCancel */);
 
@@ -4446,7 +4540,7 @@
                 // extreme case of so many attempts to deliver a command
                 // that it failed we also will stop it here.
                 if (!scheduled) {
-                    bringDownServiceLocked(sr);
+                    bringDownServiceLocked(sr, true);
                 } else if (sr.canStopIfKilled(false /* isStartCanceled */)) {
                     // Update to stopped state because the explicit start is gone. The service is
                     // scheduled to restart for other reason (e.g. connections) so we don't bring
@@ -4460,9 +4554,11 @@
             }
         }
 
+        mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+
         if (!allowRestart) {
-            app.stopAllServices();
-            app.clearBoundClientUids();
+            psr.stopAllServices();
+            psr.clearBoundClientUids();
 
             // Make sure there are no more restarting services for this process.
             for (int i=mRestartingServices.size()-1; i>=0; i--) {
@@ -4501,7 +4597,7 @@
             }
         }
 
-        app.executingServices.clear();
+        psr.stopAllExecutingServices();
     }
 
     ActivityManager.RunningServiceInfo makeRunningServiceInfoLocked(ServiceRecord r) {
@@ -4509,7 +4605,7 @@
             new ActivityManager.RunningServiceInfo();
         info.service = r.name;
         if (r.app != null) {
-            info.pid = r.app.pid;
+            info.pid = r.app.getPid();
         }
         info.uid = r.appInfo.uid;
         info.process = r.processName;
@@ -4525,7 +4621,7 @@
         if (r.startRequested) {
             info.flags |= ActivityManager.RunningServiceInfo.FLAG_STARTED;
         }
-        if (r.app != null && r.app.pid == ActivityManagerService.MY_PID) {
+        if (r.app != null && r.app.getPid() == ActivityManagerService.MY_PID) {
             info.flags |= ActivityManager.RunningServiceInfo.FLAG_SYSTEM_PROCESS;
         }
         if (r.app != null && r.app.isPersistent()) {
@@ -4624,16 +4720,17 @@
                 // The app's being debugged, ignore timeout.
                 return;
             }
-            if (proc.executingServices.size() == 0 || proc.thread == null) {
+            final ProcessServiceRecord psr = proc.mServices;
+            if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null) {
                 return;
             }
             final long now = SystemClock.uptimeMillis();
             final long maxTime =  now -
-                    (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+                    (psr.shouldExecServicesFg() ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
             ServiceRecord timeout = null;
             long nextTime = 0;
-            for (int i=proc.executingServices.size()-1; i>=0; i--) {
-                ServiceRecord sr = proc.executingServices.valueAt(i);
+            for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) {
+                ServiceRecord sr = psr.getExecutingServiceAt(i);
                 if (sr.executingStart < maxTime) {
                     timeout = sr;
                     break;
@@ -4642,7 +4739,7 @@
                     nextTime = sr.executingStart;
                 }
             }
-            if (timeout != null && mAm.mProcessList.mLruProcesses.contains(proc)) {
+            if (timeout != null && mAm.mProcessList.isInLruListLOSP(proc)) {
                 Slog.w(TAG, "Timeout executing service: " + timeout);
                 StringWriter sw = new StringWriter();
                 PrintWriter pw = new FastPrintWriter(sw, false, 1024);
@@ -4657,7 +4754,7 @@
                 Message msg = mAm.mHandler.obtainMessage(
                         ActivityManagerService.SERVICE_TIMEOUT_MSG);
                 msg.obj = proc;
-                mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
+                mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg()
                         ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
             }
         }
@@ -4684,7 +4781,7 @@
                 Slog.i(TAG, "Service foreground-required timeout for " + r);
             }
             r.fgWaiting = false;
-            stopServiceLocked(r);
+            stopServiceLocked(r, false);
         }
 
         if (app != null) {
@@ -4711,24 +4808,24 @@
     }
 
     void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) {
-        mAm.crashApplication(app.uid, app.pid, app.info.packageName, app.userId,
+        mAm.crashApplication(app.uid, app.getPid(), app.info.packageName, app.userId,
                 "Context.startForegroundService() did not then call Service.startForeground(): "
                     + serviceRecord, false /*force*/);
     }
 
     void scheduleServiceTimeoutLocked(ProcessRecord proc) {
-        if (proc.executingServices.size() == 0 || proc.thread == null) {
+        if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) {
             return;
         }
         Message msg = mAm.mHandler.obtainMessage(
                 ActivityManagerService.SERVICE_TIMEOUT_MSG);
         msg.obj = proc;
-        mAm.mHandler.sendMessageDelayed(msg,
-                proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+        mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()
+                ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
     }
 
     void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
-        if (r.app.executingServices.size() == 0 || r.app.thread == null) {
+        if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) {
             return;
         }
         Message msg = mAm.mHandler.obtainMessage(
@@ -4925,7 +5022,7 @@
             if (proc == null) {
                 return;
             }
-            final IApplicationThread thread = proc.thread;
+            final IApplicationThread thread = proc.getThread();
             if (thread == null) {
                 return;
             }
@@ -5251,20 +5348,21 @@
             pw.print(Integer.toHexString(System.identityHashCode(r)));
             pw.print(" pid=");
             if (r.app != null) {
-                pw.print(r.app.pid);
+                pw.print(r.app.getPid());
                 pw.print(" user="); pw.println(r.userId);
             } else pw.println("(not running)");
             if (dumpAll) {
                 r.dump(pw, innerPrefix);
             }
         }
-        if (r.app != null && r.app.thread != null) {
+        IApplicationThread thread;
+        if (r.app != null && (thread = r.app.getThread()) != null) {
             pw.print(prefix); pw.println("  Client:");
             pw.flush();
             try {
                 TransferPipe tp = new TransferPipe();
                 try {
-                    r.app.thread.dumpService(tp.getWriteFd(), r, args);
+                    thread.dumpService(tp.getWriteFd(), r, args);
                     tp.setBufferPrefix(prefix + "    ");
                     tp.go(fd);
                 } finally {
@@ -5326,10 +5424,10 @@
             boolean allowBackgroundActivityStarts) {
         int ret = FGS_FEATURE_DENIED;
 
-        final int uidState = mAm.getUidState(callingUid);
+        final int uidState = mAm.getUidStateLocked(callingUid);
         if (ret == FGS_FEATURE_DENIED) {
             // Is the calling UID at PROCESS_STATE_TOP or above?
-            if (uidState <= ActivityManager.PROCESS_STATE_TOP) {
+            if (uidState <= PROCESS_STATE_TOP) {
                 ret = FGS_FEATURE_ALLOWED_BY_UID_STATE;
             }
         }
@@ -5370,14 +5468,16 @@
         }
 
         if (ret == FGS_FEATURE_DENIED) {
-            for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
-                final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
+            final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> {
                 if (pr.uid == callingUid) {
                     if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) {
-                        ret = FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER;
-                        break;
+                        return FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER;
                     }
                 }
+                return null;
+            });
+            if (allowedType != null) {
+                ret = allowedType;
             }
         }
 
@@ -5432,46 +5532,37 @@
         int ret = allowWhileInUse;
 
         final StringBuilder sb = new StringBuilder(64);
-        final int uidState = mAm.getUidState(callingUid);
+        final int uidState = mAm.getUidStateLocked(callingUid);
         if (ret == FGS_FEATURE_DENIED) {
             // Is the calling UID at PROCESS_STATE_TOP or above?
-            if (uidState <= ActivityManager.PROCESS_STATE_TOP) {
+            if (uidState <= PROCESS_STATE_TOP) {
                 sb.append("uidState=").append(uidState);
                 ret = FGS_FEATURE_ALLOWED_BY_UID_STATE;
             }
         }
 
         if (ret == FGS_FEATURE_DENIED) {
-            for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
-                final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
-                if (pr.uid == callingUid) {
-                    if (pr.mAllowStartFgs) {
-                        ret = FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD;
-                        break;
-                    } else if (pr.isAllowedStartFgsState()) {
-                        ret = FGS_FEATURE_ALLOWED_BY_PROC_STATE;
-                        break;
-                    }
-                }
-            }
-        }
-
-        if (ret == FGS_FEATURE_DENIED) {
-            for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
-                final ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
-                if (pr.uid == callingUid) {
-                    if (pr.areBackgroundFgsStartsAllowedByToken()) {
-                        ret = FGS_FEATURE_ALLOWED_BY_FGS_BINDING;
-                        break;
+            final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> {
+                if (app.uid == callingUid) {
+                    final ProcessStateRecord state = app.mState;
+                    if (state.getAllowedStartFgs() != FGS_FEATURE_DENIED) {
+                        return state.getAllowedStartFgs();
+                    } else if (state.isAllowedStartFgsState()) {
+                        return FGS_FEATURE_ALLOWED_BY_PROC_STATE;
+                    } else if (state.areBackgroundFgsStartsAllowedByToken()) {
+                        return FGS_FEATURE_ALLOWED_BY_FGS_BINDING;
                     } else {
-                        final ActiveInstrumentation instr = pr.getActiveInstrumentation();
+                        final ActiveInstrumentation instr = app.getActiveInstrumentation();
                         if (instr != null
                                 && instr.mHasBackgroundForegroundServiceStartsPermission) {
-                            ret = FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION;
-                            break;
+                            return FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION;
                         }
                     }
                 }
+                return null;
+            });
+            if (allowedType != null) {
+                ret = allowedType;
             }
         }
 
@@ -5490,7 +5581,7 @@
         }
 
         if (ret == FGS_FEATURE_DENIED) {
-            if (mAm.isWhitelistedForFgsStartLocked(callingUid)) {
+            if (mAm.isAllowlistedForFgsStartLOSP(callingUid)) {
                 // uid is on DeviceIdleController's user/system allowlist
                 // or AMS's FgsStartTempAllowList.
                 ret = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST;
@@ -5566,7 +5657,7 @@
         return CompatChanges.isChangeEnabled(FGS_BG_START_USE_EXEMPTION_LIST_CHANGE_ID, uid);
     }
 
-    private static String fgsCodeToString(@FgsFeatureRetCode int code) {
+    static String fgsCodeToString(@FgsFeatureRetCode int code) {
         switch (code) {
             case FGS_FEATURE_DENIED:
                 return "DENIED";
@@ -5593,7 +5684,7 @@
             case FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION:
                 return "ALLOWED_BY_BACKGROUND_FGS_PERMISSION";
             case FGS_FEATURE_ALLOWED_BY_ALLOWLIST:
-                return "ALLOWED_BY_WHITELIST";
+                return "ALLOWED_BY_ALLOWLIST";
             case FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER:
                 return "ALLOWED_BY_DEVICE_OWNER";
             case FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST:
@@ -5652,4 +5743,23 @@
         return mAm.mConstants.mFlagFgsStartRestrictionEnabled
                 && CompatChanges.isChangeEnabled(FGS_BG_START_RESTRICTION_CHANGE_ID, r.appInfo.uid);
     }
+
+    private void logFgsBackgroundStart(ServiceRecord r) {
+        // Only log if FGS is started from background.
+        if (!isFgsBgStart(r.mAllowStartForeground)) {
+            return;
+        }
+        if (!r.mLoggedInfoAllowStartForeground) {
+            final String msg = "Background started FGS: "
+                    + ((r.mAllowStartForeground != FGS_FEATURE_DENIED) ? "Allowed " : "Disallowed ")
+                    + r.mInfoAllowStartForeground;
+            Slog.wtfQuiet(TAG, msg);
+            if (r.mAllowStartForeground != FGS_FEATURE_DENIED) {
+                Slog.i(TAG, msg);
+            } else {
+                Slog.w(TAG, msg);
+            }
+            r.mLoggedInfoAllowStartForeground = true;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActiveUids.java b/services/core/java/com/android/server/am/ActiveUids.java
index 27be53a..31db95b7 100644
--- a/services/core/java/com/android/server/am/ActiveUids.java
+++ b/services/core/java/com/android/server/am/ActiveUids.java
@@ -21,21 +21,29 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 
 /** Class for tracking active uids for running processes. */
 final class ActiveUids {
 
-    private ActivityManagerService mService;
+    private final ActivityManagerService mService;
+    private final ActivityManagerGlobalLock mProcLock;
 
-    private boolean mPostChangesToAtm;
+    private final boolean mPostChangesToAtm;
+
+    @CompositeRWLock({"mService", "mProcLock"})
     private final SparseArray<UidRecord> mActiveUids = new SparseArray<>();
 
     ActiveUids(ActivityManagerService service, boolean postChangesToAtm) {
         mService = service;
+        mProcLock = service != null ? service.mProcLock : null;
         mPostChangesToAtm = postChangesToAtm;
     }
 
+    @GuardedBy({"mService", "mProcLock"})
     void put(int uid, UidRecord value) {
         mActiveUids.put(uid, value);
         if (mPostChangesToAtm) {
@@ -43,6 +51,7 @@
         }
     }
 
+    @GuardedBy({"mService", "mProcLock"})
     void remove(int uid) {
         mActiveUids.remove(uid);
         if (mPostChangesToAtm) {
@@ -50,38 +59,45 @@
         }
     }
 
+    @GuardedBy({"mService", "mProcLock"})
     void clear() {
         mActiveUids.clear();
         // It is only called for a temporal container with mPostChangesToAtm == false or test case.
         // So there is no need to notify activity task manager.
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     UidRecord get(int uid) {
         return mActiveUids.get(uid);
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int size() {
         return mActiveUids.size();
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     UidRecord valueAt(int index) {
         return mActiveUids.valueAt(index);
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int keyAt(int index) {
         return mActiveUids.keyAt(index);
     }
 
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int indexOfKey(int uid) {
         return mActiveUids.indexOfKey(uid);
     }
 
-    boolean dump(PrintWriter pw, String dumpPackage, int dumpAppId,
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean dump(final PrintWriter pw, String dumpPackage, int dumpAppId,
             String header, boolean needSep) {
         boolean printed = false;
         for (int i = 0; i < mActiveUids.size(); i++) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
-            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
+            if (dumpPackage != null && UserHandle.getAppId(uidRec.getUid()) != dumpAppId) {
                 continue;
             }
             if (!printed) {
@@ -91,16 +107,16 @@
                 }
                 pw.print("  "); pw.println(header);
             }
-            pw.print("    UID "); UserHandle.formatUid(pw, uidRec.uid);
+            pw.print("    UID "); UserHandle.formatUid(pw, uidRec.getUid());
             pw.print(": "); pw.println(uidRec);
-            pw.print("      curProcState="); pw.print(uidRec.mCurProcState);
+            pw.print("      curProcState="); pw.print(uidRec.getCurProcState());
             pw.print(" curCapability=");
-            ActivityManager.printCapabilitiesFull(pw, uidRec.curCapability);
+            ActivityManager.printCapabilitiesFull(pw, uidRec.getCurCapability());
             pw.println();
-            for (int j = uidRec.procRecords.size() - 1; j >= 0; j--) {
+            uidRec.forEachProcess(app -> {
                 pw.print("      proc=");
-                pw.println(uidRec.procRecords.valueAt(j));
-            }
+                pw.println(app);
+            });
         }
         return printed;
     }
@@ -108,7 +124,7 @@
     void dumpProto(ProtoOutputStream proto, String dumpPackage, int dumpAppId, long fieldId) {
         for (int i = 0; i < mActiveUids.size(); i++) {
             UidRecord uidRec = mActiveUids.valueAt(i);
-            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
+            if (dumpPackage != null && UserHandle.getAppId(uidRec.getUid()) != dumpAppId) {
                 continue;
             }
             uidRec.dumpDebug(proto, fieldId);
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/services/core/java/com/android/server/am/ActivityManagerGlobalLock.java
similarity index 73%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to services/core/java/com/android/server/am/ActivityManagerGlobalLock.java
index 8b5b251..7823038 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/services/core/java/com/android/server/am/ActivityManagerGlobalLock.java
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
+package com.android.server.am;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Interface to mark whichever class with the implementation is considered as a global lock
+ * to be used in the package of ActivityManagerService.
  */
-@android.annotation.Hide
-package com.android.role;
+interface ActivityManagerGlobalLock {
+}
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/services/core/java/com/android/server/am/ActivityManagerProcLock.java
similarity index 64%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to services/core/java/com/android/server/am/ActivityManagerProcLock.java
index 8b5b251..3ef19ad 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/services/core/java/com/android/server/am/ActivityManagerProcLock.java
@@ -14,9 +14,15 @@
  * limitations under the License.
  */
 
+package com.android.server.am;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Class that is used to generate an instance of the {@link ActivityManagerService#mProcLock},
+ * so the CPU booster can identify the critical section.
+ *
+ * <p>
+ * Use "-Lpr" as the suffix of functions locked with this lock.
+ * </p>
  */
-@android.annotation.Hide
-package com.android.role;
+final class ActivityManagerProcLock implements ActivityManagerGlobalLock {
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 05f2fa0..9382e1a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -27,6 +27,7 @@
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE;
 import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
 import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
@@ -99,7 +100,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
@@ -111,7 +111,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -146,6 +145,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.ActivityThread;
+import android.app.AnrController;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
@@ -249,9 +249,9 @@
 import android.os.PowerManager;
 import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
+import android.os.PowerWhitelistManager.TempAllowListType;
 import android.os.Process;
 import android.os.RemoteCallback;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -278,7 +278,6 @@
 import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.DebugUtils;
 import android.util.EventLog;
 import android.util.IntArray;
 import android.util.Log;
@@ -296,6 +295,7 @@
 import android.view.WindowManager;
 import android.view.autofill.AutofillManagerInternal;
 
+import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsActiveCallback;
@@ -319,6 +319,7 @@
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
+import com.android.internal.policy.AttributeCache;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -329,7 +330,6 @@
 import com.android.internal.util.function.HeptFunction;
 import com.android.internal.util.function.QuadFunction;
 import com.android.server.AlarmManagerInternal;
-import com.android.server.AttributeCache;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.DisplayThread;
 import com.android.server.IntentResolver;
@@ -395,9 +395,10 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class ActivityManagerService extends IActivityManager.Stub
-        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
+        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
 
     /**
      * Priority we boost main thread and RT of top app to.
@@ -417,7 +418,6 @@
     static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
     static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
     private static final String TAG_POWER = TAG + POSTFIX_POWER;
-    static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
     static final String TAG_PROCESSES = TAG + POSTFIX_PROCESSES;
     private static final String TAG_SERVICE = TAG + POSTFIX_SERVICE;
     private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
@@ -528,28 +528,74 @@
 
     final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter();
 
+    @CompositeRWLock({"this", "mProcLock"})
     final ArrayList<ActiveInstrumentation> mActiveInstrumentation = new ArrayList<>();
 
     public final IntentFirewall mIntentFirewall;
 
     public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
 
+    /**
+     * The global lock for AMS, it's de-facto the ActivityManagerService object as of now.
+     */
+    final ActivityManagerGlobalLock mGlobalLock = ActivityManagerService.this;
+
+    /**
+     * Whether or not to enable the {@link #mProcLock}. If {@code false}, the {@link #mProcLock}
+     * will be equivalent to the {@link #mGlobalLock}.
+     */
+    private static final boolean ENABLE_PROC_LOCK = true;
+
+    /**
+     * The lock for process management.
+     *
+     * <p>
+     * This lock is widely used in conjunction with the {@link #mGlobalLock} at present,
+     * where it'll require any of the locks to read from a data class, and both of the locks
+     * to write into that data class.
+     *
+     * For the naming convention of function suffixes:
+     * <ul>
+     *    <li>-LOSP:    Locked with any Of global am Service or Process lock</li>
+     *    <li>-LSP:     Locked with both of global am Service and Process lock</li>
+     *    <li>-Locked:  Locked with global am service lock alone</li>
+     *    <li>-LPr:     Locked with Process lock alone</li>
+     * </ul>
+     * For the simplicity, the getters/setters of the fields in data classes usually don't end with
+     * the above suffixes even if they're guarded by the locks here.
+     * </p>
+     *
+     * <p>
+     * In terms of locking order, it should be right below to the {@link #mGlobalLock},
+     * and above everything else which used to be underneath the {@link #mGlobalLock}.
+     * As of today, the core components(services/providers/broadcasts) are still guarded by
+     * the {@link #mGlobalLock} alone, so be cautious, avoid from acquiring the {@link #mGlobalLock}
+     * while holding this lock.
+     * </p>
+     *
+     */
+    final ActivityManagerGlobalLock mProcLock = ENABLE_PROC_LOCK
+            ? new ActivityManagerProcLock() : mGlobalLock;
+
     // Whether we should use SCHED_FIFO for UI and RenderThreads.
-    boolean mUseFifoUiScheduling = false;
+    final boolean mUseFifoUiScheduling;
 
     // Use an offload queue for long broadcasts, e.g. BOOT_COMPLETED.
     // For simplicity, since we statically declare the size of the array of BroadcastQueues,
     // we still create this new offload queue, but never ever put anything on it.
-    boolean mEnableOffloadQueue;
+    final boolean mEnableOffloadQueue;
 
-    BroadcastQueue mFgBroadcastQueue;
-    BroadcastQueue mBgBroadcastQueue;
-    BroadcastQueue mOffloadBroadcastQueue;
+    final BroadcastQueue mFgBroadcastQueue;
+    final BroadcastQueue mBgBroadcastQueue;
+    final BroadcastQueue mOffloadBroadcastQueue;
     // Convenient for easy iteration over the queues. Foreground is first
     // so that dispatch of foreground broadcasts gets precedence.
     final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[3];
 
+    @GuardedBy("this")
     BroadcastStats mLastBroadcastStats;
+
+    @GuardedBy("this")
     BroadcastStats mCurBroadcastStats;
 
     BroadcastQueue broadcastQueueForIntent(Intent intent) {
@@ -570,10 +616,11 @@
 
     /**
      * The package name of the DeviceOwner. This package is not permitted to have its data cleared.
+     * <p>Not actually used</p>
      */
-    String mDeviceOwnerName;
+    private volatile String mDeviceOwnerName;
 
-    private int mDeviceOwnerUid = Process.INVALID_UID;
+    private volatile int mDeviceOwnerUid = Process.INVALID_UID;
 
     /**
      * Map userId to its companion app uids.
@@ -644,6 +691,25 @@
         sThreadPriorityBooster.reset();
     }
 
+    private static ThreadPriorityBooster sProcThreadPriorityBooster = new ThreadPriorityBooster(
+            THREAD_PRIORITY_FOREGROUND, LockGuard.INDEX_PROC);
+
+    static void boostPriorityForProcLockedSection() {
+        if (ENABLE_PROC_LOCK) {
+            sProcThreadPriorityBooster.boost();
+        } else {
+            sThreadPriorityBooster.boost();
+        }
+    }
+
+    static void resetPriorityAfterProcLockedSection() {
+        if (ENABLE_PROC_LOCK) {
+            sProcThreadPriorityBooster.reset();
+        } else {
+            sThreadPriorityBooster.reset();
+        }
+    }
+
     /**
      * Process management.
      */
@@ -662,22 +728,25 @@
     final ProcessStatsService mProcessStats;
 
     /**
-     * Non-persistent appId whitelist for background restrictions
+     * Non-persistent appId allowlist for background restrictions
      */
-    int[] mBackgroundAppIdWhitelist = new int[] {
+    @CompositeRWLock({"this", "mProcLock"})
+    private int[] mBackgroundAppIdAllowlist = new int[] {
             BLUETOOTH_UID
     };
 
     /**
      * Broadcast actions that will always be deliverable to unlaunched/background apps
      */
-    ArraySet<String> mBackgroundLaunchBroadcasts;
+    @GuardedBy("this")
+    private ArraySet<String> mBackgroundLaunchBroadcasts;
 
     /**
      * When an app has restrictions on the other apps that can have associations with it,
      * it appears here with a set of the allowed apps and also track debuggability of the app.
      */
-    ArrayMap<String, PackageAssociationInfo> mAllowedAssociations;
+    @GuardedBy("this")
+    private ArrayMap<String, PackageAssociationInfo> mAllowedAssociations;
 
     /**
      * Tracks association information for a particular package along with debuggability.
@@ -755,24 +824,24 @@
             return mPidMap.indexOfKey(key);
         }
 
-        void doAddInternal(ProcessRecord app) {
-            mPidMap.put(app.pid, app);
+        void doAddInternal(int pid, ProcessRecord app) {
+            mPidMap.put(pid, app);
         }
 
-        boolean doRemoveInternal(ProcessRecord app) {
-            final ProcessRecord existingApp = mPidMap.get(app.pid);
-            if (existingApp != null && existingApp.startSeq == app.startSeq) {
-                mPidMap.remove(app.pid);
+        boolean doRemoveInternal(int pid, ProcessRecord app) {
+            final ProcessRecord existingApp = mPidMap.get(pid);
+            if (existingApp != null && existingApp.getStartSeq() == app.getStartSeq()) {
+                mPidMap.remove(pid);
                 return true;
             }
             return false;
         }
 
-        boolean doRemoveIfNoThreadInternal(ProcessRecord app) {
-            if (app == null || app.thread != null) {
+        boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
+            if (app == null || app.getThread() != null) {
                 return false;
             }
-            return doRemoveInternal(app);
+            return doRemoveInternal(pid, app);
         }
     }
 
@@ -783,18 +852,20 @@
      * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
      * method.
      */
+    @GuardedBy("this")
     void addPidLocked(ProcessRecord app) {
+        final int pid = app.getPid();
         synchronized (mPidsSelfLocked) {
-            mPidsSelfLocked.doAddInternal(app);
+            mPidsSelfLocked.doAddInternal(pid, app);
         }
         synchronized (sActiveProcessInfoSelfLocked) {
             if (app.processInfo != null) {
-                sActiveProcessInfoSelfLocked.put(app.pid, app.processInfo);
+                sActiveProcessInfoSelfLocked.put(pid, app.processInfo);
             } else {
-                sActiveProcessInfoSelfLocked.remove(app.pid);
+                sActiveProcessInfoSelfLocked.remove(pid);
             }
         }
-        mAtmInternal.onProcessMapped(app.pid, app.getWindowProcessController());
+        mAtmInternal.onProcessMapped(pid, app.getWindowProcessController());
     }
 
     /**
@@ -802,16 +873,17 @@
      * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
      * method.
      */
-    void removePidLocked(ProcessRecord app) {
+    @GuardedBy("this")
+    void removePidLocked(int pid, ProcessRecord app) {
         final boolean removed;
         synchronized (mPidsSelfLocked) {
-            removed = mPidsSelfLocked.doRemoveInternal(app);
+            removed = mPidsSelfLocked.doRemoveInternal(pid, app);
         }
         if (removed) {
             synchronized (sActiveProcessInfoSelfLocked) {
-                sActiveProcessInfoSelfLocked.remove(app.pid);
+                sActiveProcessInfoSelfLocked.remove(pid);
             }
-            mAtmInternal.onProcessUnMapped(app.pid);
+            mAtmInternal.onProcessUnMapped(pid);
         }
     }
 
@@ -820,16 +892,18 @@
      * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
      * method.
      */
-    boolean removePidIfNoThread(ProcessRecord app) {
+    @GuardedBy("this")
+    private boolean removePidIfNoThreadLocked(ProcessRecord app) {
         final boolean removed;
+        final int pid = app.getPid();
         synchronized (mPidsSelfLocked) {
-            removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(app);
+            removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
         }
         if (removed) {
             synchronized (sActiveProcessInfoSelfLocked) {
-                sActiveProcessInfoSelfLocked.remove(app.pid);
+                sActiveProcessInfoSelfLocked.remove(pid);
             }
-            mAtmInternal.onProcessUnMapped(app.pid);
+            mAtmInternal.onProcessUnMapped(pid);
         }
         return removed;
     }
@@ -866,6 +940,7 @@
             proto.end(pToken);
         }
     }
+    @GuardedBy("this")
     final SparseArray<ImportanceToken> mImportantProcesses = new SparseArray<ImportanceToken>();
 
     /**
@@ -873,19 +948,16 @@
      * system was ready.  We don't start them at that point, but ensure they
      * are started by the time booting is complete.
      */
+    @GuardedBy("this")
     final ArrayList<ProcessRecord> mProcessesOnHold = new ArrayList<ProcessRecord>();
 
     /**
      * List of persistent applications that are in the process
      * of being started.
      */
+    @GuardedBy("this")
     final ArrayList<ProcessRecord> mPersistentStartingProcesses = new ArrayList<ProcessRecord>();
 
-    /**
-     * List of processes that should gc as soon as things are idle.
-     */
-    final ArrayList<ProcessRecord> mProcessesToGc = new ArrayList<ProcessRecord>();
-
     private final ActivityMetricsLaunchObserver mActivityLaunchObserver =
             new ActivityMetricsLaunchObserver() {
         @Override
@@ -915,7 +987,7 @@
         }
     };
 
-    private boolean mBinderTransactionTrackingEnabled = false;
+    private volatile boolean mBinderTransactionTrackingEnabled = false;
 
     /**
      * Fingerprints (hashCode()) of stack traces that we've
@@ -923,6 +995,7 @@
      * something (rogue user app) forces this over
      * MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared.
      */
+    @GuardedBy("mAlreadyLoggedViolatedStacks")
     private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>();
     private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
 
@@ -930,6 +1003,7 @@
      * Keeps track of all IIntentReceivers that have been registered for broadcasts.
      * Hash keys are the receiver IBinder, hash value is a ReceiverList.
      */
+    @GuardedBy("this")
     final HashMap<IBinder, ReceiverList> mRegisteredReceivers = new HashMap<>();
 
     /**
@@ -982,6 +1056,7 @@
      * by the user ID the sticky is for, and can include UserHandle.USER_ALL
      * for stickies that are sent to all users.
      */
+    @GuardedBy("this")
     final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =
             new SparseArray<ArrayMap<String, ArrayList<Intent>>>();
 
@@ -1021,6 +1096,7 @@
      * have seen.  Mapping is target uid -> target component -> source uid -> source process name
      * -> association data.
      */
+    @GuardedBy("this")
     final SparseArray<ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>>>
             mAssociations = new SparseArray<>();
     boolean mTrackingAssociations;
@@ -1062,7 +1138,7 @@
     /**
      * Information about component usage
      */
-    UsageStatsManagerInternal mUsageStatsService;
+    volatile UsageStatsManagerInternal mUsageStatsService;
 
     /**
      * Access to DeviceIdleController service.
@@ -1072,29 +1148,32 @@
     /**
      * Power-save whitelisted app-ids (not including except-idle-whitelisted ones).
      */
-    int[] mDeviceIdleWhitelist = new int[0];
+    @CompositeRWLock({"this", "mProcLock"})
+    int[] mDeviceIdleAllowlist = new int[0];
 
     /**
      * Power-save whitelisted app-ids (including except-idle-whitelisted ones).
      */
-    int[] mDeviceIdleExceptIdleWhitelist = new int[0];
+    @CompositeRWLock({"this", "mProcLock"})
+    int[] mDeviceIdleExceptIdleAllowlist = new int[0];
 
     /**
      * Set of app ids that are temporarily allowed to escape bg check due to high-pri message
      */
-    int[] mDeviceIdleTempWhitelist = new int[0];
+    @CompositeRWLock({"this", "mProcLock"})
+    int[] mDeviceIdleTempAllowlist = new int[0];
 
-    static final class PendingTempWhitelist {
+    static final class PendingTempAllowlist {
         final int targetUid;
         final long duration;
         final String tag;
         final int type;
 
-        PendingTempWhitelist(int _targetUid, long _duration, String _tag, int _type) {
-            targetUid = _targetUid;
-            duration = _duration;
-            tag = _tag;
-            type = _type;
+        PendingTempAllowlist(int targetUid, long duration, String tag, int type) {
+            this.targetUid = targetUid;
+            this.duration = duration;
+            this.tag = tag;
+            this.type = type;
         }
 
         void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -1107,11 +1186,13 @@
         }
     }
 
-    final PendingTempWhitelists mPendingTempWhitelist = new PendingTempWhitelists(this);
+    @CompositeRWLock({"this", "mProcLock"})
+    final PendingTempAllowlists mPendingTempAllowlist = new PendingTempAllowlists(this);
 
     /**
      * The temp-allowlist that is allowed to start FGS from background.
      */
+    @CompositeRWLock({"this", "mProcLock"})
     final FgsStartTempAllowList mFgsStartTempAllowList = new FgsStartTempAllowList();
 
     /**
@@ -1127,11 +1208,6 @@
     ArrayMap<String, IBinder> mAppBindArgs;
     ArrayMap<String, IBinder> mIsolatedAppBindArgs;
 
-    /**
-     * Temporary to avoid allocations.  Protected by main lock.
-     */
-    final StringBuilder mStringBuilder = new StringBuilder(256);
-
     volatile boolean mProcessesReady = false;
     volatile boolean mSystemReady = false;
     volatile boolean mOnBattery = false;
@@ -1152,6 +1228,7 @@
     /**
      * Last time (in uptime) at which we checked for power usage.
      */
+    @GuardedBy("mProcLock")
     long mLastPowerCheckUptime;
 
     /**
@@ -1162,40 +1239,68 @@
     /**
      * State of external calls telling us if the device is awake or asleep.
      */
-    int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+    AtomicInteger mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
     /**
      * The uptime of the last time we performed idle maintenance.
      */
+    @GuardedBy("mProcLock")
     long mLastIdleTime = SystemClock.uptimeMillis();
 
     /**
      * For reporting to battery stats the current top application.
+     *
+     * <p>It has its own lock to avoid from the need of double locking if using the global
+     * ActivityManagerService lock and proc lock to guard it.</p>
      */
+    @GuardedBy("mCurResumedAppLock")
     private String mCurResumedPackage = null;
+
+    @GuardedBy("mCurResumedAppLock")
     private int mCurResumedUid = -1;
 
     /**
+     * Dedicated lock for {@link #mCurResumedPackage} and {@link #mCurResumedUid}.
+     */
+    private final Object mCurResumedAppLock = new Object();
+
+    /**
      * For reporting to battery stats the apps currently running foreground
      * service.  The ProcessMap is package/uid tuples; each of these contain
      * an array of the currently foreground processes.
      */
+    @GuardedBy("this")
     final ProcessMap<ArrayList<ProcessRecord>> mForegroundPackages
             = new ProcessMap<ArrayList<ProcessRecord>>();
 
     /**
      * Set if the systemServer made a call to enterSafeMode.
      */
+    @GuardedBy("this")
     boolean mSafeMode;
 
-    String mDebugApp = null;
-    boolean mWaitForDebugger = false;
-    boolean mDebugTransient = false;
-    String mOrigDebugApp = null;
-    boolean mOrigWaitForDebugger = false;
+    @GuardedBy("this")
+    private String mDebugApp = null;
+
+    @GuardedBy("this")
+    private boolean mWaitForDebugger = false;
+
+    @GuardedBy("this")
+    private boolean mDebugTransient = false;
+
+    @GuardedBy("this")
+    private String mOrigDebugApp = null;
+
+    @GuardedBy("this")
+    private boolean mOrigWaitForDebugger = false;
+
+    @GuardedBy("this")
     boolean mAlwaysFinishActivities = false;
 
-    String mTrackAllocationApp = null;
+    @GuardedBy("mProcLock")
+    private String mTrackAllocationApp = null;
+
+    @GuardedBy("this")
     String mNativeDebuggingApp = null;
 
     final Injector mInjector;
@@ -1213,16 +1318,17 @@
         int foregroundServiceTypes;
     }
 
-    // TODO: Move below 4 members and code to ProcessList
-    final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>();
-    ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
-
-    final ArrayList<ProcessChangeItem> mPendingProcessChanges = new ArrayList<>();
-    final ArrayList<ProcessChangeItem> mAvailProcessChanges = new ArrayList<>();
-
+    @GuardedBy("mOomAdjObserverLock")
     OomAdjObserver mCurOomAdjObserver;
+
+    @GuardedBy("mOomAdjObserverLock")
     int mCurOomAdjUid;
 
+    /**
+     * Dedicated lock for {@link #mCurOomAdjObserver} and {@link #mCurOomAdjUid}.
+     */
+    final Object mOomAdjObserverLock = new Object();
+
     interface OomAdjObserver {
         void onOomAdjMessage(String msg);
     }
@@ -1301,7 +1407,7 @@
     static final int IDLE_UIDS_MSG = 58;
     static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
     static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
-    static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
+    static final int PUSH_TEMP_ALLOWLIST_UI_MSG = 68;
     static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
     static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
     static final int KILL_APP_ZYGOTE_MSG = 71;
@@ -1312,13 +1418,11 @@
 
     static final String SERVICE_RECORD_KEY = "servicerecord";
 
-    long mLastMemUsageReportTime = 0;
-
     /**
      * Flag whether the current user is a "monkey", i.e. whether
      * the UI is driven by a UI automation tool.
      */
-    private boolean mUserIsMonkey;
+    private volatile boolean mUserIsMonkey;
 
     @VisibleForTesting
     public final ServiceThread mHandlerThread;
@@ -1341,17 +1445,19 @@
     /**
      * Whether to force background check on all apps (for battery saver) or not.
      */
-    boolean mForceBackgroundCheck;
+    @CompositeRWLock({"this", "mProcLock"})
+    private boolean mForceBackgroundCheck;
 
     private static String sTheRealBuildSerial = Build.UNKNOWN;
 
+    @GuardedBy("mProcLock")
     private ParcelFileDescriptor[] mLifeMonitorFds;
 
     static final HostingRecord sNullHostingRecord = new HostingRecord(null);
     /**
      * Used to notify activity lifecycle events.
      */
-    @Nullable ContentCaptureManagerInternal mContentCaptureService;
+    @Nullable volatile ContentCaptureManagerInternal mContentCaptureService;
 
     /*
      * The default duration for the binder heavy hitter auto sampler
@@ -1366,6 +1472,7 @@
     /**
      * The last time when the binder heavy hitter auto sampler started.
      */
+    @GuardedBy("mProcLock")
     private long mLastBinderHeavyHitterAutoSamplerStart = 0L;
 
     final AppProfiler mAppProfiler;
@@ -1403,19 +1510,19 @@
                 } break;
                 case SHOW_STRICT_MODE_VIOLATION_UI_MSG: {
                     HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
-                    synchronized (ActivityManagerService.this) {
+                    synchronized (mProcLock) {
                         ProcessRecord proc = (ProcessRecord) data.get("app");
                         if (proc == null) {
                             Slog.e(TAG, "App not found when showing strict mode dialog.");
                             break;
                         }
-                        if (proc.getDialogController().hasViolationDialogs()) {
+                        if (proc.mErrorState.getDialogController().hasViolationDialogs()) {
                             Slog.e(TAG, "App already has strict mode dialog: " + proc);
                             return;
                         }
                         AppErrorResult res = (AppErrorResult) data.get("result");
                         if (mAtmInternal.showStrictModeViolationDialog()) {
-                            proc.getDialogController().showViolationDialogs(res);
+                            proc.mErrorState.getDialogController().showViolationDialogs(res);
                         } else {
                             // The device is asleep, so just pretend that the user
                             // saw a crash dialog and hit "force quit".
@@ -1425,20 +1532,20 @@
                     ensureBootCompleted();
                 } break;
                 case WAIT_FOR_DEBUGGER_UI_MSG: {
-                    synchronized (ActivityManagerService.this) {
+                    synchronized (mProcLock) {
                         ProcessRecord app = (ProcessRecord) msg.obj;
                         if (msg.arg1 != 0) {
-                            if (!app.waitedForDebugger) {
-                                app.getDialogController().showDebugWaitingDialogs();
-                                app.waitedForDebugger = true;
+                            if (!app.hasWaitedForDebugger()) {
+                                app.mErrorState.getDialogController().showDebugWaitingDialogs();
+                                app.setWaitedForDebugger(true);
                             }
                         } else {
-                            app.getDialogController().clearWaitingDialog();
+                            app.mErrorState.getDialogController().clearWaitingDialog();
                         }
                     }
                 } break;
                 case DISPATCH_PROCESSES_CHANGED_UI_MSG: {
-                    dispatchProcessesChanged();
+                    mProcessList.dispatchProcessesChanged();
                     break;
                 }
                 case DISPATCH_PROCESS_DIED_UI_MSG: {
@@ -1447,14 +1554,14 @@
                     }
                     final int pid = msg.arg1;
                     final int uid = msg.arg2;
-                    dispatchProcessDied(pid, uid);
+                    mProcessList.dispatchProcessDied(pid, uid);
                     break;
                 }
                 case DISPATCH_OOM_ADJ_OBSERVER_MSG: {
                     dispatchOomAdjObserver((String) msg.obj);
                 } break;
-                case PUSH_TEMP_WHITELIST_UI_MSG: {
-                    pushTempWhitelist();
+                case PUSH_TEMP_ALLOWLIST_UI_MSG: {
+                    pushTempAllowlist();
                 } break;
             }
         }
@@ -1470,49 +1577,50 @@
             switch (msg.what) {
             case GC_BACKGROUND_PROCESSES_MSG: {
                 synchronized (ActivityManagerService.this) {
-                    performAppGcsIfAppropriateLocked();
+                    mAppProfiler.performAppGcsIfAppropriateLocked();
                 }
             } break;
             case SERVICE_TIMEOUT_MSG: {
-                mServices.serviceTimeout((ProcessRecord)msg.obj);
+                mServices.serviceTimeout((ProcessRecord) msg.obj);
             } break;
             case SERVICE_FOREGROUND_TIMEOUT_MSG: {
-                mServices.serviceForegroundTimeout((ServiceRecord)msg.obj);
+                mServices.serviceForegroundTimeout((ServiceRecord) msg.obj);
             } break;
             case SERVICE_FOREGROUND_CRASH_MSG: {
-                mServices.serviceForegroundCrash(
-                    (ProcessRecord) msg.obj, msg.getData().getCharSequence(SERVICE_RECORD_KEY));
+                mServices.serviceForegroundCrash((ProcessRecord) msg.obj,
+                        msg.getData().getCharSequence(SERVICE_RECORD_KEY));
             } break;
             case UPDATE_TIME_ZONE: {
-                synchronized (ActivityManagerService.this) {
-                    for (int i = mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
-                        ProcessRecord r = mProcessList.mLruProcesses.get(i);
-                        if (r.thread != null) {
+                synchronized (mProcLock) {
+                    mProcessList.forEachLruProcessesLOSP(false, app -> {
+                        final IApplicationThread thread = app.getThread();
+                        if (thread != null) {
                             try {
-                                r.thread.updateTimeZone();
+                                thread.updateTimeZone();
                             } catch (RemoteException ex) {
-                                Slog.w(TAG, "Failed to update time zone for: " + r.info.processName);
+                                Slog.w(TAG, "Failed to update time zone for: "
+                                        + app.info.processName);
                             }
-                        }
+                            }
+                        });
                     }
-                }
             } break;
             case CLEAR_DNS_CACHE_MSG: {
-                synchronized (ActivityManagerService.this) {
-                    mProcessList.clearAllDnsCacheLocked();
+                synchronized (mProcLock) {
+                    mProcessList.clearAllDnsCacheLOSP();
                 }
             } break;
             case UPDATE_HTTP_PROXY_MSG: {
                 mProcessList.setAllHttpProxy();
             } break;
             case PROC_START_TIMEOUT_MSG: {
-                ProcessRecord app = (ProcessRecord)msg.obj;
+                ProcessRecord app = (ProcessRecord) msg.obj;
                 synchronized (ActivityManagerService.this) {
                     processStartTimedOutLocked(app);
                 }
             } break;
             case CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG: {
-                ProcessRecord app = (ProcessRecord)msg.obj;
+                ProcessRecord app = (ProcessRecord) msg.obj;
                 synchronized (ActivityManagerService.this) {
                     mCpHelper.processContentProviderPublishTimedOutLocked(app);
                 }
@@ -1521,13 +1629,14 @@
                 synchronized (ActivityManagerService.this) {
                     final int appId = msg.arg1;
                     final int userId = msg.arg2;
-                    Bundle bundle = (Bundle)msg.obj;
+                    Bundle bundle = (Bundle) msg.obj;
                     String pkg = bundle.getString("pkg");
                     String reason = bundle.getString("reason");
                     forceStopPackageLocked(pkg, appId, false, false, true, false,
                             false, userId, reason);
                 }
             } break;
+
                 case KILL_APP_ZYGOTE_MSG: {
                     synchronized (ActivityManagerService.this) {
                         final AppZygote appZygote = (AppZygote) msg.obj;
@@ -1541,10 +1650,10 @@
                 sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
             } break;
             case REPORT_MEM_USAGE_MSG: {
-                final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>)msg.obj;
+                final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>) msg.obj;
                 Thread thread = new Thread() {
                     @Override public void run() {
-                        reportMemUsage(memInfos);
+                        mAppProfiler.reportMemUsage(memInfos);
                     }
                 };
                 thread.start();
@@ -1553,8 +1662,8 @@
             case UPDATE_TIME_PREFERENCE_MSG: {
                 // The user's time format preference might have changed.
                 // For convenience we re-use the Intent extra values.
-                synchronized (ActivityManagerService.this) {
-                    mProcessList.updateAllTimePrefsLocked(msg.arg1);
+                synchronized (mProcLock) {
+                    mProcessList.updateAllTimePrefsLOSP(msg.arg1);
                 }
                 break;
             }
@@ -1562,19 +1671,21 @@
                 final int uid = msg.arg1;
                 final byte[] firstPacket = (byte[]) msg.obj;
 
-                synchronized (mPidsSelfLocked) {
-                    for (int i = 0; i < mPidsSelfLocked.size(); i++) {
-                        final ProcessRecord p = mPidsSelfLocked.valueAt(i);
-                        if (p.uid == uid && p.thread != null) {
-                            try {
-                                p.thread.notifyCleartextNetwork(firstPacket);
-                            } catch (RemoteException ignored) {
+                synchronized (mProcLock) {
+                    synchronized (mPidsSelfLocked) {
+                        for (int i = 0; i < mPidsSelfLocked.size(); i++) {
+                            final ProcessRecord p = mPidsSelfLocked.valueAt(i);
+                            final IApplicationThread thread = p.getThread();
+                            if (p.uid == uid && thread != null) {
+                                try {
+                                    thread.notifyCleartextNetwork(firstPacket);
+                                } catch (RemoteException ignored) {
+                                }
                             }
                         }
                     }
                 }
-                break;
-            }
+            } break;
             case POST_DUMP_HEAP_NOTIFICATION_MSG: {
                 mAppProfiler.handlePostDumpHeapNotification();
             } break;
@@ -1596,8 +1707,8 @@
                 idleUids();
             } break;
             case HANDLE_TRUST_STORAGE_UPDATE_MSG: {
-                synchronized (ActivityManagerService.this) {
-                    mProcessList.handleAllTrustStorageUpdateLocked();
+                synchronized (mProcLock) {
+                    mProcessList.handleAllTrustStorageUpdateLOSP();
                 }
             } break;
                 case BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG: {
@@ -1637,12 +1748,12 @@
                         0,
                         new HostingRecord("system"));
                 app.setPersistent(true);
-                app.pid = MY_PID;
+                app.mPid = MY_PID;
                 app.getWindowProcessController().setPid(MY_PID);
-                app.maxAdj = ProcessList.SYSTEM_ADJ;
+                app.mState.setMaxAdj(ProcessList.SYSTEM_ADJ);
                 app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
                 addPidLocked(app);
-                mProcessList.updateLruProcessLocked(app, false, null);
+                updateLruProcessLocked(app, false, null);
                 updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
             }
         } catch (PackageManager.NameNotFoundException e) {
@@ -1680,7 +1791,10 @@
         }
     }
 
-    public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) {
+    /**
+     * @param usageStatsManager shouldn't be null
+     */
+    public void setUsageStatsManager(@NonNull UsageStatsManagerInternal usageStatsManager) {
         mUsageStatsService = usageStatsManager;
         mActivityTaskManager.setUsageStatsManager(usageStatsManager);
     }
@@ -2034,6 +2148,9 @@
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
         mInternal = new LocalService();
         mPendingStartActivityUids = new PendingStartActivityUids(mContext);
+        mUseFifoUiScheduling = false;
+        mEnableOffloadQueue = false;
+        mFgBroadcastQueue = mBgBroadcastQueue = mOffloadBroadcastQueue = null;
     }
 
     // Note: This method is invoked on the main thread but may need to attach various
@@ -2128,9 +2245,7 @@
         mPendingIntentController = new PendingIntentController(
                 mHandlerThread.getLooper(), mUserController, mConstants);
 
-        if (SystemProperties.getInt("sys.use_fifo_ui", 0) != 0) {
-            mUseFifoUiScheduling = true;
-        }
+        mUseFifoUiScheduling = SystemProperties.getInt("sys.use_fifo_ui", 0) != 0;
 
         mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
@@ -2244,7 +2359,7 @@
             ArrayMap<String, ArraySet<String>> allowedAssociations =
                     SystemConfig.getInstance().getAllowedAssociations();
             mAllowedAssociations = new ArrayMap<>(allowedAssociations.size());
-            PackageManagerInternal pm = getPackageManagerInternalLocked();
+            PackageManagerInternal pm = getPackageManagerInternal();
             for (int i = 0; i < allowedAssociations.size(); i++) {
                 final String pkg = allowedAssociations.keyAt(i);
                 final ArraySet<String> asc = allowedAssociations.valueAt(i);
@@ -2280,16 +2395,18 @@
         if (code == SYSPROPS_TRANSACTION) {
             // We need to tell all apps about the system property change.
             ArrayList<IBinder> procs = new ArrayList<IBinder>();
-            synchronized (this) {
-                final int NP = mProcessList.mProcessNames.getMap().size();
-                for (int ip = 0; ip < NP; ip++) {
-                    SparseArray<ProcessRecord> apps =
-                            mProcessList.mProcessNames.getMap().valueAt(ip);
-                    final int NA = apps.size();
-                    for (int ia = 0; ia < NA; ia++) {
+            synchronized (mProcLock) {
+                final ArrayMap<String, SparseArray<ProcessRecord>> pmap =
+                        mProcessList.getProcessNamesLOSP().getMap();
+                final int numOfNames = pmap.size();
+                for (int ip = 0; ip < numOfNames; ip++) {
+                    SparseArray<ProcessRecord> apps = pmap.valueAt(ip);
+                    final int numOfApps = apps.size();
+                    for (int ia = 0; ia < numOfApps; ia++) {
                         ProcessRecord app = apps.valueAt(ia);
-                        if (app.thread != null) {
-                            procs.add(app.thread.asBinder());
+                        final IApplicationThread thread = app.getThread();
+                        if (thread != null) {
+                            procs.add(thread.asBinder());
                         }
                     }
                 }
@@ -2323,8 +2440,8 @@
         }
     }
 
-    void updateCpuStatsLocked() {
-        mAppProfiler.updateCpuStatsLocked();
+    void updateCpuStats() {
+        mAppProfiler.updateCpuStats();
     }
 
     void updateCpuStatsNow() {
@@ -2341,10 +2458,8 @@
         // When plugging in, update the CPU stats first before changing
         // the plug state.
         updateCpuStatsNow();
-        synchronized (this) {
-            synchronized(mPidsSelfLocked) {
-                mOnBattery = DEBUG_POWER ? true : onBattery;
-            }
+        synchronized (mProcLock) {
+            mOnBattery = DEBUG_POWER ? true : onBattery;
             mOomAdjProfiler.batteryPowerChanged(onBattery);
         }
     }
@@ -2439,27 +2554,29 @@
         mActivityTaskManager.unregisterTaskStackListener(listener);
     }
 
+    @GuardedBy("this")
     final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
             ProcessRecord client) {
         mProcessList.updateLruProcessLocked(app, activityChange, client);
     }
 
+    @GuardedBy("this")
     final void removeLruProcessLocked(ProcessRecord app) {
         mProcessList.removeLruProcessLocked(app);
     }
 
+    @GuardedBy("this")
     final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) {
         return mProcessList.getProcessRecordLocked(processName, uid, keepIfLarge);
     }
 
-    final ProcessMap<ProcessRecord> getProcessNames() {
-        return mProcessList.mProcessNames;
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    final ProcessMap<ProcessRecord> getProcessNamesLOSP() {
+        return mProcessList.getProcessNamesLOSP();
     }
 
     void notifyPackageUse(String packageName, int reason) {
-        synchronized(this) {
-            getPackageManagerInternalLocked().notifyPackageUse(packageName, reason);
-        }
+        getPackageManagerInternal().notifyPackageUse(packageName, reason);
     }
 
     boolean startIsolatedProcess(String entryPoint, String[] entryPointArgs,
@@ -2538,10 +2655,11 @@
         if (mUsageStatsService != null) {
             mUsageStatsService.reportEvent(activity, userId, event, appToken.hashCode(), taskRoot);
         }
-        if (mContentCaptureService != null && (event == Event.ACTIVITY_PAUSED
+        ContentCaptureManagerInternal contentCaptureService = mContentCaptureService;
+        if (contentCaptureService != null && (event == Event.ACTIVITY_PAUSED
                 || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED
                 || event == Event.ACTIVITY_DESTROYED)) {
-            mContentCaptureService.notifyActivityEvent(userId, activity, event);
+            contentCaptureService.notifyActivityEvent(userId, activity, event);
         }
     }
 
@@ -2611,19 +2729,18 @@
                     "getPackageProcessState");
         }
 
-        int procState = PROCESS_STATE_NONEXISTENT;
-        synchronized (this) {
-            for (int i=mProcessList.mLruProcesses.size()-1; i>=0; i--) {
-                final ProcessRecord proc = mProcessList.mLruProcesses.get(i);
-                if (procState > proc.setProcState) {
-                    if (proc.pkgList.containsKey(packageName) ||
-                            (proc.pkgDeps != null && proc.pkgDeps.contains(packageName))) {
-                        procState = proc.setProcState;
+        final int[] procState = {PROCESS_STATE_NONEXISTENT};
+        synchronized (mProcLock) {
+            mProcessList.forEachLruProcessesLOSP(false, proc -> {
+                if (procState[0] > proc.mState.getSetProcState()) {
+                    if (proc.getPkgList().containsKey(packageName) || (proc.getPkgDeps() != null
+                                && proc.getPkgDeps().contains(packageName))) {
+                        procState[0] = proc.mState.getSetProcState();
                     }
                 }
-            }
+            });
         }
-        return procState;
+        return procState[0];
     }
 
     @Override
@@ -2633,136 +2750,34 @@
             throw new SecurityException("Only shell can call it");
         }
         synchronized (this) {
-            final ProcessRecord app = findProcessLocked(process, userId, "setProcessMemoryTrimLevel");
+            final ProcessRecord app = findProcessLOSP(process, userId, "setProcessMemoryTrimLevel");
             if (app == null) {
                 throw new IllegalArgumentException("Unknown process: " + process);
             }
-            if (app.thread == null) {
+            final IApplicationThread thread = app.getThread();
+            if (thread == null) {
                 throw new IllegalArgumentException("Process has no app thread");
             }
-            if (app.trimMemoryLevel >= level) {
+            if (app.mProfile.getTrimMemoryLevel() >= level) {
                 throw new IllegalArgumentException(
                         "Unable to set a higher trim level than current level");
             }
             if (!(level < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN ||
-                    app.getCurProcState() > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)) {
+                    app.mState.getCurProcState() > PROCESS_STATE_IMPORTANT_FOREGROUND)) {
                 throw new IllegalArgumentException("Unable to set a background trim level "
                     + "on a foreground process");
             }
-            app.thread.scheduleTrimMemory(level);
-            app.trimMemoryLevel = level;
+            thread.scheduleTrimMemory(level);
+            synchronized (mProcLock) {
+                app.mProfile.setTrimMemoryLevel(level);
+            }
             return true;
         }
     }
 
-    private void dispatchProcessesChanged() {
-        int N;
-        synchronized (this) {
-            N = mPendingProcessChanges.size();
-            if (mActiveProcessChanges.length < N) {
-                mActiveProcessChanges = new ProcessChangeItem[N];
-            }
-            mPendingProcessChanges.toArray(mActiveProcessChanges);
-            mPendingProcessChanges.clear();
-            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                    "*** Delivering " + N + " process changes");
-        }
-
-        int i = mProcessObservers.beginBroadcast();
-        while (i > 0) {
-            i--;
-            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
-            if (observer != null) {
-                try {
-                    for (int j=0; j<N; j++) {
-                        ProcessChangeItem item = mActiveProcessChanges[j];
-                        if ((item.changes&ProcessChangeItem.CHANGE_ACTIVITIES) != 0) {
-                            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                                    "ACTIVITIES CHANGED pid=" + item.pid + " uid="
-                                    + item.uid + ": " + item.foregroundActivities);
-                            observer.onForegroundActivitiesChanged(item.pid, item.uid,
-                                    item.foregroundActivities);
-                        }
-                        if ((item.changes & ProcessChangeItem.CHANGE_FOREGROUND_SERVICES) != 0) {
-                            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                                    "FOREGROUND SERVICES CHANGED pid=" + item.pid + " uid="
-                                            + item.uid + ": " + item.foregroundServiceTypes);
-                            observer.onForegroundServicesChanged(item.pid, item.uid,
-                                    item.foregroundServiceTypes);
-                        }
-                    }
-                } catch (RemoteException e) {
-                }
-            }
-        }
-        mProcessObservers.finishBroadcast();
-
-        synchronized (this) {
-            for (int j=0; j<N; j++) {
-                mAvailProcessChanges.add(mActiveProcessChanges[j]);
-            }
-        }
-    }
-
-    @GuardedBy("this")
-    ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) {
-        int i = mPendingProcessChanges.size()-1;
-        ActivityManagerService.ProcessChangeItem item = null;
-        while (i >= 0) {
-            item = mPendingProcessChanges.get(i);
-            if (item.pid == pid) {
-                if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                        "Re-using existing item: " + item);
-                break;
-            }
-            i--;
-        }
-
-        if (i < 0) {
-            // No existing item in pending changes; need a new one.
-            final int NA = mAvailProcessChanges.size();
-            if (NA > 0) {
-                item = mAvailProcessChanges.remove(NA-1);
-                if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                        "Retrieving available item: " + item);
-            } else {
-                item = new ActivityManagerService.ProcessChangeItem();
-                if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                        "Allocating new item: " + item);
-            }
-            item.changes = 0;
-            item.pid = pid;
-            item.uid = uid;
-            if (mPendingProcessChanges.size() == 0) {
-                if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
-                        "*** Enqueueing dispatch processes changed!");
-                mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
-                        .sendToTarget();
-            }
-            mPendingProcessChanges.add(item);
-        }
-
-        return item;
-    }
-
-    private void dispatchProcessDied(int pid, int uid) {
-        int i = mProcessObservers.beginBroadcast();
-        while (i > 0) {
-            i--;
-            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
-            if (observer != null) {
-                try {
-                    observer.onProcessDied(pid, uid);
-                } catch (RemoteException e) {
-                }
-            }
-        }
-        mProcessObservers.finishBroadcast();
-    }
-
     void dispatchOomAdjObserver(String msg) {
         OomAdjObserver observer;
-        synchronized (this) {
+        synchronized (mOomAdjObserverLock) {
             observer = mCurOomAdjObserver;
         }
 
@@ -2772,32 +2787,26 @@
     }
 
     void setOomAdjObserver(int uid, OomAdjObserver observer) {
-        synchronized (this) {
+        synchronized (mOomAdjObserverLock) {
             mCurOomAdjUid = uid;
             mCurOomAdjObserver = observer;
         }
     }
 
     void clearOomAdjObserver() {
-        synchronized (this) {
+        synchronized (mOomAdjObserverLock) {
             mCurOomAdjUid = -1;
             mCurOomAdjObserver = null;
         }
     }
 
-    void reportOomAdjMessageLocked(String tag, String msg) {
-        Slog.d(tag, msg);
-        if (mCurOomAdjObserver != null) {
-            mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget();
-        }
-    }
-
     void reportUidInfoMessageLocked(String tag, String msg, int uid) {
         Slog.i(TAG, msg);
-        if (mCurOomAdjObserver != null && uid == mCurOomAdjUid) {
-            mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget();
+        synchronized (mOomAdjObserverLock) {
+            if (mCurOomAdjObserver != null && uid == mCurOomAdjUid) {
+                mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget();
+            }
         }
-
     }
 
     /**
@@ -2918,10 +2927,9 @@
      * to the process.
      */
     @GuardedBy("this")
-    final void handleAppDiedLocked(ProcessRecord app,
+    final void handleAppDiedLocked(ProcessRecord app, int pid,
             boolean restarting, boolean allowRestart) {
-        int pid = app.pid;
-        boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1,
+        boolean kept = cleanUpApplicationRecordLocked(app, pid, restarting, allowRestart, -1,
                 false /*replacingPid*/);
         if (!kept && !restarting) {
             removeLruProcessLocked(app);
@@ -2941,24 +2949,26 @@
         });
     }
 
-    ProcessRecord getRecordForAppLocked(IApplicationThread thread) {
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    ProcessRecord getRecordForAppLOSP(IApplicationThread thread) {
         if (thread == null) {
             return null;
         }
 
-        ProcessRecord record = mProcessList.getLRURecordForAppLocked(thread);
+        ProcessRecord record = mProcessList.getLRURecordForAppLOSP(thread);
         if (record != null) return record;
 
         // Validation: if it isn't in the LRU list, it shouldn't exist, but let's
         // double-check that.
         final IBinder threadBinder = thread.asBinder();
         final ArrayMap<String, SparseArray<ProcessRecord>> pmap =
-                mProcessList.mProcessNames.getMap();
+                mProcessList.getProcessNamesLOSP().getMap();
         for (int i = pmap.size()-1; i >= 0; i--) {
             final SparseArray<ProcessRecord> procs = pmap.valueAt(i);
             for (int j = procs.size()-1; j >= 0; j--) {
                 final ProcessRecord proc = procs.valueAt(j);
-                if (proc.thread != null && proc.thread.asBinder() == threadBinder) {
+                final IApplicationThread procThread = proc.getThread();
+                if (procThread != null && procThread.asBinder() == threadBinder) {
                     Slog.wtf(TAG, "getRecordForApp: exists in name list but not in LRU list: "
                             + proc);
                     return proc;
@@ -2969,60 +2979,9 @@
         return null;
     }
 
-    final void doLowMemReportIfNeededLocked(ProcessRecord dyingProc) {
-        // If there are no longer any background processes running,
-        // and the app that died was not running instrumentation,
-        // then tell everyone we are now low on memory.
-        if (!mProcessList.haveBackgroundProcessLocked()) {
-            boolean doReport = Build.IS_DEBUGGABLE;
-            if (doReport) {
-                long now = SystemClock.uptimeMillis();
-                if (now < (mLastMemUsageReportTime+5*60*1000)) {
-                    doReport = false;
-                } else {
-                    mLastMemUsageReportTime = now;
-                }
-            }
-            final ArrayList<ProcessMemInfo> memInfos
-                    = doReport ? new ArrayList<ProcessMemInfo>(mProcessList.getLruSizeLocked())
-                    : null;
-            EventLogTags.writeAmLowMemory(mProcessList.getLruSizeLocked());
-            long now = SystemClock.uptimeMillis();
-            for (int i = mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
-                ProcessRecord rec = mProcessList.mLruProcesses.get(i);
-                if (rec == dyingProc || rec.thread == null) {
-                    continue;
-                }
-                if (doReport) {
-                    memInfos.add(new ProcessMemInfo(rec.processName, rec.pid, rec.setAdj,
-                            rec.setProcState, rec.adjType, rec.makeAdjReason()));
-                }
-                if ((rec.lastLowMemory+mConstants.GC_MIN_INTERVAL) <= now) {
-                    // The low memory report is overriding any current
-                    // state for a GC request.  Make sure to do
-                    // heavy/important/visible/foreground processes first.
-                    if (rec.setAdj <= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
-                        rec.lastRequestedGc = 0;
-                    } else {
-                        rec.lastRequestedGc = rec.lastLowMemory;
-                    }
-                    rec.reportLowMemory = true;
-                    rec.lastLowMemory = now;
-                    mProcessesToGc.remove(rec);
-                    addProcessToGcListLocked(rec);
-                }
-            }
-            if (doReport) {
-                Message msg = mHandler.obtainMessage(REPORT_MEM_USAGE_MSG, memInfos);
-                mHandler.sendMessage(msg);
-            }
-            scheduleAppGcsLocked();
-        }
-    }
-
     @GuardedBy("this")
     final void appDiedLocked(ProcessRecord app, String reason) {
-        appDiedLocked(app, app.pid, app.thread, false, reason);
+        appDiedLocked(app, app.getPid(), app.getThread(), false, reason);
     }
 
     @GuardedBy("this")
@@ -3039,26 +2998,31 @@
 
         mBatteryStatsService.noteProcessDied(app.info.uid, pid);
 
-        if (!app.killed) {
+        if (!app.isKilled()) {
             if (!fromBinderDied) {
                 killProcessQuiet(pid);
                 mProcessList.noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
                         ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
             }
             ProcessList.killProcessGroup(app.uid, pid);
-            app.killed = true;
+            synchronized (mProcLock) {
+                app.setKilled(true);
+            }
         }
 
         // Clean up already done if the process has been re-started.
-        if (app.pid == pid && app.thread != null &&
-                app.thread.asBinder() == thread.asBinder()) {
+        IApplicationThread appThread;
+        final int setAdj = app.mState.getSetAdj();
+        final int setProcState = app.mState.getSetProcState();
+        if (app.getPid() == pid && (appThread = app.getThread()) != null
+                && appThread.asBinder() == thread.asBinder()) {
             boolean doLowMem = app.getActiveInstrumentation() == null;
             boolean doOomAdj = doLowMem;
-            if (!app.killedByAm) {
+            if (!app.isKilledByAm()) {
                 reportUidInfoMessageLocked(TAG,
                         "Process " + app.processName + " (pid " + pid + ") has died: "
-                                + ProcessList.makeOomAdjString(app.setAdj, true) + " "
-                                + ProcessList.makeProcStateString(app.setProcState), app.info.uid);
+                        + ProcessList.makeOomAdjString(setAdj, true) + " "
+                        + ProcessList.makeProcStateString(setProcState), app.info.uid);
                 mAppProfiler.setAllowLowerMemLevelLocked(true);
             } else {
                 // Note that we always want to do oom adj to update our state with the
@@ -3066,26 +3030,25 @@
                 mAppProfiler.setAllowLowerMemLevelLocked(false);
                 doLowMem = false;
             }
-            EventLogTags.writeAmProcDied(app.userId, app.pid, app.processName, app.setAdj,
-                    app.setProcState);
+            EventLogTags.writeAmProcDied(app.userId, pid, app.processName, setAdj, setProcState);
             if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
                 "Dying app: " + app + ", pid: " + pid + ", thread: " + thread.asBinder());
-            handleAppDiedLocked(app, false, true);
+            handleAppDiedLocked(app, pid, false, true);
 
             if (doOomAdj) {
                 updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
             }
             if (doLowMem) {
-                doLowMemReportIfNeededLocked(app);
+                mAppProfiler.doLowMemReportIfNeededLocked(app);
             }
-        } else if (app.pid != pid) {
+        } else if (app.getPid() != pid) {
             // A new process has already been started.
             reportUidInfoMessageLocked(TAG,
                     "Process " + app.processName + " (pid " + pid
-                            + ") has died and restarted (pid " + app.pid + ").", app.info.uid);
+                            + ") has died and restarted (pid " + app.getPid() + ").", app.info.uid);
 
-            EventLogTags.writeAmProcDied(app.userId, app.pid, app.processName, app.setAdj,
-                    app.setProcState);
+            EventLogTags.writeAmProcDied(app.userId, app.getPid(), app.processName,
+                    setAdj, setProcState);
         } else if (DEBUG_PROCESSES) {
             Slog.d(TAG_PROCESSES, "Received spurious death notification for thread "
                     + thread.asBinder());
@@ -3379,55 +3342,55 @@
         final long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
-            synchronized(this) {
-                // Instant packages are not protected
-                if (getPackageManagerInternalLocked().isPackageDataProtected(
-                        resolvedUserId, packageName)) {
-                    throw new SecurityException(
-                            "Cannot clear data for a protected package: " + packageName);
-                }
+            // Instant packages are not protected
+            if (getPackageManagerInternal().isPackageDataProtected(
+                    resolvedUserId, packageName)) {
+                throw new SecurityException(
+                        "Cannot clear data for a protected package: " + packageName);
+            }
 
-                ApplicationInfo applicationInfo = null;
-                try {
-                    applicationInfo = pm.getApplicationInfo(packageName,
-                            MATCH_UNINSTALLED_PACKAGES, resolvedUserId);
-                } catch (RemoteException e) {
-                    /* ignore */
-                }
-                appInfo = applicationInfo;
+            ApplicationInfo applicationInfo = null;
+            try {
+                applicationInfo = pm.getApplicationInfo(packageName,
+                        MATCH_UNINSTALLED_PACKAGES, resolvedUserId);
+            } catch (RemoteException e) {
+                /* ignore */
+            }
+            appInfo = applicationInfo;
 
-                final boolean clearingOwnUidData = appInfo != null && appInfo.uid == uid;
+            final boolean clearingOwnUidData = appInfo != null && appInfo.uid == uid;
 
-                if (!clearingOwnUidData && checkComponentPermission(permission.CLEAR_APP_USER_DATA,
+            if (!clearingOwnUidData && checkComponentPermission(permission.CLEAR_APP_USER_DATA,
                         pid, uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
-                    throw new SecurityException("PID " + pid + " does not have permission "
-                            + android.Manifest.permission.CLEAR_APP_USER_DATA + " to clear data"
-                            + " of package " + packageName);
-                }
+                throw new SecurityException("PID " + pid + " does not have permission "
+                        + android.Manifest.permission.CLEAR_APP_USER_DATA + " to clear data"
+                        + " of package " + packageName);
+            }
 
-                final boolean hasInstantMetadata = getPackageManagerInternalLocked()
-                        .hasInstantApplicationMetadata(packageName, resolvedUserId);
-                final boolean isUninstalledAppWithoutInstantMetadata =
-                        (appInfo == null && !hasInstantMetadata);
-                isInstantApp = (appInfo != null && appInfo.isInstantApp())
-                        || hasInstantMetadata;
-                final boolean canAccessInstantApps = checkComponentPermission(
-                        permission.ACCESS_INSTANT_APPS, pid, uid, -1, true)
-                        == PackageManager.PERMISSION_GRANTED;
+            final boolean hasInstantMetadata = getPackageManagerInternal()
+                    .hasInstantApplicationMetadata(packageName, resolvedUserId);
+            final boolean isUninstalledAppWithoutInstantMetadata =
+                    (appInfo == null && !hasInstantMetadata);
+            isInstantApp = (appInfo != null && appInfo.isInstantApp())
+                    || hasInstantMetadata;
+            final boolean canAccessInstantApps = checkComponentPermission(
+                    permission.ACCESS_INSTANT_APPS, pid, uid, -1, true)
+                    == PackageManager.PERMISSION_GRANTED;
 
-                if (isUninstalledAppWithoutInstantMetadata || (isInstantApp
+            if (isUninstalledAppWithoutInstantMetadata || (isInstantApp
                         && !canAccessInstantApps)) {
-                    Slog.w(TAG, "Invalid packageName: " + packageName);
-                    if (observer != null) {
-                        try {
-                            observer.onRemoveCompleted(packageName, false);
-                        } catch (RemoteException e) {
-                            Slog.i(TAG, "Observer no longer exists.");
-                        }
+                Slog.w(TAG, "Invalid packageName: " + packageName);
+                if (observer != null) {
+                    try {
+                        observer.onRemoveCompleted(packageName, false);
+                    } catch (RemoteException e) {
+                        Slog.i(TAG, "Observer no longer exists.");
                     }
-                    return false;
                 }
+                return false;
+            }
 
+            synchronized (this) {
                 if (appInfo != null) {
                     forceStopPackageLocked(packageName, appInfo.uid, "clear data");
                     mAtmInternal.removeRecentTasksByPackageName(packageName, resolvedUserId);
@@ -3533,9 +3496,11 @@
                     return;
                 }
                 synchronized (this) {
-                    mProcessList.killPackageProcessesLocked(packageName, appId, targetUserId,
-                            ProcessList.SERVICE_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED,
-                            ApplicationExitInfo.SUBREASON_UNKNOWN, "kill background");
+                    synchronized (mProcLock) {
+                        mProcessList.killPackageProcessesLSP(packageName, appId, targetUserId,
+                                ProcessList.SERVICE_ADJ, ApplicationExitInfo.REASON_USER_REQUESTED,
+                                ApplicationExitInfo.SUBREASON_UNKNOWN, "kill background");
+                    }
                 }
             }
         } finally {
@@ -3560,13 +3525,15 @@
                 // Allow memory level to go down (the flag needs to be set before updating oom adj)
                 // because this method is also used to simulate low memory.
                 mAppProfiler.setAllowLowerMemLevelLocked(true);
-                mProcessList.killPackageProcessesLocked(null /* packageName */, -1 /* appId */,
-                        UserHandle.USER_ALL, ProcessList.CACHED_APP_MIN_ADJ,
-                        ApplicationExitInfo.REASON_USER_REQUESTED,
-                        ApplicationExitInfo.SUBREASON_UNKNOWN,
-                        "kill all background");
+                synchronized (mProcLock) {
+                    mProcessList.killPackageProcessesLSP(null /* packageName */, -1 /* appId */,
+                            UserHandle.USER_ALL, ProcessList.CACHED_APP_MIN_ADJ,
+                            ApplicationExitInfo.REASON_USER_REQUESTED,
+                            ApplicationExitInfo.SUBREASON_UNKNOWN,
+                            "kill all background");
+                }
 
-                doLowMemReportIfNeededLocked(null);
+                mAppProfiler.doLowMemReportIfNeededLocked(null);
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -3595,7 +3562,9 @@
         final long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                mProcessList.killAllBackgroundProcessesExceptLocked(minTargetSdk, maxProcState);
+                synchronized (mProcLock) {
+                    mProcessList.killAllBackgroundProcessesExceptLSP(minTargetSdk, maxProcState);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -3623,7 +3592,7 @@
                 int[] users = userId == UserHandle.USER_ALL
                         ? mUserController.getUsers() : new int[] { userId };
                 for (int user : users) {
-                    if (getPackageManagerInternalLocked().isPackageStateProtected(
+                    if (getPackageManagerInternal().isPackageStateProtected(
                             packageName, user)) {
                         Slog.w(TAG, "Ignoring request to force stop protected package "
                                 + packageName + " u" + user);
@@ -3660,21 +3629,24 @@
 
     @Override
     public void addPackageDependency(String packageName) {
-        synchronized (this) {
-            int callingPid = Binder.getCallingPid();
-            if (callingPid == myPid()) {
-                //  Yeah, um, no.
-                return;
-            }
-            ProcessRecord proc;
-            synchronized (mPidsSelfLocked) {
-                proc = mPidsSelfLocked.get(Binder.getCallingPid());
-            }
-            if (proc != null) {
-                if (proc.pkgDeps == null) {
-                    proc.pkgDeps = new ArraySet<String>(1);
+        int callingPid = Binder.getCallingPid();
+        if (callingPid == myPid()) {
+            //  Yeah, um, no.
+            return;
+        }
+        ProcessRecord proc;
+        synchronized (mPidsSelfLocked) {
+            proc = mPidsSelfLocked.get(Binder.getCallingPid());
+        }
+        if (proc != null) {
+            ArraySet<String> pkgDeps = proc.getPkgDeps();
+            synchronized (this) {
+                synchronized (mProcLock) {
+                    if (pkgDeps == null) {
+                        proc.setPkgDeps(pkgDeps = new ArraySet<String>(1));
+                    }
+                    pkgDeps.add(packageName);
                 }
-                proc.pkgDeps.add(packageName);
             }
         }
     }
@@ -3734,24 +3706,33 @@
         // Check if the caller is actually instrumented and from shell, if it's true, we may lift
         // the throttle of PSS info sampling.
         boolean isCallerInstrumentedFromShell = false;
-        synchronized (mPidsSelfLocked) {
-            ProcessRecord caller = mPidsSelfLocked.get(callingPid);
-            if (caller != null) {
-                final ActiveInstrumentation instr = caller.getActiveInstrumentation();
-                isCallerInstrumentedFromShell = instr != null
-                        && (instr.mSourceUid == SHELL_UID || instr.mSourceUid == ROOT_UID);
+        synchronized (mProcLock) {
+            synchronized (mPidsSelfLocked) {
+                ProcessRecord caller = mPidsSelfLocked.get(callingPid);
+                if (caller != null) {
+                    final ActiveInstrumentation instr = caller.getActiveInstrumentation();
+                    isCallerInstrumentedFromShell = instr != null
+                            && (instr.mSourceUid == SHELL_UID || instr.mSourceUid == ROOT_UID);
+                }
             }
         }
 
-        Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length];
+        final Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length];
         for (int i=pids.length-1; i>=0; i--) {
-            infos[i] = new Debug.MemoryInfo();
+            final Debug.MemoryInfo mi = infos[i] = new Debug.MemoryInfo();
             final ProcessRecord proc;
             final int oomAdj;
-            synchronized (this) {
+            final ProcessProfileRecord profile;
+            synchronized (mAppProfiler.mProfilerLock) {
                 synchronized (mPidsSelfLocked) {
                     proc = mPidsSelfLocked.get(pids[i]);
-                    oomAdj = proc != null ? proc.setAdj : 0;
+                    if (proc != null) {
+                        profile = proc.mProfile;
+                        oomAdj = profile.getSetAdj();
+                    } else {
+                        profile = null;
+                        oomAdj = 0;
+                    }
                 }
             }
             final int targetUid = (proc != null) ? proc.uid : -1;
@@ -3766,43 +3747,43 @@
                     continue; // Not allowed to see other users.
                 }
             }
-            if (proc != null && proc.lastMemInfoTime >= lastNow && proc.lastMemInfo != null
-                    && !isCallerInstrumentedFromShell) {
-                // It hasn't been long enough that we want to take another sample; return
-                // the last one.
-                infos[i].set(proc.lastMemInfo);
-                continue;
+            if (proc != null) {
+                synchronized (mAppProfiler.mProfilerLock) {
+                    if (profile.getLastMemInfoTime() >= lastNow && profile.getLastMemInfo() != null
+                            && !isCallerInstrumentedFromShell) {
+                        // It hasn't been long enough that we want to take another sample; return
+                        // the last one.
+                        mi.set(profile.getLastMemInfo());
+                        continue;
+                    }
+                }
             }
             final long startTime = SystemClock.currentThreadTimeMillis();
             final Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
             Debug.getMemoryInfo(pids[i], memInfo);
-            final long endTime = SystemClock.currentThreadTimeMillis();
-            infos[i].set(memInfo);
+            final long duration = SystemClock.currentThreadTimeMillis() - startTime;
+            mi.set(memInfo);
             if (proc != null) {
-                synchronized (this) {
-                    proc.lastMemInfo = memInfo;
-                    proc.lastMemInfoTime = SystemClock.uptimeMillis();
-                    if (proc.thread != null && proc.setAdj == oomAdj) {
+                synchronized (mAppProfiler.mProfilerLock) {
+                    profile.setLastMemInfo(memInfo);
+                    profile.setLastMemInfoTime(SystemClock.uptimeMillis());
+                    if (profile.getThread() != null && profile.getSetAdj() == oomAdj) {
                         // Record this for posterity if the process has been stable.
-                        synchronized (mProcessStats.mLock) {
-                            proc.baseProcessTracker.addPss(infos[i].getTotalPss(),
-                                    infos[i].getTotalUss(), infos[i].getTotalRss(), false,
-                                    ProcessStats.ADD_PSS_EXTERNAL_SLOW, endTime - startTime,
-                                    proc.pkgList.mPkgList);
-                        }
-                        for (int ipkg = proc.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                            ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
+                        profile.addPss(mi.getTotalPss(),
+                                mi.getTotalUss(), mi.getTotalRss(), false,
+                                ProcessStats.ADD_PSS_EXTERNAL_SLOW, duration);
+                        proc.getPkgList().forEachPackageProcessStats(holder -> {
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
                                     proc.info.uid,
                                     holder.state.getName(),
                                     holder.state.getPackage(),
-                                    infos[i].getTotalPss(),
-                                    infos[i].getTotalUss(),
-                                    infos[i].getTotalRss(),
+                                    mi.getTotalPss(),
+                                    mi.getTotalUss(),
+                                    mi.getTotalRss(),
                                     ProcessStats.ADD_PSS_EXTERNAL_SLOW,
-                                    endTime-startTime,
+                                    duration,
                                     holder.appVersion);
-                        }
+                        });
                     }
                 }
             }
@@ -3823,14 +3804,14 @@
         final boolean allUids = mAtmInternal.isGetTasksAllowed(
                 "getProcessPss", callingPid, callingUid);
 
-        long[] pss = new long[pids.length];
+        final long[] pss = new long[pids.length];
         for (int i=pids.length-1; i>=0; i--) {
             ProcessRecord proc;
             int oomAdj;
-            synchronized (this) {
+            synchronized (mProcLock) {
                 synchronized (mPidsSelfLocked) {
                     proc = mPidsSelfLocked.get(pids[i]);
-                    oomAdj = proc != null ? proc.setAdj : 0;
+                    oomAdj = proc != null ? proc.mState.getSetAdj() : 0;
                 }
             }
             if (!allUids || (!allUsers && UserHandle.getUserId(proc.uid) != userId)) {
@@ -3838,29 +3819,29 @@
                 // just leave it empty.
                 continue;
             }
-            long[] tmpUss = new long[3];
-            long startTime = SystemClock.currentThreadTimeMillis();
-            pss[i] = Debug.getPss(pids[i], tmpUss, null);
-            long endTime = SystemClock.currentThreadTimeMillis();
+            final long[] tmpUss = new long[3];
+            final long startTime = SystemClock.currentThreadTimeMillis();
+            final long pi = pss[i] = Debug.getPss(pids[i], tmpUss, null);
+            final long duration = SystemClock.currentThreadTimeMillis() - startTime;
             if (proc != null) {
-                synchronized (this) {
-                    if (proc.thread != null && proc.setAdj == oomAdj) {
+                final ProcessProfileRecord profile = proc.mProfile;
+                synchronized (mAppProfiler.mProfilerLock) {
+                    if (profile.getThread() != null && profile.getSetAdj() == oomAdj) {
                         // Record this for posterity if the process has been stable.
-                        synchronized (mProcessStats.mLock) {
-                            proc.baseProcessTracker.addPss(pss[i], tmpUss[0], tmpUss[2], false,
-                                    ProcessStats.ADD_PSS_EXTERNAL, endTime - startTime,
-                                    proc.pkgList.mPkgList);
-                        }
-                        for (int ipkg = proc.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                            ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
+                        profile.addPss(pi, tmpUss[0], tmpUss[2], false,
+                                ProcessStats.ADD_PSS_EXTERNAL, duration);
+                        proc.getPkgList().forEachPackageProcessStats(holder -> {
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
                                     proc.info.uid,
                                     holder.state.getName(),
                                     holder.state.getPackage(),
-                                    pss[i], tmpUss[0], tmpUss[2],
-                                    ProcessStats.ADD_PSS_EXTERNAL, endTime-startTime,
+                                    pi,
+                                    tmpUss[0],
+                                    tmpUss[2],
+                                    ProcessStats.ADD_PSS_EXTERNAL,
+                                    duration,
                                     holder.appVersion);
-                        }
+                        });
                     }
                 }
             }
@@ -3879,9 +3860,10 @@
         if (callerUid == SYSTEM_UID) {
             synchronized (this) {
                 ProcessRecord app = getProcessRecordLocked(processName, uid, true);
-                if (app != null && app.thread != null) {
+                IApplicationThread thread;
+                if (app != null && (thread = app.getThread()) != null) {
                     try {
-                        app.thread.scheduleSuicide();
+                        thread.scheduleSuicide();
                     } catch (RemoteException e) {
                         // If the other end already died, then our work here is done.
                     }
@@ -4043,6 +4025,7 @@
             }
         }
 
+        boolean didSomething;
         if (doit) {
             if (packageName != null) {
                 Slog.i(TAG, "Force stopping " + packageName + " appid=" + appId
@@ -4051,23 +4034,25 @@
                 Slog.i(TAG, "Force stopping u" + userId + ": " + reason);
             }
 
-            mAppErrors.resetProcessCrashTimeLocked(packageName == null, appId, userId);
+            mAppErrors.resetProcessCrashTime(packageName == null, appId, userId);
         }
 
-        // Notify first that the package is stopped, so its process won't be restarted unexpectedly
-        // if there is an activity of the package without attached process becomes visible when
-        // killing its other processes with visible activities.
-        boolean didSomething =
-                mAtmInternal.onForceStopPackage(packageName, doit, evenPersistent, userId);
+        synchronized (mProcLock) {
+            // Notify first that the package is stopped, so its process won't be restarted
+            // unexpectedly if there is an activity of the package without attached process
+            // becomes visible when killing its other processes with visible activities.
+            didSomething = mAtmInternal.onForceStopPackage(
+                    packageName, doit, evenPersistent, userId);
 
-        didSomething |= mProcessList.killPackageProcessesLocked(packageName, appId, userId,
-                ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
-                evenPersistent, true /* setRemoved */,
-                packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
-                        : ApplicationExitInfo.REASON_USER_REQUESTED,
-                ApplicationExitInfo.SUBREASON_UNKNOWN,
-                (packageName == null ? ("stop user " + userId) : ("stop " + packageName))
-                + " due to " + reason);
+            didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId,
+                    ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
+                    evenPersistent, true /* setRemoved */,
+                    packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
+                    : ApplicationExitInfo.REASON_USER_REQUESTED,
+                    ApplicationExitInfo.SUBREASON_UNKNOWN,
+                    (packageName == null ? ("stop user " + userId) : ("stop " + packageName))
+                    + " due to " + reason);
+        }
 
         if (mServices.bringDownDisabledPackageServicesLocked(
                 packageName, null /* filterByClasses */, userId, evenPersistent, doit)) {
@@ -4126,26 +4111,29 @@
 
     @GuardedBy("this")
     private final void processStartTimedOutLocked(ProcessRecord app) {
-        final int pid = app.pid;
-        boolean gone = removePidIfNoThread(app);
+        final int pid = app.getPid();
+        boolean gone = removePidIfNoThreadLocked(app);
 
         if (gone) {
             Slog.w(TAG, "Process " + app + " failed to attach");
             EventLogTags.writeAmProcessStartTimeout(app.userId, pid, app.uid, app.processName);
-            mProcessList.removeProcessNameLocked(app.processName, app.uid);
-            mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
-            mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
-            // Take care of any launching providers waiting for this process.
-            mCpHelper.cleanupAppInLaunchingProvidersLocked(app, true);
-            // Take care of any services that are waiting for the process.
-            mServices.processStartTimedOutLocked(app);
-            app.kill("start timeout", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
-            if (app.isolated) {
-                mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
+            synchronized (mProcLock) {
+                mProcessList.removeProcessNameLocked(app.processName, app.uid);
+                mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
+                mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
+                // Take care of any launching providers waiting for this process.
+                mCpHelper.cleanupAppInLaunchingProvidersLocked(app, true);
+                // Take care of any services that are waiting for the process.
+                mServices.processStartTimedOutLocked(app);
+                app.killLocked("start timeout", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+                        true);
+                if (app.isolated) {
+                    mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
+                }
+                removeLruProcessLocked(app);
             }
-            removeLruProcessLocked(app);
             final BackupRecord backupTarget = mBackupTargets.get(app.userId);
-            if (backupTarget != null && backupTarget.app.pid == pid) {
+            if (backupTarget != null && backupTarget.app.getPid() == pid) {
                 Slog.w(TAG, "Unattached app died before backup, skipping");
                 mHandler.post(new Runnable() {
                 @Override
@@ -4183,7 +4171,7 @@
             synchronized (mPidsSelfLocked) {
                 app = mPidsSelfLocked.get(pid);
             }
-            if (app != null && (app.startUid != callingUid || app.startSeq != startSeq)) {
+            if (app != null && (app.getStartUid() != callingUid || app.getStartSeq() != startSeq)) {
                 String processName = null;
                 final ProcessRecord pending = mProcessList.mPendingStarts.get(startSeq);
                 if (pending != null) {
@@ -4193,14 +4181,14 @@
                         + " startSeq:" + startSeq
                         + " pid:" + pid
                         + " belongs to another existing app:" + app.processName
-                        + " startSeq:" + app.startSeq;
+                        + " startSeq:" + app.getStartSeq();
                 Slog.wtf(TAG, msg);
                 // SafetyNet logging for b/131105245.
-                EventLog.writeEvent(0x534e4554, "131105245", app.startUid, msg);
+                EventLog.writeEvent(0x534e4554, "131105245", app.getStartUid(), msg);
                 // If there is already an app occupying that pid that hasn't been cleaned up
-                cleanUpApplicationRecordLocked(app, false, false, -1,
-                            true /*replacingPid*/);
-                removePidLocked(app);
+                cleanUpApplicationRecordLocked(app, pid, false, false, -1,
+                        true /*replacingPid*/);
+                removePidLocked(pid, app);
                 app = null;
             }
         } else {
@@ -4211,10 +4199,10 @@
         // update the internal state.
         if (app == null && startSeq > 0) {
             final ProcessRecord pending = mProcessList.mPendingStarts.get(startSeq);
-            if (pending != null && pending.startUid == callingUid && pending.startSeq == startSeq
-                    && mProcessList.handleProcessStartedLocked(pending, pid, pending
-                            .isUsingWrapper(),
-                            startSeq, true)) {
+            if (pending != null && pending.getStartUid() == callingUid
+                    && pending.getStartSeq() == startSeq
+                    && mProcessList.handleProcessStartedLocked(pending, pid,
+                        pending.isUsingWrapper(), startSeq, true)) {
                 app = pending;
             }
         }
@@ -4240,8 +4228,8 @@
 
         // If this application record is still attached to a previous
         // process, clean it up now.
-        if (app.thread != null) {
-            handleAppDiedLocked(app, true, true);
+        if (app.getThread() != null) {
+            handleAppDiedLocked(app, pid, true, true);
         }
 
         // Tell the process all about itself.
@@ -4254,7 +4242,7 @@
             AppDeathRecipient adr = new AppDeathRecipient(
                     app, pid, thread);
             thread.asBinder().linkToDeath(adr, 0);
-            app.deathRecipient = adr;
+            app.setDeathRecipient(adr);
         } catch (RemoteException e) {
             app.resetPackageList(mProcessStats);
             mProcessList.startProcessLocked(app,
@@ -4263,23 +4251,25 @@
             return false;
         }
 
-        EventLogTags.writeAmProcBound(app.userId, app.pid, app.processName);
+        EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
 
-        app.curAdj = app.setAdj = app.verifiedAdj = ProcessList.INVALID_ADJ;
-        mOomAdjuster.setAttachingSchedGroupLocked(app);
-        app.forcingToImportant = null;
-        updateProcessForegroundLocked(app, false, 0, false);
-        app.hasShownUi = false;
-        app.setDebugging(false);
-        app.setCached(false);
-        app.killedByAm = false;
-        app.killed = false;
-
-
-        // We carefully use the same state that PackageManager uses for
-        // filtering, since we use this flag to decide if we need to install
-        // providers when user is unlocked later
-        app.unlocked = StorageManager.isUserKeyUnlocked(app.userId);
+        synchronized (mProcLock) {
+            app.mState.setCurAdj(ProcessList.INVALID_ADJ);
+            app.mState.setSetAdj(ProcessList.INVALID_ADJ);
+            app.mState.setVerifiedAdj(ProcessList.INVALID_ADJ);
+            mOomAdjuster.setAttachingSchedGroupLSP(app);
+            app.mState.setForcingToImportant(null);
+            updateProcessForegroundLocked(app, false, 0, false);
+            app.mState.setHasShownUi(false);
+            app.mState.setCached(false);
+            app.setDebugging(false);
+            app.setKilledByAm(false);
+            app.setKilled(false);
+            // We carefully use the same state that PackageManager uses for
+            // filtering, since we use this flag to decide if we need to install
+            // providers when user is unlocked later
+            app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
+        }
 
         mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
 
@@ -4319,9 +4309,11 @@
             }
 
             boolean enableTrackAllocation = false;
-            if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
-                enableTrackAllocation = true;
-                mTrackAllocationApp = null;
+            synchronized (mProcLock) {
+                if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
+                    enableTrackAllocation = true;
+                    mTrackAllocationApp = null;
+                }
             }
 
             // If the app is being launched for restore or full backup, set it up specially
@@ -4342,7 +4334,7 @@
             ProtoLog.v(WM_DEBUG_CONFIGURATION, "Binding proc %s with config %s",
                     processName, app.getWindowProcessController().getConfiguration());
             ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info;
-            app.compat = compatibilityInfoForPackage(appInfo);
+            app.setCompat(compatibilityInfoForPackage(appInfo));
 
             ProfilerInfo profilerInfo = mAppProfiler.setupProfilerInfoLocked(thread, app, instr);
 
@@ -4386,10 +4378,11 @@
                 mPlatformCompat.resetReporting(app.info);
             }
             final ProviderInfoList providerList = ProviderInfoList.fromList(providers);
-            if (app.isolatedEntryPoint != null) {
+            if (app.getIsolatedEntryPoint() != null) {
                 // This is an isolated process which should just call an entry point instead of
                 // being bound to an application.
-                thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs);
+                thread.runIsolatedEntryPoint(
+                        app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
             } else if (instr2 != null) {
                 thread.bindApplication(processName, appInfo, providerList,
                         instr2.mClass,
@@ -4399,20 +4392,20 @@
                         mBinderTransactionTrackingEnabled, enableTrackAllocation,
                         isRestrictedBackupMode || !normalMode, app.isPersistent(),
                         new Configuration(app.getWindowProcessController().getConfiguration()),
-                        app.compat, getCommonServicesLocked(app.isolated),
+                        app.getCompat(), getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
                         buildSerial, autofillOptions, contentCaptureOptions,
-                        app.mDisabledCompatChanges, serializedSystemFontMap);
+                        app.getDisabledCompatChanges(), serializedSystemFontMap);
             } else {
                 thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
                         null, null, null, testMode,
                         mBinderTransactionTrackingEnabled, enableTrackAllocation,
                         isRestrictedBackupMode || !normalMode, app.isPersistent(),
                         new Configuration(app.getWindowProcessController().getConfiguration()),
-                        app.compat, getCommonServicesLocked(app.isolated),
+                        app.getCompat(), getCommonServicesLocked(app.isolated),
                         mCoreSettingsObserver.getCoreSettingsLocked(),
                         buildSerial, autofillOptions, contentCaptureOptions,
-                        app.mDisabledCompatChanges, serializedSystemFontMap);
+                        app.getDisabledCompatChanges(), serializedSystemFontMap);
             }
             if (profilerInfo != null) {
                 profilerInfo.closeFd();
@@ -4421,18 +4414,25 @@
 
             // Make app active after binding application or client may be running requests (e.g
             // starting activities) before it is ready.
-            app.makeActive(thread, mProcessStats);
-            checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
-            mProcessList.updateLruProcessLocked(app, false, null);
+            synchronized (mProcLock) {
+                app.makeActive(thread, mProcessStats);
+                checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");
+            }
+            updateLruProcessLocked(app, false, null);
             checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");
-            app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
+            final long now = SystemClock.uptimeMillis();
+            synchronized (mAppProfiler.mProfilerLock) {
+                app.mProfile.setLastRequestedGc(now);
+                app.mProfile.setLastLowMemory(now);
+            }
         } catch (Exception e) {
             // We need kill the process group here. (b/148588589)
             Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
             app.resetPackageList(mProcessStats);
             app.unlinkDeathRecipient();
-            app.kill("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
-            handleAppDiedLocked(app, false, true);
+            app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+                    true);
+            handleAppDiedLocked(app, pid, false, true);
             return false;
         }
 
@@ -4495,8 +4495,9 @@
         }
 
         if (badApp) {
-            app.kill("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
-            handleAppDiedLocked(app, false, true);
+            app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+                    true);
+            handleAppDiedLocked(app, pid, false, true);
             return false;
         }
 
@@ -4508,14 +4509,14 @@
         FrameworkStatsLog.write(
                 FrameworkStatsLog.PROCESS_START_TIME,
                 app.info.uid,
-                app.pid,
+                pid,
                 app.info.packageName,
                 FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
-                app.startTime,
-                (int) (bindApplicationTimeMillis - app.startTime),
-                (int) (SystemClock.elapsedRealtime() - app.startTime),
-                app.hostingRecord.getType(),
-                (app.hostingRecord.getName() != null ? app.hostingRecord.getName() : ""));
+                app.getStartTime(),
+                (int) (bindApplicationTimeMillis - app.getStartTime()),
+                (int) (SystemClock.elapsedRealtime() - app.getStartTime()),
+                app.getHostingRecord().getType(),
+                (app.getHostingRecord().getName() != null ? app.getHostingRecord().getName() : ""));
         return true;
     }
 
@@ -4608,11 +4609,11 @@
             // up.
             final int NP = mProcessesOnHold.size();
             if (NP > 0) {
-                ArrayList<ProcessRecord> procs =
-                    new ArrayList<ProcessRecord>(mProcessesOnHold);
-                for (int ip=0; ip<NP; ip++) {
-                    if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "Starting process on hold: "
-                            + procs.get(ip));
+                ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(mProcessesOnHold);
+                for (int ip = 0; ip < NP; ip++) {
+                    if (DEBUG_PROCESSES) {
+                        Slog.v(TAG_PROCESSES, "Starting process on hold: " + procs.get(ip));
+                    }
                     mProcessList.startProcessLocked(procs.get(ip),
                             new HostingRecord("on-hold"),
                             ZYGOTE_POLICY_FLAG_BATCH_LAUNCH);
@@ -4644,9 +4645,8 @@
                         public void performReceive(Intent intent, int resultCode,
                                 String data, Bundle extras, boolean ordered,
                                 boolean sticky, int sendingUser) {
-                            synchronized (ActivityManagerService.this) {
-                                mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
-                                mAppProfiler.requestPssAllProcsLocked(
+                            synchronized (mProcLock) {
+                                mAppProfiler.requestPssAllProcsLPr(
                                         SystemClock.uptimeMillis(), true, false);
                             }
                         }
@@ -5081,7 +5081,7 @@
                 if (pr == null) {
                     return;
                 }
-                pr.forcingToImportant = null;
+                pr.mState.setForcingToImportant(null);
                 updateProcessForegroundLocked(pr, false, 0, false);
             }
             updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
@@ -5107,7 +5107,7 @@
                     oldToken.token.unlinkToDeath(oldToken, 0);
                     mImportantProcesses.remove(pid);
                     if (pr != null) {
-                        pr.forcingToImportant = null;
+                        pr.mState.setForcingToImportant(null);
                     }
                     changed = true;
                 }
@@ -5121,7 +5121,7 @@
                     try {
                         token.linkToDeath(newToken, 0);
                         mImportantProcesses.put(pid, newToken);
-                        pr.forcingToImportant = newToken;
+                        pr.mState.setForcingToImportant(newToken);
                         changed = true;
                     } catch (RemoteException e) {
                         // If the process died while doing this, we will later
@@ -5137,13 +5137,12 @@
     }
 
     private boolean isAppForeground(int uid) {
-        synchronized (this) {
+        synchronized (mProcLock) {
             UidRecord uidRec = mProcessList.mActiveUids.get(uid);
-            if (uidRec == null || uidRec.idle) {
+            if (uidRec == null || uidRec.isIdle()) {
                 return false;
             }
-            return uidRec.getCurProcState()
-                    <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            return uidRec.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
         }
     }
 
@@ -5154,11 +5153,16 @@
     // NOTE: this is an internal method used by the OnShellCommand implementation only and should
     // be guarded by permission checking.
     int getUidState(int uid) {
-        synchronized (this) {
-            return mProcessList.getUidProcStateLocked(uid);
+        synchronized (mProcLock) {
+            return mProcessList.getUidProcStateLOSP(uid);
         }
     }
 
+    @GuardedBy("this")
+    int getUidStateLocked(int uid) {
+        return mProcessList.getUidProcStateLOSP(uid);
+    }
+
     // =========================================================
     // PROCESS INFO
     // =========================================================
@@ -5207,20 +5211,23 @@
             throw new IllegalArgumentException("pids and scores arrays have different lengths!");
         }
 
-        synchronized (mPidsSelfLocked) {
-            for (int i = 0; i < pids.length; i++) {
-                ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
-                if (pr != null) {
-                    final boolean isPendingTop =
+        synchronized (mProcLock) {
+            synchronized (mPidsSelfLocked) {
+                for (int i = 0; i < pids.length; i++) {
+                    ProcessRecord pr = mPidsSelfLocked.get(pids[i]);
+                    if (pr != null) {
+                        final boolean isPendingTop =
                                 mPendingStartActivityUids.isPendingTopPid(pr.uid, pids[i]);
-                    states[i] = isPendingTop ? PROCESS_STATE_TOP : pr.getCurProcState();
-                    if (scores != null) {
-                        scores[i] = isPendingTop ? (ProcessList.FOREGROUND_APP_ADJ - 1) : pr.curAdj;
-                    }
-                } else {
-                    states[i] = PROCESS_STATE_NONEXISTENT;
-                    if (scores != null) {
-                        scores[i] = ProcessList.INVALID_ADJ;
+                        states[i] = isPendingTop ? PROCESS_STATE_TOP : pr.mState.getCurProcState();
+                        if (scores != null) {
+                            scores[i] = isPendingTop
+                                    ? (ProcessList.FOREGROUND_APP_ADJ - 1) : pr.mState.getCurAdj();
+                        }
+                    } else {
+                        states[i] = PROCESS_STATE_NONEXISTENT;
+                        if (scores != null) {
+                            scores[i] = ProcessList.INVALID_ADJ;
+                        }
                     }
                 }
             }
@@ -5398,8 +5405,8 @@
     }
 
     public boolean isAppStartModeDisabled(int uid, String packageName) {
-        synchronized (this) {
-            return getAppStartModeLocked(uid, packageName, 0, -1, false, true, false)
+        synchronized (mProcLock) {
+            return getAppStartModeLOSP(uid, packageName, 0, -1, false, true, false)
                     == ActivityManager.APP_START_MODE_DISABLED;
         }
     }
@@ -5410,7 +5417,8 @@
     }
 
     // Unified app-op and target sdk check
-    int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    int appRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
         // Apps that target O+ are always subject to background check
         if (packageTargetSdk >= Build.VERSION_CODES.O) {
             if (DEBUG_BACKGROUND_CHECK) {
@@ -5439,7 +5447,7 @@
                 // If force-background-check is enabled, restrict all apps that aren't whitelisted.
                 if (mForceBackgroundCheck &&
                         !UserHandle.isCore(uid) &&
-                        !isOnDeviceIdleWhitelistLocked(uid, /*allowExceptIdleToo=*/ true)) {
+                        !isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ true)) {
                     if (DEBUG_BACKGROUND_CHECK) {
                         Slog.i(TAG, "Force background check: " +
                                 uid + "/" + packageName + " restricted");
@@ -5457,7 +5465,8 @@
     // Service launch is available to apps with run-in-background exemptions but
     // some other background operations are not.  If we're doing a check
     // of service-launch policy, allow those callers to proceed unrestricted.
-    int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    int appServicesRestrictedInBackgroundLOSP(int uid, String packageName, int packageTargetSdk) {
         // Persistent app?
         if (mPackageManagerInt.isPackagePersistent(packageName)) {
             if (DEBUG_BACKGROUND_CHECK) {
@@ -5468,7 +5477,7 @@
         }
 
         // Non-persistent but background whitelisted?
-        if (uidOnBackgroundWhitelist(uid)) {
+        if (uidOnBackgroundAllowlistLOSP(uid)) {
             if (DEBUG_BACKGROUND_CHECK) {
                 Slog.i(TAG, "App " + uid + "/" + packageName
                         + " on background whitelist; not restricted in background");
@@ -5477,7 +5486,7 @@
         }
 
         // Is this app on the battery whitelist?
-        if (isOnDeviceIdleWhitelistLocked(uid, /*allowExceptIdleToo=*/ false)) {
+        if (isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ false)) {
             if (DEBUG_BACKGROUND_CHECK) {
                 Slog.i(TAG, "App " + uid + "/" + packageName
                         + " on idle whitelist; not restricted in background");
@@ -5486,25 +5495,26 @@
         }
 
         // None of the service-policy criteria apply, so we apply the common criteria
-        return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
+        return appRestrictedInBackgroundLOSP(uid, packageName, packageTargetSdk);
     }
 
-    int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    int getAppStartModeLOSP(int uid, String packageName, int packageTargetSdk,
             int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
         if (mInternal.isPendingTopUid(uid)) {
             return ActivityManager.APP_START_MODE_NORMAL;
         }
-        UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
+        UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
         if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
                 + packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
-                + (uidRec != null ? uidRec.idle : false));
-        if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.idle) {
+                + (uidRec != null ? uidRec.isIdle() : false));
+        if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.isIdle()) {
             boolean ephemeral;
             if (uidRec == null) {
-                ephemeral = getPackageManagerInternalLocked().isPackageEphemeral(
+                ephemeral = getPackageManagerInternal().isPackageEphemeral(
                         UserHandle.getUserId(uid), packageName);
             } else {
-                ephemeral = uidRec.ephemeral;
+                ephemeral = uidRec.isEphemeral();
             }
 
             if (ephemeral) {
@@ -5519,14 +5529,14 @@
                     return ActivityManager.APP_START_MODE_NORMAL;
                 }
                 final int startMode = (alwaysRestrict)
-                        ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
-                        : appServicesRestrictedInBackgroundLocked(uid, packageName,
+                        ? appRestrictedInBackgroundLOSP(uid, packageName, packageTargetSdk)
+                        : appServicesRestrictedInBackgroundLOSP(uid, packageName,
                                 packageTargetSdk);
                 if (DEBUG_BACKGROUND_CHECK) {
                     Slog.d(TAG, "checkAllowBackground: uid=" + uid
                             + " pkg=" + packageName + " startMode=" + startMode
-                            + " onwhitelist=" + isOnDeviceIdleWhitelistLocked(uid, false)
-                            + " onwhitelist(ei)=" + isOnDeviceIdleWhitelistLocked(uid, true));
+                            + " onallowlist=" + isOnDeviceIdleAllowlistLOSP(uid, false)
+                            + " onallowlist(ei)=" + isOnDeviceIdleAllowlistLOSP(uid, true));
                 }
                 if (startMode == ActivityManager.APP_START_MODE_DELAYED) {
                     // This is an old app that has been forced into a "compatible as possible"
@@ -5537,8 +5547,8 @@
                         synchronized (mPidsSelfLocked) {
                             proc = mPidsSelfLocked.get(callingPid);
                         }
-                        if (proc != null &&
-                                !ActivityManager.isProcStateBackground(proc.getCurProcState())) {
+                        if (proc != null && !ActivityManager.isProcStateBackground(
+                                proc.mState.getCurProcState())) {
                             // Whoever is instigating this is in the foreground, so we will allow it
                             // to go through.
                             return ActivityManager.APP_START_MODE_NORMAL;
@@ -5552,38 +5562,41 @@
     }
 
     /**
-     * @return whether a UID is in the system, user or temp doze whitelist.
+     * @return whether a UID is in the system, user or temp doze allowlist.
      */
-    boolean isOnDeviceIdleWhitelistLocked(int uid, boolean allowExceptIdleToo) {
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    boolean isOnDeviceIdleAllowlistLOSP(int uid, boolean allowExceptIdleToo) {
         final int appId = UserHandle.getAppId(uid);
 
-        final int[] whitelist = allowExceptIdleToo
-                ? mDeviceIdleExceptIdleWhitelist
-                : mDeviceIdleWhitelist;
+        final int[] allowlist = allowExceptIdleToo
+                ? mDeviceIdleExceptIdleAllowlist
+                : mDeviceIdleAllowlist;
 
-        return Arrays.binarySearch(whitelist, appId) >= 0
-                || Arrays.binarySearch(mDeviceIdleTempWhitelist, appId) >= 0
-                || mPendingTempWhitelist.indexOfKey(uid) >= 0;
+        return Arrays.binarySearch(allowlist, appId) >= 0
+                || Arrays.binarySearch(mDeviceIdleTempAllowlist, appId) >= 0
+                || mPendingTempAllowlist.indexOfKey(uid) >= 0;
     }
 
-    boolean isWhitelistedForFgsStartLocked(int uid) {
-        return Arrays.binarySearch(mDeviceIdleExceptIdleWhitelist, UserHandle.getAppId(uid)) >= 0
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    boolean isAllowlistedForFgsStartLOSP(int uid) {
+        return Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, UserHandle.getAppId(uid)) >= 0
                 || mFgsStartTempAllowList.isAllowed(uid);
     }
 
     /**
-     * @return whitelist tag for a uid from mPendingTempWhitelist, null if not currently on
-     * the whitelist
+     * @return allowlist tag for a uid from mPendingTempAllowlist, null if not currently on
+     * the allowlist
      */
-    String getPendingTempWhitelistTagForUidLocked(int uid) {
-        final PendingTempWhitelist ptw = mPendingTempWhitelist.get(uid);
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    String getPendingTempAllowlistTagForUidLOSP(int uid) {
+        final PendingTempAllowlist ptw = mPendingTempAllowlist.get(uid);
         return ptw != null ? ptw.tag : null;
     }
 
     @VisibleForTesting
     public void grantImplicitAccess(int userId, Intent intent, int visibleUid, int recipientAppId) {
-        getPackageManagerInternalLocked().
-                grantImplicitAccess(userId, intent, recipientAppId, visibleUid, true /*direct*/);
+        getPackageManagerInternal()
+                .grantImplicitAccess(userId, intent, recipientAppId, visibleUid, true /*direct*/);
     }
 
     /**
@@ -5620,8 +5633,8 @@
             final int modeFlags, int userId) {
         enforceNotIsolatedCaller("grantUriPermission");
         GrantUri grantUri = new GrantUri(userId, uri, modeFlags);
-        synchronized(this) {
-            final ProcessRecord r = getRecordForAppLocked(caller);
+        synchronized (this) {
+            final ProcessRecord r = getRecordForAppLOSP(caller);
             if (r == null) {
                 throw new SecurityException("Unable to find app for caller "
                         + caller
@@ -5654,8 +5667,8 @@
     public void revokeUriPermission(IApplicationThread caller, String targetPackage, Uri uri,
             final int modeFlags, int userId) {
         enforceNotIsolatedCaller("revokeUriPermission");
-        synchronized(this) {
-            final ProcessRecord r = getRecordForAppLocked(caller);
+        synchronized (this) {
+            final ProcessRecord r = getRecordForAppLOSP(caller);
             if (r == null) {
                 throw new SecurityException("Unable to find app for caller "
                         + caller
@@ -5686,9 +5699,8 @@
 
     @Override
     public void showWaitingForDebugger(IApplicationThread who, boolean waiting) {
-        synchronized (this) {
-            ProcessRecord app =
-                who != null ? getRecordForAppLocked(who) : null;
+        synchronized (mProcLock) {
+            final ProcessRecord app = who != null ? getRecordForAppLOSP(who) : null;
             if (app == null) return;
 
             Message msg = Message.obtain();
@@ -5831,14 +5843,18 @@
     }
 
     @VisibleForTesting
-    public PackageManagerInternal getPackageManagerInternalLocked() {
+    public PackageManagerInternal getPackageManagerInternal() {
+        // Intentionally hold no locks: in case of race conditions, the mPackageManagerInt will
+        // be set to the same value anyway.
         if (mPackageManagerInt == null) {
             mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
         }
         return mPackageManagerInt;
     }
 
-    private PermissionManagerServiceInternal getPermissionManagerInternalLocked() {
+    private PermissionManagerServiceInternal getPermissionManagerInternal() {
+        // Intentionally hold no locks: in case of race conditions, the mPermissionManagerInt will
+        // be set to the same value anyway.
         if (mPermissionManagerInt == null) {
             mPermissionManagerInt =
                     LocalServices.getService(PermissionManagerServiceInternal.class);
@@ -5983,12 +5999,12 @@
     // GLOBAL MANAGEMENT
     // =========================================================
 
-    private boolean uidOnBackgroundWhitelist(final int uid) {
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    private boolean uidOnBackgroundAllowlistLOSP(final int uid) {
         final int appId = UserHandle.getAppId(uid);
-        final int[] whitelist = mBackgroundAppIdWhitelist;
-        final int N = whitelist.length;
-        for (int i = 0; i < N; i++) {
-            if (appId == whitelist[i]) {
+        final int[] allowlist = mBackgroundAppIdAllowlist;
+        for (int i = 0, len = allowlist.length; i < len; i++) {
+            if (appId == allowlist[i]) {
                 return true;
             }
         }
@@ -6028,11 +6044,13 @@
             Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist");
         }
         synchronized (this) {
-            final int N = mBackgroundAppIdWhitelist.length;
-            int[] newList = new int[N+1];
-            System.arraycopy(mBackgroundAppIdWhitelist, 0, newList, 0, N);
-            newList[N] = UserHandle.getAppId(uid);
-            mBackgroundAppIdWhitelist = newList;
+            synchronized (mProcLock) {
+                final int num = mBackgroundAppIdAllowlist.length;
+                int[] newList = new int[num + 1];
+                System.arraycopy(mBackgroundAppIdAllowlist, 0, newList, 0, num);
+                newList[num] = UserHandle.getAppId(uid);
+                mBackgroundAppIdAllowlist = newList;
+            }
         }
     }
 
@@ -6043,18 +6061,10 @@
                 abiOverride, zygotePolicyFlags);
     }
 
-    @GuardedBy("this")
-    final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
-            boolean disableHiddenApiChecks, String abiOverride, int zygotePolicyFlags) {
-        return addAppLocked(info, customProcess, isolated, disableHiddenApiChecks,
-                false /* disableTestApiChecks */, abiOverride, zygotePolicyFlags);
-    }
-
     // TODO: Move to ProcessList?
     @GuardedBy("this")
     final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
-            boolean disableHiddenApiChecks, boolean disableTestApiChecks,
-            String abiOverride, int zygotePolicyFlags) {
+            boolean disableHiddenApiChecks, String abiOverride, int zygotePolicyFlags) {
         ProcessRecord app;
         if (!isolated) {
             app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
@@ -6066,8 +6076,8 @@
         if (app == null) {
             app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0,
                     new HostingRecord("added application",
-                            customProcess != null ? customProcess : info.processName));
-            mProcessList.updateLruProcessLocked(app, false, null);
+                        customProcess != null ? customProcess : info.processName));
+            updateLruProcessLocked(app, false, null);
             updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
         }
 
@@ -6083,13 +6093,13 @@
 
         if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
             app.setPersistent(true);
-            app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
+            app.mState.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ);
         }
-        if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
+        if (app.getThread() == null && mPersistentStartingProcesses.indexOf(app) < 0) {
             mPersistentStartingProcesses.add(app);
             mProcessList.startProcessLocked(app, new HostingRecord("added application",
                     customProcess != null ? customProcess : app.processName),
-                    zygotePolicyFlags, disableHiddenApiChecks, disableTestApiChecks, abiOverride);
+                    zygotePolicyFlags, disableHiddenApiChecks, abiOverride);
         }
 
         return app;
@@ -6150,16 +6160,16 @@
     }
 
     void reportCurWakefulnessUsageEvent() {
-        reportGlobalUsageEvent(mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE
+        reportGlobalUsageEvent(mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE
                 ? UsageEvents.Event.SCREEN_INTERACTIVE
                 : UsageEvents.Event.SCREEN_NON_INTERACTIVE);
     }
 
     void onWakefulnessChanged(int wakefulness) {
-        synchronized(this) {
-            boolean wasAwake = mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
+        synchronized (this) {
+            boolean wasAwake = mWakefulness.getAndSet(wakefulness)
+                    == PowerManagerInternal.WAKEFULNESS_AWAKE;
             boolean isAwake = wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
-            mWakefulness = wakefulness;
 
             if (wasAwake != isAwake) {
                 // Also update state in a special way for running foreground services UI.
@@ -6269,34 +6279,33 @@
      */
     @Override
     public void setAgentApp(@NonNull String packageName, @Nullable String agent) {
-        synchronized (this) {
-            // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
-            // its own permission.
-            if (checkCallingPermission(
-                    android.Manifest.permission.SET_ACTIVITY_WATCHER) !=
-                        PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException(
-                        "Requires permission " + android.Manifest.permission.SET_ACTIVITY_WATCHER);
-            }
+        // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
+        // its own permission.
+        if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "Requires permission " + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+        }
 
-            mAppProfiler.setAgentAppLocked(packageName, agent);
+        synchronized (mAppProfiler.mProfilerLock) {
+            mAppProfiler.setAgentAppLPf(packageName, agent);
         }
     }
 
     void setTrackAllocationApp(ApplicationInfo app, String processName) {
-        synchronized (this) {
-            if (!Build.IS_DEBUGGABLE) {
-                if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
-                    throw new SecurityException("Process not debuggable: " + app.packageName);
-                }
+        if (!Build.IS_DEBUGGABLE) {
+            if ((app.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+                throw new SecurityException("Process not debuggable: " + app.packageName);
             }
+        }
 
+        synchronized (mProcLock) {
             mTrackAllocationApp = processName;
         }
     }
 
     void setProfileApp(ApplicationInfo app, String processName, ProfilerInfo profilerInfo) {
-        synchronized (this) {
+        synchronized (mAppProfiler.mProfilerLock) {
             if (!Build.IS_DEBUGGABLE) {
                 boolean isAppDebuggable = (app.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
                 boolean isAppProfileable = app.isProfileableByShell();
@@ -6305,7 +6314,7 @@
                             + "and not profileable by shell: " + app.packageName);
                 }
             }
-            mAppProfiler.setProfileAppLocked(processName, profilerInfo);
+            mAppProfiler.setProfileAppLPf(processName, profilerInfo);
         }
     }
 
@@ -6347,7 +6356,7 @@
 
     @Override
     public void setUserIsMonkey(boolean userIsMonkey) {
-        synchronized (this) {
+        synchronized (mProcLock) {
             synchronized (mPidsSelfLocked) {
                 final int callingPid = Binder.getCallingPid();
                 ProcessRecord proc = mPidsSelfLocked.get(callingPid);
@@ -6366,7 +6375,7 @@
 
     @Override
     public boolean isUserAMonkey() {
-        synchronized (this) {
+        synchronized (mProcLock) {
             // If there is a controller also implies the user is a monkey.
             return mUserIsMonkey || mActivityTaskManager.isControllerAMonkey();
         }
@@ -6391,8 +6400,8 @@
             Slog.w(TAG, "system process not in mPidsSelfLocked: " + myPid());
             return;
         }
-        synchronized (this) {
-            mAppProfiler.startHeapDumpLocked(pr, true);
+        synchronized (mAppProfiler.mProfilerLock) {
+            mAppProfiler.startHeapDumpLPf(pr.mProfile, true);
         }
     }
 
@@ -6572,16 +6581,12 @@
     public void registerProcessObserver(IProcessObserver observer) {
         enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerProcessObserver()");
-        synchronized (this) {
-            mProcessObservers.register(observer);
-        }
+        mProcessList.registerProcessObserver(observer);
     }
 
     @Override
     public void unregisterProcessObserver(IProcessObserver observer) {
-        synchronized (this) {
-            mProcessObservers.unregister(observer);
-        }
+        mProcessList.unregisterProcessObserver(observer);
     }
 
     @Override
@@ -6591,8 +6596,8 @@
                     "getUidProcessState");
         }
 
-        synchronized (this) {
-            return mProcessList.getUidProcStateLocked(uid);
+        synchronized (mProcLock) {
+            return mProcessList.getUidProcStateLOSP(uid);
         }
     }
 
@@ -6603,17 +6608,13 @@
             enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
                     "registerUidObserver");
         }
-        synchronized (this) {
-            mUidObserverController.register(observer, which, cutpoint, callingPackage,
-                    Binder.getCallingUid());
-        }
+        mUidObserverController.register(observer, which, cutpoint, callingPackage,
+                Binder.getCallingUid());
     }
 
     @Override
     public void unregisterUidObserver(IUidObserver observer) {
-        synchronized (this) {
-            mUidObserverController.unregister(observer);
-        }
+        mUidObserverController.unregister(observer);
     }
 
     @Override
@@ -6622,17 +6623,18 @@
             enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
                     "isUidActive");
         }
-        synchronized (this) {
-            if (isUidActiveLocked(uid)) {
+        synchronized (mProcLock) {
+            if (isUidActiveLOSP(uid)) {
                 return true;
             }
         }
         return mInternal.isPendingTopUid(uid);
     }
 
-    boolean isUidActiveLocked(int uid) {
-        final UidRecord uidRecord = mProcessList.getUidRecordLocked(uid);
-        return uidRecord != null && !uidRecord.setIdle;
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    boolean isUidActiveLOSP(int uid) {
+        final UidRecord uidRecord = mProcessList.getUidRecordLOSP(uid);
+        return uidRecord != null && !uidRecord.isSetIdle();
     }
 
     @Override
@@ -6690,7 +6692,7 @@
 
     @Override
     public void setRenderThread(int tid) {
-        synchronized (this) {
+        synchronized (mProcLock) {
             ProcessRecord proc;
             int pid = Binder.getCallingPid();
             if (pid == Process.myPid()) {
@@ -6699,32 +6701,31 @@
             }
             synchronized (mPidsSelfLocked) {
                 proc = mPidsSelfLocked.get(pid);
-                if (proc != null && proc.renderThreadTid == 0 && tid > 0) {
-                    // ensure the tid belongs to the process
-                    if (!isThreadInProcess(pid, tid)) {
-                        throw new IllegalArgumentException(
+            }
+            if (proc != null && proc.getRenderThreadTid() == 0 && tid > 0) {
+                // ensure the tid belongs to the process
+                if (!isThreadInProcess(pid, tid)) {
+                    throw new IllegalArgumentException(
                             "Render thread does not belong to process");
-                    }
-                    proc.renderThreadTid = tid;
-                    if (DEBUG_OOM_ADJ) {
-                        Slog.d("UI_FIFO", "Set RenderThread tid " + tid + " for pid " + pid);
-                    }
-                    // promote to FIFO now
-                    if (proc.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
-                        if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
-                        if (mUseFifoUiScheduling) {
-                            setThreadScheduler(proc.renderThreadTid,
+                }
+                proc.setRenderThreadTid(tid);
+                if (DEBUG_OOM_ADJ) {
+                    Slog.d("UI_FIFO", "Set RenderThread tid " + tid + " for pid " + pid);
+                }
+                // promote to FIFO now
+                if (proc.mState.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
+                    if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
+                    if (mUseFifoUiScheduling) {
+                        setThreadScheduler(proc.getRenderThreadTid(),
                                 SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
-                        } else {
-                            setThreadPriority(proc.renderThreadTid, TOP_APP_PRIORITY_BOOST);
-                        }
+                    } else {
+                        setThreadPriority(proc.getRenderThreadTid(), TOP_APP_PRIORITY_BOOST);
                     }
-                } else {
-                    if (DEBUG_OOM_ADJ) {
-                        Slog.d("UI_FIFO", "Didn't set thread from setRenderThread? " +
-                               "PID: " + pid + ", TID: " + tid + " FIFO: " +
-                               mUseFifoUiScheduling);
-                    }
+                }
+            } else {
+                if (DEBUG_OOM_ADJ) {
+                    Slog.d("UI_FIFO", "Didn't set thread from setRenderThread? "
+                            + "PID: " + pid + ", TID: " + tid + " FIFO: " + mUseFifoUiScheduling);
                 }
             }
         }
@@ -6781,11 +6782,11 @@
                         Slog.w(TAG, "setHasTopUi called on unknown pid: " + pid);
                         return;
                     }
-                    if (pr.hasTopUi() != hasTopUi) {
+                    if (pr.mState.hasTopUi() != hasTopUi) {
                         if (DEBUG_OOM_ADJ) {
                             Slog.d(TAG, "Setting hasTopUi=" + hasTopUi + " for pid=" + pid);
                         }
-                        pr.setHasTopUi(hasTopUi);
+                        pr.mState.setHasTopUi(hasTopUi);
                         changed = true;
                     }
                 }
@@ -6965,42 +6966,46 @@
         // manager calls in with its locks held.
 
         boolean killed = false;
-        synchronized (mPidsSelfLocked) {
-            int worstType = 0;
-            for (int i=0; i<pids.length; i++) {
-                ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
-                if (proc != null) {
-                    int type = proc.setAdj;
-                    if (type > worstType) {
-                        worstType = type;
+        synchronized (this) {
+            synchronized (mProcLock) {
+                synchronized (mPidsSelfLocked) {
+                    int worstType = 0;
+                    for (int i = 0; i < pids.length; i++) {
+                        ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
+                        if (proc != null) {
+                            int type = proc.mState.getSetAdj();
+                            if (type > worstType) {
+                                worstType = type;
+                            }
+                        }
                     }
-                }
-            }
 
-            // If the worst oom_adj is somewhere in the cached proc LRU range,
-            // then constrain it so we will kill all cached procs.
-            if (worstType < ProcessList.CACHED_APP_MAX_ADJ
-                    && worstType > ProcessList.CACHED_APP_MIN_ADJ) {
-                worstType = ProcessList.CACHED_APP_MIN_ADJ;
-            }
+                    // If the worst oom_adj is somewhere in the cached proc LRU range,
+                    // then constrain it so we will kill all cached procs.
+                    if (worstType < ProcessList.CACHED_APP_MAX_ADJ
+                            && worstType > ProcessList.CACHED_APP_MIN_ADJ) {
+                        worstType = ProcessList.CACHED_APP_MIN_ADJ;
+                    }
 
-            // If this is not a secure call, don't let it kill processes that
-            // are important.
-            if (!secure && worstType < ProcessList.SERVICE_ADJ) {
-                worstType = ProcessList.SERVICE_ADJ;
-            }
+                    // If this is not a secure call, don't let it kill processes that
+                    // are important.
+                    if (!secure && worstType < ProcessList.SERVICE_ADJ) {
+                        worstType = ProcessList.SERVICE_ADJ;
+                    }
 
-            Slog.w(TAG, "Killing processes " + reason + " at adjustment " + worstType);
-            for (int i=0; i<pids.length; i++) {
-                ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
-                if (proc == null) {
-                    continue;
-                }
-                int adj = proc.setAdj;
-                if (adj >= worstType && !proc.killedByAm) {
-                    proc.kill(reason, ApplicationExitInfo.REASON_OTHER,
-                            ApplicationExitInfo.SUBREASON_KILL_PID, true);
-                    killed = true;
+                    Slog.w(TAG, "Killing processes " + reason + " at adjustment " + worstType);
+                    for (int i = 0; i < pids.length; i++) {
+                        ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
+                        if (proc == null) {
+                            continue;
+                        }
+                        int adj = proc.mState.getSetAdj();
+                        if (adj >= worstType && !proc.isKilledByAm()) {
+                            proc.killLocked(reason, ApplicationExitInfo.REASON_OTHER,
+                                    ApplicationExitInfo.SUBREASON_KILL_PID, true);
+                            killed = true;
+                        }
+                    }
                 }
             }
         }
@@ -7013,13 +7018,15 @@
         synchronized (this) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mProcessList.killPackageProcessesLocked(null /* packageName */, appId, userId,
-                        ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
-                        true /* callerWillRestart */, true /* doit */, true /* evenPersistent */,
-                        false /* setRemoved */,
-                        ApplicationExitInfo.REASON_OTHER,
-                        ApplicationExitInfo.SUBREASON_KILL_UID,
-                        reason != null ? reason : "kill uid");
+                synchronized (mProcLock) {
+                    mProcessList.killPackageProcessesLSP(null /* packageName */, appId, userId,
+                            ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
+                            true /* callerWillRestart */, true /* doit */,
+                            true /* evenPersistent */, false /* setRemoved */,
+                            ApplicationExitInfo.REASON_OTHER,
+                            ApplicationExitInfo.SUBREASON_KILL_UID,
+                            reason != null ? reason : "kill uid");
+                }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -7032,13 +7039,15 @@
         synchronized (this) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mProcessList.killPackageProcessesLocked(null /* packageName */, appId, userId,
-                        ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
-                        true /* callerWillRestart */, true /* doit */, true /* evenPersistent */,
-                        false /* setRemoved */,
-                        ApplicationExitInfo.REASON_PERMISSION_CHANGE,
-                        ApplicationExitInfo.SUBREASON_UNKNOWN,
-                        reason != null ? reason : "kill uid");
+                synchronized (mProcLock) {
+                    mProcessList.killPackageProcessesLSP(null /* packageName */, appId, userId,
+                            ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
+                            true /* callerWillRestart */, true /* doit */,
+                            true /* evenPersistent */, false /* setRemoved */,
+                            ApplicationExitInfo.REASON_PERMISSION_CHANGE,
+                            ApplicationExitInfo.SUBREASON_UNKNOWN,
+                            reason != null ? reason : "kill uid");
+                }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -7060,17 +7069,22 @@
         }
 
         boolean killed = false;
-        synchronized (mPidsSelfLocked) {
-            final int size = mPidsSelfLocked.size();
-            for (int i = 0; i < size; i++) {
-                final int pid = mPidsSelfLocked.keyAt(i);
-                final ProcessRecord proc = mPidsSelfLocked.valueAt(i);
-                if (proc == null) continue;
+        synchronized (this) {
+            synchronized (mProcLock) {
+                synchronized (mPidsSelfLocked) {
+                    final int size = mPidsSelfLocked.size();
+                    for (int i = 0; i < size; i++) {
+                        final int pid = mPidsSelfLocked.keyAt(i);
+                        final ProcessRecord proc = mPidsSelfLocked.valueAt(i);
+                        if (proc == null) continue;
 
-                final int adj = proc.setAdj;
-                if (adj > belowAdj && !proc.killedByAm) {
-                    proc.kill(reason, ApplicationExitInfo.REASON_PERMISSION_CHANGE, true);
-                    killed = true;
+                        final int adj = proc.mState.getSetAdj();
+                        if (adj > belowAdj && !proc.isKilledByAm()) {
+                            proc.killLocked(reason, ApplicationExitInfo.REASON_PERMISSION_CHANGE,
+                                    true);
+                            killed = true;
+                        }
+                    }
                 }
             }
         }
@@ -7176,16 +7190,16 @@
                     + android.Manifest.permission.SET_ACTIVITY_WATCHER);
         }
 
-        synchronized (this) {
+        synchronized (mProcLock) {
             final long now = SystemClock.uptimeMillis();
             final long timeSinceLastIdle = now - mLastIdleTime;
 
             // Compact all non-zygote processes to freshen up the page cache.
             mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
 
-            final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLocked(now);
+            final long lowRamSinceLastIdle = mAppProfiler.getLowRamTimeSinceIdleLPr(now);
             mLastIdleTime = now;
-            mAppProfiler.updateLowRamTimestampLocked(now);
+            mAppProfiler.updateLowRamTimestampLPr(now);
 
             StringBuilder sb = new StringBuilder(128);
             sb.append("Idle maintenance over ");
@@ -7202,43 +7216,57 @@
             final long totalMemoryInKb = getTotalMemory() / 1000;
             final long memoryGrowthThreshold =
                     Math.max(totalMemoryInKb / 100, MINIMUM_MEMORY_GROWTH_THRESHOLD);
-
-            for (int i = mProcessList.mLruProcesses.size() - 1 ; i >= 0 ; i--) {
-                ProcessRecord proc = mProcessList.mLruProcesses.get(i);
-                if (proc.notCachedSinceIdle) {
-                    if (proc.setProcState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-                            && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
-                        if (doKilling && proc.initialIdlePss != 0
-                                && proc.lastPss > ((proc.initialIdlePss * 3) / 2)
-                                && proc.lastPss > (proc.initialIdlePss + memoryGrowthThreshold)) {
-                            sb = new StringBuilder(128);
-                            sb.append("Kill");
-                            sb.append(proc.processName);
-                            sb.append(" in idle maint: pss=");
-                            sb.append(proc.lastPss);
-                            sb.append(", swapPss=");
-                            sb.append(proc.lastSwapPss);
-                            sb.append(", initialPss=");
-                            sb.append(proc.initialIdlePss);
-                            sb.append(", period=");
-                            TimeUtils.formatDuration(timeSinceLastIdle, sb);
-                            sb.append(", lowRamPeriod=");
-                            TimeUtils.formatDuration(lowRamSinceLastIdle, sb);
-                            Slog.wtfQuiet(TAG, sb.toString());
-                            proc.kill("idle maint (pss " + proc.lastPss
-                                    + " from " + proc.initialIdlePss + ")",
-                                    ApplicationExitInfo.REASON_OTHER,
-                                    ApplicationExitInfo.SUBREASON_MEMORY_PRESSURE,
-                                    true);
+            mProcessList.forEachLruProcessesLOSP(false, proc -> {
+                final ProcessProfileRecord pr = proc.mProfile;
+                final ProcessStateRecord state = proc.mState;
+                final int setProcState = state.getSetProcState();
+                if (state.isNotCachedSinceIdle()) {
+                    if (setProcState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+                            && setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
+                        final long initialIdlePss, lastPss, lastSwapPss;
+                        synchronized (mAppProfiler.mProfilerLock) {
+                            initialIdlePss = pr.getInitialIdlePss();
+                            lastPss = pr.getLastPss();
+                            lastSwapPss = pr.getLastSwapPss();
+                        }
+                        if (doKilling && initialIdlePss != 0
+                                && lastPss > (initialIdlePss * 3 / 2)
+                                && lastPss > (initialIdlePss + memoryGrowthThreshold)) {
+                            final StringBuilder sb2 = new StringBuilder(128);
+                            sb2.append("Kill");
+                            sb2.append(proc.processName);
+                            sb2.append(" in idle maint: pss=");
+                            sb2.append(lastPss);
+                            sb2.append(", swapPss=");
+                            sb2.append(lastSwapPss);
+                            sb2.append(", initialPss=");
+                            sb2.append(initialIdlePss);
+                            sb2.append(", period=");
+                            TimeUtils.formatDuration(timeSinceLastIdle, sb2);
+                            sb2.append(", lowRamPeriod=");
+                            TimeUtils.formatDuration(lowRamSinceLastIdle, sb2);
+                            Slog.wtfQuiet(TAG, sb2.toString());
+                            mHandler.post(() -> {
+                                synchronized (ActivityManagerService.this) {
+                                    proc.killLocked("idle maint (pss " + lastPss
+                                            + " from " + initialIdlePss + ")",
+                                            ApplicationExitInfo.REASON_OTHER,
+                                            ApplicationExitInfo.SUBREASON_MEMORY_PRESSURE,
+                                            true);
+                                }
+                            });
                         }
                     }
-                } else if (proc.setProcState < ActivityManager.PROCESS_STATE_HOME
-                        && proc.setProcState >= ActivityManager.PROCESS_STATE_PERSISTENT) {
-                    proc.notCachedSinceIdle = true;
-                    proc.initialIdlePss = 0;
-                    mAppProfiler.updateNextPssTimeLocked(proc.setProcState, proc, now, true);
+                } else if (setProcState < ActivityManager.PROCESS_STATE_HOME
+                        && setProcState >= ActivityManager.PROCESS_STATE_PERSISTENT) {
+                    state.setNotCachedSinceIdle(true);
+                    synchronized (mAppProfiler.mProfilerLock) {
+                        pr.setInitialIdlePss(0);
+                        mAppProfiler.updateNextPssTimeLPf(
+                                state.getSetProcState(), proc.mProfile, now, true);
+                    }
                 }
-            }
+            });
         }
     }
 
@@ -7277,14 +7305,13 @@
 
         mAppProfiler.retrieveSettings();
 
+        final Resources res;
         synchronized (this) {
             mDebugApp = mOrigDebugApp = debugApp;
             mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
             mAlwaysFinishActivities = alwaysFinishActivities;
             // Load resources only after the current configuration has been set.
-            final Resources res = mContext.getResources();
-            mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
-                    com.android.internal.R.string.config_appsNotReportingCrashes));
+            res = mContext.getResources();
             final boolean userSwitchUiEnabled = !res.getBoolean(
                     com.android.internal.R.bool.config_customUserSwitchUi);
             final int maxRunningUsers = res.getInteger(
@@ -7295,6 +7322,8 @@
                     delayUserDataLocking);
             mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
         }
+        mAppErrors.loadAppsNotReportingCrashesFromConfig(res.getString(
+                com.android.internal.R.string.config_appsNotReportingCrashes));
     }
 
     /**
@@ -7348,7 +7377,7 @@
 
         synchronized(this) {
             if (procsToKill != null) {
-                for (int i=procsToKill.size()-1; i>=0; i--) {
+                for (int i = procsToKill.size() - 1; i >= 0; i--) {
                     ProcessRecord proc = procsToKill.get(i);
                     Slog.i(TAG, "Removing system update proc: " + proc);
                     mProcessList.removeProcessLocked(proc, true, false,
@@ -7575,16 +7604,18 @@
 
     private void updateForceBackgroundCheck(boolean enabled) {
         synchronized (this) {
-            if (mForceBackgroundCheck != enabled) {
-                mForceBackgroundCheck = enabled;
+            synchronized (mProcLock) {
+                if (mForceBackgroundCheck != enabled) {
+                    mForceBackgroundCheck = enabled;
 
-                if (DEBUG_BACKGROUND_CHECK) {
-                    Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
-                }
+                    if (DEBUG_BACKGROUND_CHECK) {
+                        Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
+                    }
 
-                if (mForceBackgroundCheck) {
-                    // Stop background services for idle UIDs.
-                    mProcessList.doStopUidForIdleUidsLocked();
+                    if (mForceBackgroundCheck) {
+                        // Stop background services for idle UIDs.
+                        mProcessList.doStopUidForIdleUidsLocked();
+                    }
                 }
             }
         }
@@ -7649,7 +7680,7 @@
                 (r != null) ? r.uid : -1,
                 eventType,
                 processName,
-                (r != null) ? r.pid : -1,
+                (r != null) ? r.getPid() : -1,
                 (r != null && r.info != null) ? r.info.packageName : "",
                 (r != null && r.info != null) ? (r.info.isInstantApp()
                         ? FrameworkStatsLog.APP_CRASH_OCCURRED__IS_INSTANT_APP__TRUE
@@ -7714,9 +7745,8 @@
 
         if ((penaltyMask & StrictMode.PENALTY_DIALOG) != 0) {
             AppErrorResult result = new AppErrorResult();
-            synchronized (this) {
-                final long origId = Binder.clearCallingIdentity();
-
+            final long origId = Binder.clearCallingIdentity();
+            try {
                 Message msg = Message.obtain();
                 msg.what = SHOW_STRICT_MODE_VIOLATION_UI_MSG;
                 HashMap<String, Object> data = new HashMap<String, Object>();
@@ -7725,7 +7755,7 @@
                 data.put("info", info);
                 msg.obj = data;
                 mUiHandler.sendMessage(msg);
-
+            } finally {
                 Binder.restoreCallingIdentity(origId);
             }
             int res = result.get();
@@ -7896,8 +7926,8 @@
             return null;
         }
 
-        synchronized (this) {
-            return mProcessList.findAppProcessLocked(app, reason);
+        synchronized (mProcLock) {
+            return mProcessList.findAppProcessLOSP(app, reason);
         }
     }
 
@@ -7906,7 +7936,7 @@
      * to append various headers to the dropbox log text.
      */
     void appendDropBoxProcessHeaders(ProcessRecord process, String processName,
-            StringBuilder sb) {
+            final StringBuilder sb) {
         // Watchdog thread ends up invoking this function (with
         // a null ProcessRecord) to add the stack file to dropbox.
         // Do not acquire a lock on this (am) in such cases, as it
@@ -7920,18 +7950,18 @@
         // Note: ProcessRecord 'process' is guarded by the service
         // instance.  (notably process.pkgList, which could otherwise change
         // concurrently during execution of this method)
-        synchronized (this) {
+        synchronized (mProcLock) {
             sb.append("Process: ").append(processName).append("\n");
-            sb.append("PID: ").append(process.pid).append("\n");
+            sb.append("PID: ").append(process.getPid()).append("\n");
             sb.append("UID: ").append(process.uid).append("\n");
             int flags = process.info.flags;
-            IPackageManager pm = AppGlobals.getPackageManager();
+            final IPackageManager pm = AppGlobals.getPackageManager();
             sb.append("Flags: 0x").append(Integer.toHexString(flags)).append("\n");
-            for (int ip=0; ip<process.pkgList.size(); ip++) {
-                String pkg = process.pkgList.keyAt(ip);
+            final int callingUserId = UserHandle.getCallingUserId();
+            process.getPkgList().forEachPackage(pkg -> {
                 sb.append("Package: ").append(pkg);
                 try {
-                    PackageInfo pi = pm.getPackageInfo(pkg, 0, UserHandle.getCallingUserId());
+                    final PackageInfo pi = pm.getPackageInfo(pkg, 0, callingUserId);
                     if (pi != null) {
                         sb.append(" v").append(pi.getLongVersionCode());
                         if (pi.versionName != null) {
@@ -7942,7 +7972,7 @@
                     Slog.e(TAG, "Error getting package info: " + pkg, e);
                 }
                 sb.append("\n");
-            }
+            });
             if (process.info.isInstantApp()) {
                 sb.append("Instant-App: true\n");
             }
@@ -7950,7 +7980,7 @@
     }
 
     private static String processClass(ProcessRecord process) {
-        if (process == null || process.pid == MY_PID) {
+        if (process == null || process.getPid() == MY_PID) {
             return "system_server";
         } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
             return "system_app";
@@ -8012,8 +8042,8 @@
             sb.append("Foreground: ")
                     .append(process.isInterestingToUserLocked() ? "Yes" : "No")
                     .append("\n");
-            if (process.startTime > 0) {
-                long runtimeMillis = SystemClock.elapsedRealtime() - process.startTime;
+            if (process.getStartTime() > 0) {
+                long runtimeMillis = SystemClock.elapsedRealtime() - process.getStartTime();
                 sb.append("Process-Runtime: ").append(runtimeMillis).append("\n");
             }
         }
@@ -8021,7 +8051,7 @@
             sb.append("Activity: ").append(activityShortComponentName).append("\n");
         }
         if (parentShortComponentName != null) {
-            if (parentProcess != null && parentProcess.pid != process.pid) {
+            if (parentProcess != null && parentProcess.getPid() != process.getPid()) {
                 sb.append("Parent-Process: ").append(parentProcess.processName).append("\n");
             }
             if (!parentShortComponentName.equals(activityShortComponentName)) {
@@ -8117,47 +8147,46 @@
     public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
         enforceNotIsolatedCaller("getProcessesInErrorState");
         // assume our apps are happy - lazy create the list
-        List<ActivityManager.ProcessErrorStateInfo> errList = null;
+        final List<ActivityManager.ProcessErrorStateInfo>[] errList = new List[1];
 
         final boolean allUsers = ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL,
                 Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED;
         int userId = UserHandle.getUserId(Binder.getCallingUid());
 
-        synchronized (this) {
-
+        synchronized (mProcLock) {
             // iterate across all processes
-            for (int i=mProcessList.mLruProcesses.size()-1; i>=0; i--) {
-                ProcessRecord app = mProcessList.mLruProcesses.get(i);
+            mProcessList.forEachLruProcessesLOSP(false, app -> {
                 if (!allUsers && app.userId != userId) {
-                    continue;
+                    return;
                 }
-                final boolean crashing = app.isCrashing();
-                final boolean notResponding = app.isNotResponding();
-                if ((app.thread != null) && (crashing || notResponding)) {
+                final ProcessErrorStateRecord errState = app.mErrorState;
+                final boolean crashing = errState.isCrashing();
+                final boolean notResponding = errState.isNotResponding();
+                if ((app.getThread() != null) && (crashing || notResponding)) {
                     // This one's in trouble, so we'll generate a report for it
                     // crashes are higher priority (in case there's a crash *and* an anr)
                     ActivityManager.ProcessErrorStateInfo report = null;
                     if (crashing) {
-                        report = app.crashingReport;
+                        report = errState.getCrashingReport();
                     } else if (notResponding) {
-                        report = app.notRespondingReport;
+                        report = errState.getNotRespondingReport();
                     }
 
                     if (report != null) {
-                        if (errList == null) {
-                            errList = new ArrayList<>(1);
+                        if (errList[0] == null) {
+                            errList[0] = new ArrayList<>(1);
                         }
-                        errList.add(report);
+                        errList[0].add(report);
                     } else {
                         Slog.w(TAG, "Missing app error report, app = " + app.processName +
                                 " crashing = " + crashing +
                                 " notResponding = " + notResponding);
                     }
                 }
-            }
+            });
         }
 
-        return errList;
+        return errList[0];
     }
 
     @Override
@@ -8173,9 +8202,9 @@
         final boolean allUids = mAtmInternal.isGetTasksAllowed(
                 "getRunningAppProcesses", Binder.getCallingPid(), callingUid);
 
-        synchronized (this) {
+        synchronized (mProcLock) {
             // Iterate across all processes
-            return mProcessList.getRunningAppProcessesLocked(allUsers, userId, allUids,
+            return mProcessList.getRunningAppProcessesLOSP(allUsers, userId, allUids,
                     callingUid, clientTargetSdk);
         }
     }
@@ -8286,13 +8315,13 @@
         final int callingUid = Binder.getCallingUid();
         final int clientTargetSdk = mPackageManagerInt.getUidTargetSdkVersion(callingUid);
 
-        synchronized (this) {
+        synchronized (mProcLock) {
             ProcessRecord proc;
             synchronized (mPidsSelfLocked) {
                 proc = mPidsSelfLocked.get(Binder.getCallingPid());
             }
             if (proc != null) {
-                mProcessList.fillInProcMemInfoLocked(proc, outState, clientTargetSdk);
+                mProcessList.fillInProcMemInfoLOSP(proc, outState, clientTargetSdk);
             }
         }
     }
@@ -8338,7 +8367,9 @@
 
         synchronized(this) {
             mConstants.dump(pw);
-            mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
+            synchronized (mProcLock) {
+                mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
+            }
             mOomAdjuster.dumpCacheOomRankerSettings(pw);
             pw.println();
             if (dumpAll) {
@@ -8373,7 +8404,7 @@
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
-            dumpPermissionsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+            dumpPermissions(fd, pw, args, opti, dumpAll, dumpPackage);
             pw.println();
             sdumper = mServices.newServiceDumperLocked(fd, pw, args, opti, dumpAll, dumpPackage);
             if (!dumpClient) {
@@ -8470,12 +8501,14 @@
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
-            dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
+            synchronized (mProcLock) {
+                mProcessList.dumpProcessesLSP(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
+            }
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
-            dumpUsersLocked(pw);
+            dumpUsers(pw);
         }
     }
 
@@ -8574,7 +8607,9 @@
                 }
                 // output proto is ProcessProto
                 synchronized (this) {
-                    writeProcessesToProtoLocked(proto, dumpPackage);
+                    synchronized (mProcLock) {
+                        mProcessList.writeProcessesToProtoLSP(proto, dumpPackage);
+                    }
                 }
             } else {
                 // default option, dump everything, output is ActivityManagerServiceProto
@@ -8593,7 +8628,9 @@
                     proto.end(serviceToken);
 
                     long processToken = proto.start(ActivityManagerServiceProto.PROCESSES);
-                    writeProcessesToProtoLocked(proto, dumpPackage);
+                    synchronized (mProcLock) {
+                        mProcessList.writeProcessesToProtoLSP(proto, dumpPackage);
+                    }
                     proto.end(processToken);
                 }
             }
@@ -8660,20 +8697,21 @@
                     dumpPackage = args[opti];
                     opti++;
                 }
-                synchronized (this) {
-                    mPendingIntentController.dumpPendingIntents(pw, true, dumpPackage);
-                }
+                mPendingIntentController.dumpPendingIntents(pw, true, dumpPackage);
             } else if ("processes".equals(cmd) || "p".equals(cmd)) {
                 if (opti < args.length) {
                     dumpPackage = args[opti];
                     opti++;
                 }
                 synchronized (this) {
-                    dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage, dumpAppId);
+                    synchronized (mProcLock) {
+                        mProcessList.dumpProcessesLSP(
+                                fd, pw, args, opti, true, dumpPackage, dumpAppId);
+                    }
                 }
             } else if ("oom".equals(cmd) || "o".equals(cmd)) {
                 synchronized (this) {
-                    dumpOomLocked(fd, pw, false, args, opti, true, dumpPackage, true);
+                    mProcessList.dumpOomLocked(fd, pw, false, args, opti, true, dumpPackage, true);
                 }
             } else if ("lmk".equals(cmd)) {
                 synchronized (this) {
@@ -8681,12 +8719,10 @@
                 }
             } else if ("lru".equals(cmd)) {
                 synchronized (this) {
-                    dumpLruLocked(pw, dumpPackage, null);
+                    mProcessList.dumpLruLocked(pw, dumpPackage, null);
                 }
             } else if ("permissions".equals(cmd) || "perm".equals(cmd)) {
-                synchronized (this) {
-                    dumpPermissionsLocked(fd, pw, args, opti, true, dumpPackage);
-                }
+                dumpPermissions(fd, pw, args, opti, true, dumpPackage);
             } else if ("provider".equals(cmd)) {
                 String[] newArgs;
                 String name;
@@ -8759,6 +8795,8 @@
             } else if ("settings".equals(cmd)) {
                 synchronized (this) {
                     mConstants.dump(pw);
+                }
+                synchronized (mProcLock) {
                     mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
                     mOomAdjuster.dumpCacheOomRankerSettings(pw);
                 }
@@ -8779,9 +8817,7 @@
             } else if ("locks".equals(cmd)) {
                 LockGuard.dump(fd, pw, args);
             } else if ("users".equals(cmd)) {
-                synchronized (this) {
-                    dumpUsersLocked(pw);
-                }
+                dumpUsers(pw);
             } else if ("exit-info".equals(cmd)) {
                 if (opti < args.length) {
                     dumpPackage = args[opti];
@@ -8918,7 +8954,7 @@
         }
     }
 
-    private int getAppId(String dumpPackage) {
+    int getAppId(String dumpPackage) {
         if (dumpPackage != null) {
             try {
                 ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
@@ -8983,177 +9019,12 @@
                 "  Counts of Binder Proxies held by SYSTEM");
     }
 
-    void dumpLruEntryLocked(PrintWriter pw, int index, ProcessRecord proc, String prefix) {
-        pw.print(prefix);
-        pw.print('#');
-        if (index < 10) {
-            pw.print(' ');
-        }
-        pw.print(index);
-        pw.print(": ");
-        pw.print(ProcessList.makeOomAdjString(proc.setAdj, false));
-        pw.print(' ');
-        pw.print(ProcessList.makeProcStateString(proc.getCurProcState()));
-        pw.print(' ');
-        ActivityManager.printCapabilitiesSummary(pw, proc.curCapability);
-        pw.print(' ');
-        pw.print(proc.toShortString());
-        if (proc.hasActivitiesOrRecentTasks() || proc.hasClientActivities()
-                || proc.treatLikeActivity) {
-            pw.print(" act:");
-            boolean printed = false;
-            if (proc.hasActivities()) {
-                pw.print("activities");
-                printed = true;
-            }
-            if (proc.hasRecentTasks()) {
-                if (printed) {
-                    pw.print("|");
-                }
-                pw.print("recents");
-                printed = true;
-            }
-            if (proc.hasClientActivities()) {
-                if (printed) {
-                    pw.print("|");
-                }
-                pw.print("client");
-                printed = true;
-            }
-            if (proc.treatLikeActivity) {
-                if (printed) {
-                    pw.print("|");
-                }
-                pw.print("treated");
-            }
-        }
-        pw.println();
-    }
-
-    // TODO: Move to ProcessList?
-    boolean dumpLruLocked(PrintWriter pw, String dumpPackage, String prefix) {
-        final int N = mProcessList.mLruProcesses.size();
-        final String innerPrefix;
-        if (prefix == null) {
-            pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity lru)");
-            innerPrefix = "  ";
-        } else {
-            boolean haveAny = false;
-            for (int i = N - 1; i >= 0; i--) {
-                final ProcessRecord r = mProcessList.mLruProcesses.get(i);
-                if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                    continue;
-                }
-                haveAny = true;
-                break;
-            }
-            if (!haveAny) {
-                return false;
-            }
-            pw.print(prefix);
-            pw.println("Raw LRU list (dumpsys activity lru):");
-            innerPrefix = prefix + "  ";
-        }
-        int i;
-        boolean first = true;
-        for (i = N - 1; i >= mProcessList.mLruProcessActivityStart; i--) {
-            final ProcessRecord r = mProcessList.mLruProcesses.get(i);
-            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                continue;
-            }
-            if (first) {
-                pw.print(innerPrefix);
-                pw.println("Activities:");
-                first = false;
-            }
-            dumpLruEntryLocked(pw, i, r, innerPrefix);
-        }
-        first = true;
-        for (; i >= mProcessList.mLruProcessServiceStart; i--) {
-            final ProcessRecord r = mProcessList.mLruProcesses.get(i);
-            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                continue;
-            }
-            if (first) {
-                pw.print(innerPrefix);
-                pw.println("Services:");
-                first = false;
-            }
-            dumpLruEntryLocked(pw, i, r, innerPrefix);
-        }
-        first = true;
-        for (; i >= 0; i--) {
-            final ProcessRecord r = mProcessList.mLruProcesses.get(i);
-            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                continue;
-            }
-            if (first) {
-                pw.print(innerPrefix);
-                pw.println("Other:");
-                first = false;
-            }
-            dumpLruEntryLocked(pw, i, r, innerPrefix);
-        }
-        return true;
-    }
-
-    // TODO: Move to ProcessList?
     @GuardedBy("this")
-    void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
-        boolean needSep = false;
-        int numPers = 0;
-
-        pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
-
-        if (dumpAll || dumpPackage != null) {
-            final int NP = mProcessList.mProcessNames.getMap().size();
-            for (int ip=0; ip<NP; ip++) {
-                SparseArray<ProcessRecord> procs = mProcessList.mProcessNames.getMap().valueAt(ip);
-                final int NA = procs.size();
-                for (int ia=0; ia<NA; ia++) {
-                    ProcessRecord r = procs.valueAt(ia);
-                    if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                        continue;
-                    }
-                    if (!needSep) {
-                        pw.println("  All known processes:");
-                        needSep = true;
-                    }
-                    pw.print(r.isPersistent() ? "  *PERS*" : "  *APP*");
-                        pw.print(" UID "); pw.print(procs.keyAt(ia));
-                        pw.print(" "); pw.println(r);
-                    r.dump(pw, "    ");
-                    if (r.isPersistent()) {
-                        numPers++;
-                    }
-                }
-            }
-        }
-
-        if (mProcessList.mIsolatedProcesses.size() > 0) {
+    boolean dumpActiveInstruments(PrintWriter pw, String dumpPackage, boolean needSep) {
+        final int size = mActiveInstrumentation.size();
+        if (size > 0) {
             boolean printed = false;
-            for (int i=0; i<mProcessList.mIsolatedProcesses.size(); i++) {
-                ProcessRecord r = mProcessList.mIsolatedProcesses.valueAt(i);
-                if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) {
-                        pw.println();
-                    }
-                    pw.println("  Isolated process list (sorted by uid):");
-                    printed = true;
-                    needSep = true;
-                }
-                pw.print("    Isolated #"); pw.print(i); pw.print(": ");
-                pw.println(r);
-            }
-        }
-
-        if (mActiveInstrumentation.size() > 0) {
-            boolean printed = false;
-            for (int i=0; i<mActiveInstrumentation.size(); i++) {
+            for (int i = 0; i < size; i++) {
                 ActiveInstrumentation ai = mActiveInstrumentation.get(i);
                 if (dumpPackage != null && !ai.mClass.getPackageName().equals(dumpPackage)
                         && !ai.mTargetInfo.packageName.equals(dumpPackage)) {
@@ -9172,48 +9043,20 @@
                 ai.dump(pw, "      ");
             }
         }
+        return needSep;
+    }
 
-        if (dumpOomLocked(fd, pw, needSep, args, opti, dumpAll, dumpPackage, false)) {
-            needSep = true;
-        }
-
-        needSep = dumpProcessesToGc(pw, needSep, dumpPackage);
-
-        if (mProcessList.mActiveUids.size() > 0) {
-            needSep |= mProcessList.mActiveUids.dump(pw, dumpPackage, dumpAppId,
-                    "UID states:", needSep);
-        }
-
-        if (dumpAll) {
-            needSep |= mUidObserverController.dumpValidateUids(pw,
-                    dumpPackage, dumpAppId, "UID validation:", needSep);
-        }
-
-        if (needSep) {
-            pw.println();
-        }
-        if (dumpLruLocked(pw, dumpPackage, "  ")) {
-            needSep = true;
-        }
-
-        if (mProcessList.getLruSizeLocked() > 0) {
-            if (needSep) {
-                pw.println();
-            }
-            mProcessList.dumpLruListHeaderLocked(pw);
-            dumpProcessOomList(pw, this, mProcessList.mLruProcesses, "    ", "Proc", "PERS", false,
-                    dumpPackage);
-            needSep = true;
-        }
-
+    @GuardedBy({"this", "mProcLock"})
+    void dumpOtherProcessesInfoLSP(FileDescriptor fd, PrintWriter pw,
+            boolean dumpAll, String dumpPackage, int dumpAppId, int numPers, boolean needSep) {
         if (dumpAll || dumpPackage != null) {
             final SparseArray<ProcessRecord> pidToProcess = new SparseArray<>();
             synchronized (mPidsSelfLocked) {
                 boolean printed = false;
-                for (int i=0; i<mPidsSelfLocked.size(); i++) {
+                for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
                     ProcessRecord r = mPidsSelfLocked.valueAt(i);
-                    pidToProcess.put(r.pid, r);
-                    if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                    pidToProcess.put(r.getPid(), r);
+                    if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
                         continue;
                     }
                     if (!printed) {
@@ -9223,16 +9066,17 @@
                         printed = true;
                     }
                     pw.print("    PID #"); pw.print(mPidsSelfLocked.keyAt(i));
-                        pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
+                    pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
                 }
             }
 
             synchronized (sActiveProcessInfoSelfLocked) {
                 boolean printed = false;
-                for (int i = 0; i < sActiveProcessInfoSelfLocked.size(); i++) {
+                for (int i = 0, size = sActiveProcessInfoSelfLocked.size(); i < size; i++) {
                     ProcessInfo info = sActiveProcessInfoSelfLocked.valueAt(i);
                     ProcessRecord r = pidToProcess.get(sActiveProcessInfoSelfLocked.keyAt(i));
-                    if (r != null && dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                    if (r != null && dumpPackage != null
+                            && !r.getPkgList().containsKey(dumpPackage)) {
                         continue;
                     }
                     if (!printed) {
@@ -9261,11 +9105,10 @@
         if (mImportantProcesses.size() > 0) {
             synchronized (mPidsSelfLocked) {
                 boolean printed = false;
-                for (int i = 0; i< mImportantProcesses.size(); i++) {
-                    ProcessRecord r = mPidsSelfLocked.get(
-                            mImportantProcesses.valueAt(i).pid);
+                for (int i = 0, size = mImportantProcesses.size(); i < size; i++) {
+                    ProcessRecord r = mPidsSelfLocked.get(mImportantProcesses.valueAt(i).pid);
                     if (dumpPackage != null && (r == null
-                            || !r.pkgList.containsKey(dumpPackage))) {
+                            || !r.getPkgList().containsKey(dumpPackage))) {
                         continue;
                     }
                     if (!printed) {
@@ -9304,10 +9147,10 @@
                     "OnHold Norm", "OnHold PERS", dumpPackage);
         }
 
-        needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
+        needSep = mAppErrors.dumpLPr(fd, pw, needSep, dumpPackage);
 
         needSep = mAtmInternal.dumpForProcesses(fd, pw, dumpAll, dumpPackage, dumpAppId, needSep,
-                mAppProfiler.getTestPssModeLocked(), mWakefulness);
+                mAppProfiler.getTestPssMode(), mWakefulness.get());
 
         if (dumpAll && mProcessList.mPendingStarts.size() > 0) {
             if (needSep) pw.println();
@@ -9321,14 +9164,14 @@
         if (dumpAll) {
             mUidObserverController.dump(pw, dumpPackage);
 
-            pw.println("  mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist));
-            pw.println("  mDeviceIdleExceptIdleWhitelist="
-                    + Arrays.toString(mDeviceIdleExceptIdleWhitelist));
-            pw.println("  mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist));
-            if (mPendingTempWhitelist.size() > 0) {
-                pw.println("  mPendingTempWhitelist:");
-                for (int i = 0; i < mPendingTempWhitelist.size(); i++) {
-                    PendingTempWhitelist ptw = mPendingTempWhitelist.valueAt(i);
+            pw.println("  mDeviceIdleAllowlist=" + Arrays.toString(mDeviceIdleAllowlist));
+            pw.println("  mDeviceIdleExceptIdleAllowlist="
+                    + Arrays.toString(mDeviceIdleExceptIdleAllowlist));
+            pw.println("  mDeviceIdleTempAllowlist=" + Arrays.toString(mDeviceIdleTempAllowlist));
+            if (mPendingTempAllowlist.size() > 0) {
+                pw.println("  mPendingTempAllowlist:");
+                for (int i = 0, size = mPendingTempAllowlist.size(); i < size; i++) {
+                    PendingTempAllowlist ptw = mPendingTempAllowlist.valueAt(i);
                     pw.print("    ");
                     UserHandle.formatUid(pw, ptw.targetUid);
                     pw.print(": ");
@@ -9353,7 +9196,9 @@
                         + " mOrigWaitForDebugger=" + mOrigWaitForDebugger);
             }
         }
-        needSep = mAppProfiler.dumpMemWatchProcessesLocked(pw, needSep);
+        synchronized (mAppProfiler.mProfilerLock) {
+            needSep = mAppProfiler.dumpMemWatchProcessesLPf(pw, needSep);
+        }
         if (mTrackAllocationApp != null) {
             if (dumpPackage == null || dumpPackage.equals(mTrackAllocationApp)) {
                 if (needSep) {
@@ -9397,7 +9242,7 @@
                         TimeUtils.formatDuration(now, mLastIdleTime, pw);
                         pw.print(" mLowRamSinceLastIdle=");
                         TimeUtils.formatDuration(
-                                mAppProfiler.getLowRamTimeSinceIdleLocked(now), pw);
+                                mAppProfiler.getLowRamTimeSinceIdleLPr(now), pw);
                         pw.println();
 
                 pw.println();
@@ -9409,45 +9254,15 @@
         pw.println("  mForceBackgroundCheck=" + mForceBackgroundCheck);
     }
 
-    @GuardedBy("this")
-    private void dumpUsersLocked(PrintWriter pw) {
+    private void dumpUsers(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER USERS (dumpsys activity users)");
         mUserController.dump(pw);
     }
 
-    @GuardedBy("this")
-    void writeProcessesToProtoLocked(ProtoOutputStream proto, String dumpPackage) {
-        int numPers = 0;
-
-        final int NP = mProcessList.mProcessNames.getMap().size();
-        for (int ip=0; ip<NP; ip++) {
-            SparseArray<ProcessRecord> procs = mProcessList.mProcessNames.getMap().valueAt(ip);
-            final int NA = procs.size();
-            for (int ia = 0; ia<NA; ia++) {
-                ProcessRecord r = procs.valueAt(ia);
-                if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                    continue;
-                }
-                r.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.PROCS,
-                        mProcessList.mLruProcesses.indexOf(r)
-                );
-                if (r.isPersistent()) {
-                    numPers++;
-                }
-            }
-        }
-
-        for (int i=0; i<mProcessList.mIsolatedProcesses.size(); i++) {
-            ProcessRecord r = mProcessList.mIsolatedProcesses.valueAt(i);
-            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                continue;
-            }
-            r.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.ISOLATED_PROCS,
-                    mProcessList.mLruProcesses.indexOf(r)
-            );
-        }
-
-        for (int i=0; i<mActiveInstrumentation.size(); i++) {
+    @GuardedBy({"this", "mProcLock"})
+    void writeOtherProcessesInfoToProtoLSP(ProtoOutputStream proto, String dumpPackage,
+            int dumpAppId, int numPers) {
+        for (int i = 0, size = mActiveInstrumentation.size(); i < size; i++) {
             ActiveInstrumentation ai = mActiveInstrumentation.get(i);
             if (dumpPackage != null && !ai.mClass.getPackageName().equals(dumpPackage)
                     && !ai.mTargetInfo.packageName.equals(dumpPackage)) {
@@ -9457,32 +9272,14 @@
                     ActivityManagerServiceDumpProcessesProto.ACTIVE_INSTRUMENTATIONS);
         }
 
-        final int dumpAppId = getAppId(dumpPackage);
-        mProcessList.mActiveUids.dumpProto(proto, dumpPackage, dumpAppId,
-                ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
-
         mUidObserverController.dumpValidateUidsProto(proto, dumpPackage, dumpAppId,
                 ActivityManagerServiceDumpProcessesProto.VALIDATE_UIDS);
 
-        if (mProcessList.getLruSizeLocked() > 0) {
-            long lruToken = proto.start(ActivityManagerServiceDumpProcessesProto.LRU_PROCS);
-            int total = mProcessList.getLruSizeLocked();
-            proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.SIZE, total);
-            proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.NON_ACT_AT,
-                    total - mProcessList.mLruProcessActivityStart);
-            proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.NON_SVC_AT,
-                    total - mProcessList.mLruProcessServiceStart);
-            writeProcessOomListToProto(proto,
-                    ActivityManagerServiceDumpProcessesProto.LruProcesses.LIST, this,
-                    mProcessList.mLruProcesses,false, dumpPackage);
-            proto.end(lruToken);
-        }
-
         if (dumpPackage != null) {
             synchronized (mPidsSelfLocked) {
-                for (int i=0; i<mPidsSelfLocked.size(); i++) {
+                for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
                     ProcessRecord r = mPidsSelfLocked.valueAt(i);
-                    if (!r.pkgList.containsKey(dumpPackage)) {
+                    if (!r.getPkgList().containsKey(dumpPackage)) {
                         continue;
                     }
                     r.dumpDebug(proto,
@@ -9493,11 +9290,11 @@
 
         if (mImportantProcesses.size() > 0) {
             synchronized (mPidsSelfLocked) {
-                for (int i=0; i<mImportantProcesses.size(); i++) {
+                for (int i = 0, size = mImportantProcesses.size(); i < size; i++) {
                     ImportanceToken it = mImportantProcesses.valueAt(i);
                     ProcessRecord r = mPidsSelfLocked.get(it.pid);
                     if (dumpPackage != null && (r == null
-                            || !r.pkgList.containsKey(dumpPackage))) {
+                            || !r.getPkgList().containsKey(dumpPackage))) {
                         continue;
                     }
                     it.dumpDebug(proto,
@@ -9506,7 +9303,7 @@
             }
         }
 
-        for (int i=0; i<mPersistentStartingProcesses.size(); i++) {
+        for (int i = 0, size = mPersistentStartingProcesses.size(); i < size; i++) {
             ProcessRecord r = mPersistentStartingProcesses.get(i);
             if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
                 continue;
@@ -9515,7 +9312,7 @@
                     ActivityManagerServiceDumpProcessesProto.PERSISTENT_STARTING_PROCS);
         }
 
-        for (int i = 0; i < mProcessList.mRemovedProcesses.size(); i++) {
+        for (int i = 0, size = mProcessList.mRemovedProcesses.size(); i < size; i++) {
             ProcessRecord r = mProcessList.mRemovedProcesses.get(i);
             if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
                 continue;
@@ -9523,7 +9320,7 @@
             r.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.REMOVED_PROCS);
         }
 
-        for (int i=0; i<mProcessesOnHold.size(); i++) {
+        for (int i = 0, size = mProcessesOnHold.size(); i < size; i++) {
             ProcessRecord r = mProcessesOnHold.get(i);
             if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
                 continue;
@@ -9531,12 +9328,15 @@
             r.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.ON_HOLD_PROCS);
         }
 
-        writeProcessesToGcToProto(proto, ActivityManagerServiceDumpProcessesProto.GC_PROCS,
+        synchronized (mAppProfiler.mProfilerLock) {
+            mAppProfiler.writeProcessesToGcToProto(proto,
+                    ActivityManagerServiceDumpProcessesProto.GC_PROCS,
+                    dumpPackage);
+        }
+        mAppErrors.dumpDebugLPr(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS,
                 dumpPackage);
-        mAppErrors.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS,
-                dumpPackage);
-        mAtmInternal.writeProcessesToProto(proto, dumpPackage, mWakefulness,
-                mAppProfiler.getTestPssModeLocked());
+        mAtmInternal.writeProcessesToProto(proto, dumpPackage, mWakefulness.get(),
+                mAppProfiler.getTestPssMode());
 
         if (dumpPackage == null) {
             mUserController.dumpDebug(proto,
@@ -9545,17 +9345,17 @@
 
         mUidObserverController.dumpDebug(proto, dumpPackage);
 
-        for (int v : mDeviceIdleWhitelist) {
+        for (int v : mDeviceIdleAllowlist) {
             proto.write(ActivityManagerServiceDumpProcessesProto.DEVICE_IDLE_WHITELIST, v);
         }
 
-        for (int v : mDeviceIdleTempWhitelist) {
+        for (int v : mDeviceIdleTempAllowlist) {
             proto.write(ActivityManagerServiceDumpProcessesProto.DEVICE_IDLE_TEMP_WHITELIST, v);
         }
 
-        if (mPendingTempWhitelist.size() > 0) {
-            for (int i=0; i < mPendingTempWhitelist.size(); i++) {
-                mPendingTempWhitelist.valueAt(i).dumpDebug(proto,
+        if (mPendingTempAllowlist.size() > 0) {
+            for (int i = 0, size = mPendingTempAllowlist.size(); i < size; i++) {
+                mPendingTempAllowlist.valueAt(i).dumpDebug(proto,
                         ActivityManagerServiceDumpProcessesProto.PENDING_TEMP_WHITELIST);
             }
         }
@@ -9573,7 +9373,9 @@
             }
         }
 
-        mAppProfiler.writeMemWatchProcessToProtoLocked(proto);
+        synchronized (mAppProfiler.mProfilerLock) {
+            mAppProfiler.writeMemWatchProcessToProtoLPf(proto);
+        }
 
         if (mTrackAllocationApp != null) {
             if (dumpPackage == null || dumpPackage.equals(mTrackAllocationApp)) {
@@ -9604,117 +9406,10 @@
             long now = SystemClock.uptimeMillis();
             ProtoUtils.toDuration(proto, ActivityManagerServiceDumpProcessesProto.LAST_IDLE_TIME, mLastIdleTime, now);
             proto.write(ActivityManagerServiceDumpProcessesProto.LOW_RAM_SINCE_LAST_IDLE_MS,
-                    mAppProfiler.getLowRamTimeSinceIdleLocked(now));
+                    mAppProfiler.getLowRamTimeSinceIdleLPr(now));
         }
     }
 
-    void writeProcessesToGcToProto(ProtoOutputStream proto, long fieldId, String dumpPackage) {
-        if (mProcessesToGc.size() > 0) {
-            long now = SystemClock.uptimeMillis();
-            for (int i=0; i<mProcessesToGc.size(); i++) {
-                ProcessRecord r = mProcessesToGc.get(i);
-                if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
-                    continue;
-                }
-                final long token = proto.start(fieldId);
-                r.dumpDebug(proto, ProcessToGcProto.PROC);
-                proto.write(ProcessToGcProto.REPORT_LOW_MEMORY, r.reportLowMemory);
-                proto.write(ProcessToGcProto.NOW_UPTIME_MS, now);
-                proto.write(ProcessToGcProto.LAST_GCED_MS, r.lastRequestedGc);
-                proto.write(ProcessToGcProto.LAST_LOW_MEMORY_MS, r.lastLowMemory);
-                proto.end(token);
-            }
-        }
-    }
-
-    boolean dumpProcessesToGc(PrintWriter pw, boolean needSep, String dumpPackage) {
-        if (mProcessesToGc.size() > 0) {
-            boolean printed = false;
-            long now = SystemClock.uptimeMillis();
-            for (int i=0; i<mProcessesToGc.size(); i++) {
-                ProcessRecord proc = mProcessesToGc.get(i);
-                if (dumpPackage != null && !dumpPackage.equals(proc.info.packageName)) {
-                    continue;
-                }
-                if (!printed) {
-                    if (needSep) pw.println();
-                    needSep = true;
-                    pw.println("  Processes that are waiting to GC:");
-                    printed = true;
-                }
-                pw.print("    Process "); pw.println(proc);
-                pw.print("      lowMem="); pw.print(proc.reportLowMemory);
-                        pw.print(", last gced=");
-                        pw.print(now-proc.lastRequestedGc);
-                        pw.print(" ms ago, last lowMem=");
-                        pw.print(now-proc.lastLowMemory);
-                        pw.println(" ms ago");
-
-            }
-        }
-        return needSep;
-    }
-
-    void printOomLevel(PrintWriter pw, String name, int adj) {
-        pw.print("    ");
-        if (adj >= 0) {
-            pw.print(' ');
-            if (adj < 10) pw.print(' ');
-        } else {
-            if (adj > -10) pw.print(' ');
-        }
-        pw.print(adj);
-        pw.print(": ");
-        pw.print(name);
-        pw.print(" (");
-        pw.print(stringifySize(mProcessList.getMemLevel(adj), 1024));
-        pw.println(")");
-    }
-
-    boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String[] args,
-            int opti, boolean dumpAll, String dumpPackage, boolean inclGc) {
-        if (mProcessList.getLruSizeLocked() > 0) {
-            if (needSep) pw.println();
-            needSep = true;
-            pw.println("  OOM levels:");
-            printOomLevel(pw, "SYSTEM_ADJ", ProcessList.SYSTEM_ADJ);
-            printOomLevel(pw, "PERSISTENT_PROC_ADJ", ProcessList.PERSISTENT_PROC_ADJ);
-            printOomLevel(pw, "PERSISTENT_SERVICE_ADJ", ProcessList.PERSISTENT_SERVICE_ADJ);
-            printOomLevel(pw, "FOREGROUND_APP_ADJ", ProcessList.FOREGROUND_APP_ADJ);
-            printOomLevel(pw, "VISIBLE_APP_ADJ", ProcessList.VISIBLE_APP_ADJ);
-            printOomLevel(pw, "PERCEPTIBLE_APP_ADJ", ProcessList.PERCEPTIBLE_APP_ADJ);
-            printOomLevel(pw, "PERCEPTIBLE_LOW_APP_ADJ", ProcessList.PERCEPTIBLE_LOW_APP_ADJ);
-            printOomLevel(pw, "BACKUP_APP_ADJ", ProcessList.BACKUP_APP_ADJ);
-            printOomLevel(pw, "HEAVY_WEIGHT_APP_ADJ", ProcessList.HEAVY_WEIGHT_APP_ADJ);
-            printOomLevel(pw, "SERVICE_ADJ", ProcessList.SERVICE_ADJ);
-            printOomLevel(pw, "HOME_APP_ADJ", ProcessList.HOME_APP_ADJ);
-            printOomLevel(pw, "PREVIOUS_APP_ADJ", ProcessList.PREVIOUS_APP_ADJ);
-            printOomLevel(pw, "SERVICE_B_ADJ", ProcessList.SERVICE_B_ADJ);
-            printOomLevel(pw, "CACHED_APP_MIN_ADJ", ProcessList.CACHED_APP_MIN_ADJ);
-            printOomLevel(pw, "CACHED_APP_MAX_ADJ", ProcessList.CACHED_APP_MAX_ADJ);
-
-            if (needSep) pw.println();
-            pw.print("  Process OOM control ("); pw.print(mProcessList.getLruSizeLocked());
-                    pw.print(" total, non-act at ");
-                    pw.print(mProcessList.getLruSizeLocked()
-                            - mProcessList.mLruProcessActivityStart);
-                    pw.print(", non-svc at ");
-                    pw.print(mProcessList.getLruSizeLocked()
-                            - mProcessList.mLruProcessServiceStart);
-                    pw.println("):");
-            dumpProcessOomList(pw, this, mProcessList.mLruProcesses, "    ", "Proc", "PERS", true,
-                    dumpPackage);
-            needSep = true;
-        }
-
-        dumpProcessesToGc(pw, needSep, dumpPackage);
-
-        pw.println();
-        mAtmInternal.dumpForOom(pw);
-
-        return true;
-    }
-
     private boolean reportLmkKillAtOrBelow(PrintWriter pw, int oom_adj) {
         Integer cnt = ProcessList.getLmkdKillCount(0, oom_adj);
         if (cnt != null) {
@@ -10050,8 +9745,7 @@
         }
     }
 
-    @GuardedBy("this")
-    void dumpPermissionsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+    void dumpPermissions(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
 
         pw.println("ACTIVITY MANAGER URI PERMISSIONS (dumpsys activity permissions)");
@@ -10059,14 +9753,13 @@
         mUgmInternal.dump(pw, dumpAll, dumpPackage);
     }
 
-    private static final int dumpProcessList(PrintWriter pw,
+    private static int dumpProcessList(PrintWriter pw,
             ActivityManagerService service, List list,
             String prefix, String normalLabel, String persistentLabel,
             String dumpPackage) {
         int numPers = 0;
-        final int N = list.size()-1;
-        for (int i=N; i>=0; i--) {
-            ProcessRecord r = (ProcessRecord)list.get(i);
+        for (int i = list.size() - 1; i >= 0; i--) {
+            ProcessRecord r = (ProcessRecord) list.get(i);
             if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
                 continue;
             }
@@ -10080,274 +9773,10 @@
         return numPers;
     }
 
-    private static final ArrayList<Pair<ProcessRecord, Integer>>
-        sortProcessOomList(List<ProcessRecord> origList, String dumpPackage) {
-        ArrayList<Pair<ProcessRecord, Integer>> list
-                = new ArrayList<Pair<ProcessRecord, Integer>>(origList.size());
-        for (int i=0; i<origList.size(); i++) {
-            ProcessRecord r = origList.get(i);
-            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
-                continue;
-            }
-            list.add(new Pair<ProcessRecord, Integer>(origList.get(i), i));
-        }
-
-        Comparator<Pair<ProcessRecord, Integer>> comparator
-                = new Comparator<Pair<ProcessRecord, Integer>>() {
-            @Override
-            public int compare(Pair<ProcessRecord, Integer> object1,
-                    Pair<ProcessRecord, Integer> object2) {
-                if (object1.first.setAdj != object2.first.setAdj) {
-                    return object1.first.setAdj > object2.first.setAdj ? -1 : 1;
-                }
-                if (object1.first.setProcState != object2.first.setProcState) {
-                    return object1.first.setProcState > object2.first.setProcState ? -1 : 1;
-                }
-                if (object1.second.intValue() != object2.second.intValue()) {
-                    return object1.second.intValue() > object2.second.intValue() ? -1 : 1;
-                }
-                return 0;
-            }
-        };
-
-        Collections.sort(list, comparator);
-        return list;
-    }
-
-    private static final boolean writeProcessOomListToProto(ProtoOutputStream proto, long fieldId,
-            ActivityManagerService service, List<ProcessRecord> origList,
-            boolean inclDetails, String dumpPackage) {
-        ArrayList<Pair<ProcessRecord, Integer>> list = sortProcessOomList(origList, dumpPackage);
-        if (list.isEmpty()) return false;
-
-        final long curUptime = SystemClock.uptimeMillis();
-
-        for (int i = list.size() - 1; i >= 0; i--) {
-            ProcessRecord r = list.get(i).first;
-            long token = proto.start(fieldId);
-            String oomAdj = ProcessList.makeOomAdjString(r.setAdj, true);
-            proto.write(ProcessOomProto.PERSISTENT, r.isPersistent());
-            proto.write(ProcessOomProto.NUM, (origList.size()-1)-list.get(i).second);
-            proto.write(ProcessOomProto.OOM_ADJ, oomAdj);
-            int schedGroup = ProcessOomProto.SCHED_GROUP_UNKNOWN;
-            switch (r.setSchedGroup) {
-                case ProcessList.SCHED_GROUP_BACKGROUND:
-                    schedGroup = ProcessOomProto.SCHED_GROUP_BACKGROUND;
-                    break;
-                case ProcessList.SCHED_GROUP_DEFAULT:
-                    schedGroup = ProcessOomProto.SCHED_GROUP_DEFAULT;
-                    break;
-                case ProcessList.SCHED_GROUP_TOP_APP:
-                    schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP;
-                    break;
-                case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
-                    schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP_BOUND;
-                    break;
-            }
-            if (schedGroup != ProcessOomProto.SCHED_GROUP_UNKNOWN) {
-                proto.write(ProcessOomProto.SCHED_GROUP, schedGroup);
-            }
-            if (r.hasForegroundActivities()) {
-                proto.write(ProcessOomProto.ACTIVITIES, true);
-            } else if (r.hasForegroundServices()) {
-                proto.write(ProcessOomProto.SERVICES, true);
-            }
-            proto.write(ProcessOomProto.STATE,
-                    ProcessList.makeProcStateProtoEnum(r.getCurProcState()));
-            proto.write(ProcessOomProto.TRIM_MEMORY_LEVEL, r.trimMemoryLevel);
-            r.dumpDebug(proto, ProcessOomProto.PROC);
-            proto.write(ProcessOomProto.ADJ_TYPE, r.adjType);
-            if (r.adjSource != null || r.adjTarget != null) {
-                if (r.adjTarget instanceof  ComponentName) {
-                    ComponentName cn = (ComponentName) r.adjTarget;
-                    cn.dumpDebug(proto, ProcessOomProto.ADJ_TARGET_COMPONENT_NAME);
-                } else if (r.adjTarget != null) {
-                    proto.write(ProcessOomProto.ADJ_TARGET_OBJECT, r.adjTarget.toString());
-                }
-                if (r.adjSource instanceof ProcessRecord) {
-                    ProcessRecord p = (ProcessRecord) r.adjSource;
-                    p.dumpDebug(proto, ProcessOomProto.ADJ_SOURCE_PROC);
-                } else if (r.adjSource != null) {
-                    proto.write(ProcessOomProto.ADJ_SOURCE_OBJECT, r.adjSource.toString());
-                }
-            }
-            if (inclDetails) {
-                long detailToken = proto.start(ProcessOomProto.DETAIL);
-                proto.write(ProcessOomProto.Detail.MAX_ADJ, r.maxAdj);
-                proto.write(ProcessOomProto.Detail.CUR_RAW_ADJ, r.getCurRawAdj());
-                proto.write(ProcessOomProto.Detail.SET_RAW_ADJ, r.setRawAdj);
-                proto.write(ProcessOomProto.Detail.CUR_ADJ, r.curAdj);
-                proto.write(ProcessOomProto.Detail.SET_ADJ, r.setAdj);
-                proto.write(ProcessOomProto.Detail.CURRENT_STATE,
-                        ProcessList.makeProcStateProtoEnum(r.getCurProcState()));
-                proto.write(ProcessOomProto.Detail.SET_STATE,
-                        ProcessList.makeProcStateProtoEnum(r.setProcState));
-                proto.write(ProcessOomProto.Detail.LAST_PSS, DebugUtils.sizeValueToString(
-                        r.lastPss*1024, new StringBuilder()));
-                proto.write(ProcessOomProto.Detail.LAST_SWAP_PSS, DebugUtils.sizeValueToString(
-                        r.lastSwapPss*1024, new StringBuilder()));
-                proto.write(ProcessOomProto.Detail.LAST_CACHED_PSS, DebugUtils.sizeValueToString(
-                        r.lastCachedPss*1024, new StringBuilder()));
-                proto.write(ProcessOomProto.Detail.CACHED, r.isCached());
-                proto.write(ProcessOomProto.Detail.EMPTY, r.empty);
-                proto.write(ProcessOomProto.Detail.HAS_ABOVE_CLIENT, r.hasAboveClient);
-
-                if (r.setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
-                    if (r.lastCpuTime != 0) {
-                        long uptimeSince = curUptime - service.mLastPowerCheckUptime;
-                        long timeUsed = r.curCpuTime - r.lastCpuTime;
-                        long cpuTimeToken = proto.start(ProcessOomProto.Detail.SERVICE_RUN_TIME);
-                        proto.write(ProcessOomProto.Detail.CpuRunTime.OVER_MS, uptimeSince);
-                        proto.write(ProcessOomProto.Detail.CpuRunTime.USED_MS, timeUsed);
-                        proto.write(ProcessOomProto.Detail.CpuRunTime.ULTILIZATION,
-                                (100.0*timeUsed)/uptimeSince);
-                        proto.end(cpuTimeToken);
-                    }
-                }
-                proto.end(detailToken);
-            }
-            proto.end(token);
-        }
-
-        return true;
-    }
-
-    private static final boolean dumpProcessOomList(PrintWriter pw,
-            ActivityManagerService service, List<ProcessRecord> origList,
-            String prefix, String normalLabel, String persistentLabel,
-            boolean inclDetails, String dumpPackage) {
-
-        ArrayList<Pair<ProcessRecord, Integer>> list = sortProcessOomList(origList, dumpPackage);
-        if (list.isEmpty()) return false;
-
-        final long curUptime = SystemClock.uptimeMillis();
-        final long uptimeSince = curUptime - service.mLastPowerCheckUptime;
-
-        for (int i=list.size()-1; i>=0; i--) {
-            ProcessRecord r = list.get(i).first;
-            String oomAdj = ProcessList.makeOomAdjString(r.setAdj, false);
-            char schedGroup;
-            switch (r.setSchedGroup) {
-                case ProcessList.SCHED_GROUP_BACKGROUND:
-                    schedGroup = 'b';
-                    break;
-                case ProcessList.SCHED_GROUP_DEFAULT:
-                    schedGroup = 'F';
-                    break;
-                case ProcessList.SCHED_GROUP_TOP_APP:
-                    schedGroup = 'T';
-                    break;
-                case ProcessList.SCHED_GROUP_RESTRICTED:
-                    schedGroup = 'R';
-                    break;
-                case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
-                    schedGroup = 'B';
-                    break;
-                default:
-                    schedGroup = '?';
-                    break;
-            }
-            char foreground;
-            if (r.hasForegroundActivities()) {
-                foreground = 'A';
-            } else if (r.hasForegroundServices()) {
-                foreground = 'S';
-            } else {
-                foreground = ' ';
-            }
-            String procState = ProcessList.makeProcStateString(r.getCurProcState());
-            pw.print(prefix);
-            pw.print(r.isPersistent() ? persistentLabel : normalLabel);
-            pw.print(" #");
-            int num = (origList.size()-1)-list.get(i).second;
-            if (num < 10) pw.print(' ');
-            pw.print(num);
-            pw.print(": ");
-            pw.print(oomAdj);
-            pw.print(' ');
-            pw.print(schedGroup);
-            pw.print('/');
-            pw.print(foreground);
-            pw.print('/');
-            pw.print(procState);
-            pw.print(' ');
-            ActivityManager.printCapabilitiesSummary(pw, r.curCapability);
-            pw.print(' ');
-            pw.print(" t:");
-            if (r.trimMemoryLevel < 10) pw.print(' ');
-            pw.print(r.trimMemoryLevel);
-            pw.print(' ');
-            pw.print(r.toShortString());
-            pw.print(" (");
-            pw.print(r.adjType);
-            pw.println(')');
-            if (r.adjSource != null || r.adjTarget != null) {
-                pw.print(prefix);
-                pw.print("    ");
-                if (r.adjTarget instanceof ComponentName) {
-                    pw.print(((ComponentName)r.adjTarget).flattenToShortString());
-                } else if (r.adjTarget != null) {
-                    pw.print(r.adjTarget.toString());
-                } else {
-                    pw.print("{null}");
-                }
-                pw.print("<=");
-                if (r.adjSource instanceof ProcessRecord) {
-                    pw.print("Proc{");
-                    pw.print(((ProcessRecord)r.adjSource).toShortString());
-                    pw.println("}");
-                } else if (r.adjSource != null) {
-                    pw.println(r.adjSource.toString());
-                } else {
-                    pw.println("{null}");
-                }
-            }
-            if (inclDetails) {
-                pw.print(prefix);
-                pw.print("    ");
-                pw.print("oom: max="); pw.print(r.maxAdj);
-                pw.print(" curRaw="); pw.print(r.getCurRawAdj());
-                pw.print(" setRaw="); pw.print(r.setRawAdj);
-                pw.print(" cur="); pw.print(r.curAdj);
-                pw.print(" set="); pw.println(r.setAdj);
-                pw.print(prefix);
-                pw.print("    ");
-                pw.print("state: cur="); pw.print(
-                        ProcessList.makeProcStateString(r.getCurProcState()));
-                pw.print(" set="); pw.print(ProcessList.makeProcStateString(r.setProcState));
-                pw.print(" lastPss="); DebugUtils.printSizeValue(pw, r.lastPss*1024);
-                pw.print(" lastSwapPss="); DebugUtils.printSizeValue(pw, r.lastSwapPss*1024);
-                pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, r.lastCachedPss*1024);
-                pw.println();
-                pw.print(prefix);
-                pw.print("    ");
-                pw.print("cached="); pw.print(r.isCached());
-                pw.print(" empty="); pw.print(r.empty);
-                pw.print(" hasAboveClient="); pw.println(r.hasAboveClient);
-
-                if (r.setProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
-                    if (r.lastCpuTime != 0) {
-                        long timeUsed = r.curCpuTime - r.lastCpuTime;
-                        pw.print(prefix);
-                        pw.print("    ");
-                        pw.print("run cpu over ");
-                        TimeUtils.formatDuration(uptimeSince, pw);
-                        pw.print(" used ");
-                        TimeUtils.formatDuration(timeUsed, pw);
-                        pw.print(" (");
-                        pw.print((timeUsed*100)/uptimeSince);
-                        pw.println("%)");
-                    }
-                }
-            }
-        }
-        return true;
-    }
-
     ArrayList<ProcessRecord> collectProcesses(PrintWriter pw, int start, boolean allPkgs,
             String[] args) {
-        synchronized (this) {
-            return mProcessList.collectProcessesLocked(start, allPkgs, args);
+        synchronized (mProcLock) {
+            return mProcessList.collectProcessesLOSP(start, allPkgs, args);
         }
     }
 
@@ -10366,13 +9795,15 @@
 
         for (int i = procs.size() - 1 ; i >= 0 ; i--) {
             ProcessRecord r = procs.get(i);
-            if (r.thread != null) {
-                pw.println("\n** Graphics info for pid " + r.pid + " [" + r.processName + "] **");
+            final int pid = r.getPid();
+            final IApplicationThread thread = r.getThread();
+            if (thread != null) {
+                pw.println("\n** Graphics info for pid " + pid + " [" + r.processName + "] **");
                 pw.flush();
                 try {
                     TransferPipe tp = new TransferPipe();
                     try {
-                        r.thread.dumpGfxInfo(tp.getWriteFd(), args);
+                        thread.dumpGfxInfo(tp.getWriteFd(), args);
                         tp.go(fd);
                     } finally {
                         tp.kill();
@@ -10399,13 +9830,15 @@
 
         for (int i = procs.size() - 1; i >= 0; i--) {
             ProcessRecord r = procs.get(i);
-            if (r.thread != null) {
-                pw.println("\n\n** Cache info for pid " + r.pid + " [" + r.processName + "] **");
+            final int pid = r.getPid();
+            final IApplicationThread thread = r.getThread();
+            if (thread != null) {
+                pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **");
                 pw.flush();
                 try {
                     TransferPipe tp = new TransferPipe();
                     try {
-                        r.thread.dumpCacheInfo(tp.getWriteFd(), args);
+                        thread.dumpCacheInfo(tp.getWriteFd(), args);
                         tp.go(fd);
                     } finally {
                         tp.kill();
@@ -10432,13 +9865,15 @@
 
         for (int i = procs.size() - 1 ; i >= 0 ; i--) {
             ProcessRecord r = procs.get(i);
-            if (r.thread != null) {
-                pw.println("\n** Database info for pid " + r.pid + " [" + r.processName + "] **");
+            final int pid = r.getPid();
+            final IApplicationThread thread = r.getThread();
+            if (thread != null) {
+                pw.println("\n** Database info for pid " + pid + " [" + r.processName + "] **");
                 pw.flush();
                 try {
                     TransferPipe tp = new TransferPipe();
                     try {
-                        r.thread.dumpDbInfo(tp.getWriteFd(), args);
+                        thread.dumpDbInfo(tp.getWriteFd(), args);
                         tp.go(fd);
                     } finally {
                         tp.kill();
@@ -10637,12 +10072,12 @@
         }
     }
 
-    private static final int KSM_SHARED = 0;
-    private static final int KSM_SHARING = 1;
-    private static final int KSM_UNSHARED = 2;
-    private static final int KSM_VOLATILE = 3;
+    static final int KSM_SHARED = 0;
+    static final int KSM_SHARING = 1;
+    static final int KSM_UNSHARED = 2;
+    static final int KSM_VOLATILE = 3;
 
-    private final long[] getKsmInfo() {
+    static final long[] getKsmInfo() {
         long[] longOut = new long[4];
         final int[] SINGLE_LONG_FORMAT = new int[] {
             PROC_SPACE_TERM| PROC_OUT_LONG
@@ -10666,7 +10101,7 @@
         return longOut;
     }
 
-    private static String stringifySize(long size, int order) {
+    static String stringifySize(long size, int order) {
         Locale locale = Locale.US;
         switch (order) {
             case 1:
@@ -10682,7 +10117,7 @@
         }
     }
 
-    private static String stringifyKBSize(long size) {
+    static String stringifyKBSize(long size) {
         return stringifySize(size * 1024, 1024);
     }
 
@@ -10901,10 +10336,10 @@
             final int pid;
             final int oomAdj;
             final boolean hasActivities;
-            synchronized (this) {
-                thread = r.thread;
-                pid = r.pid;
-                oomAdj = r.getSetAdjWithServices();
+            synchronized (mProcLock) {
+                thread = r.getThread();
+                pid = r.getPid();
+                oomAdj = r.mState.getSetAdjWithServices();
                 hasActivities = r.hasActivities();
             }
             if (thread != null) {
@@ -10975,15 +10410,12 @@
                 final long myTotalRss = mi.getTotalRss();
                 final long myTotalSwapPss = mi.getTotalSwappedOutPss();
 
-                synchronized (this) {
-                    if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
+                synchronized (mProcLock) {
+                    if (r.getThread() != null && oomAdj == r.mState.getSetAdjWithServices()) {
                         // Record this for posterity if the process has been stable.
-                        synchronized (mProcessStats.mLock) {
-                            r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
-                                    reportType, endTime - startTime, r.pkgList.mPkgList);
-                        }
-                        for (int ipkg = r.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                            ProcessStats.ProcessStateHolder holder = r.pkgList.valueAt(ipkg);
+                        r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true,
+                                reportType, endTime - startTime);
+                        r.getPkgList().forEachPackageProcessStats(holder -> {
                             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
                                     r.info.uid,
                                     holder.state.getName(),
@@ -10991,7 +10423,7 @@
                                     myTotalPss, myTotalUss, myTotalRss, reportType,
                                     endTime-startTime,
                                     holder.appVersion);
-                        }
+                        });
                     }
                 }
 
@@ -11489,10 +10921,10 @@
             final int pid;
             final int oomAdj;
             final boolean hasActivities;
-            synchronized (this) {
-                thread = r.thread;
-                pid = r.pid;
-                oomAdj = r.getSetAdjWithServices();
+            synchronized (mProcLock) {
+                thread = r.getThread();
+                pid = r.getPid();
+                oomAdj = r.mState.getSetAdjWithServices();
                 hasActivities = r.hasActivities();
             }
             if (thread == null) {
@@ -11558,22 +10990,19 @@
             final long myTotalRss = mi.getTotalRss();
             final long myTotalSwapPss = mi.getTotalSwappedOutPss();
 
-            synchronized (this) {
-                if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
+            synchronized (mProcLock) {
+                if (r.getThread() != null && oomAdj == r.mState.getSetAdjWithServices()) {
                     // Record this for posterity if the process has been stable.
-                    synchronized (mProcessStats.mLock) {
-                        r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true,
-                                reportType, endTime - startTime, r.pkgList.mPkgList);
-                    }
-                    for (int ipkg = r.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                        ProcessStats.ProcessStateHolder holder = r.pkgList.valueAt(ipkg);
+                    r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true,
+                                reportType, endTime - startTime);
+                    r.getPkgList().forEachPackageProcessStats(holder -> {
                         FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
                                 r.info.uid,
                                 holder.state.getName(),
                                 holder.state.getPackage(),
                                 myTotalPss, myTotalUss, myTotalRss, reportType, endTime-startTime,
                                 holder.appVersion);
-                    }
+                    });
                 }
             }
 
@@ -11844,7 +11273,7 @@
         proto.flush();
     }
 
-    private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss,
+    static void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss,
             long memtrack, String name) {
         sb.append("  ");
         sb.append(ProcessList.makeOomAdjString(oomAdj, false));
@@ -11861,7 +11290,7 @@
         }
     }
 
-    private void appendMemInfo(StringBuilder sb, ProcessMemInfo mi) {
+    static void appendMemInfo(StringBuilder sb, ProcessMemInfo mi) {
         appendBasicMemEntry(sb, mi.oomAdj, mi.procState, mi.pss, mi.memtrack, mi.name);
         sb.append(" (pid ");
         sb.append(mi.pid);
@@ -11875,283 +11304,6 @@
         }
     }
 
-    void reportMemUsage(ArrayList<ProcessMemInfo> memInfos) {
-        final SparseArray<ProcessMemInfo> infoMap = new SparseArray<>(memInfos.size());
-        for (int i=0, N=memInfos.size(); i<N; i++) {
-            ProcessMemInfo mi = memInfos.get(i);
-            infoMap.put(mi.pid, mi);
-        }
-        updateCpuStatsNow();
-        long[] memtrackTmp = new long[1];
-        long[] swaptrackTmp = new long[2];
-        // Get a list of Stats that have vsize > 0
-        final List<ProcessCpuTracker.Stats> stats = mAppProfiler.getCpuStats(st -> st.vsize > 0);
-        final int statsCount = stats.size();
-        for (int i = 0; i < statsCount; i++) {
-            ProcessCpuTracker.Stats st = stats.get(i);
-            long pss = Debug.getPss(st.pid, swaptrackTmp, memtrackTmp);
-            if (pss > 0) {
-                if (infoMap.indexOfKey(st.pid) < 0) {
-                    ProcessMemInfo mi = new ProcessMemInfo(st.name, st.pid,
-                            ProcessList.NATIVE_ADJ, -1, "native", null);
-                    mi.pss = pss;
-                    mi.swapPss = swaptrackTmp[1];
-                    mi.memtrack = memtrackTmp[0];
-                    memInfos.add(mi);
-                }
-            }
-        }
-
-        long totalPss = 0;
-        long totalSwapPss = 0;
-        long totalMemtrack = 0;
-        for (int i=0, N=memInfos.size(); i<N; i++) {
-            ProcessMemInfo mi = memInfos.get(i);
-            if (mi.pss == 0) {
-                mi.pss = Debug.getPss(mi.pid, swaptrackTmp, memtrackTmp);
-                mi.swapPss = swaptrackTmp[1];
-                mi.memtrack = memtrackTmp[0];
-            }
-            totalPss += mi.pss;
-            totalSwapPss += mi.swapPss;
-            totalMemtrack += mi.memtrack;
-        }
-        Collections.sort(memInfos, new Comparator<ProcessMemInfo>() {
-            @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) {
-                if (lhs.oomAdj != rhs.oomAdj) {
-                    return lhs.oomAdj < rhs.oomAdj ? -1 : 1;
-                }
-                if (lhs.pss != rhs.pss) {
-                    return lhs.pss < rhs.pss ? 1 : -1;
-                }
-                return 0;
-            }
-        });
-
-        StringBuilder tag = new StringBuilder(128);
-        StringBuilder stack = new StringBuilder(128);
-        tag.append("Low on memory -- ");
-        appendMemBucket(tag, totalPss, "total", false);
-        appendMemBucket(stack, totalPss, "total", true);
-
-        StringBuilder fullNativeBuilder = new StringBuilder(1024);
-        StringBuilder shortNativeBuilder = new StringBuilder(1024);
-        StringBuilder fullJavaBuilder = new StringBuilder(1024);
-
-        boolean firstLine = true;
-        int lastOomAdj = Integer.MIN_VALUE;
-        long extraNativeRam = 0;
-        long extraNativeMemtrack = 0;
-        long cachedPss = 0;
-        for (int i=0, N=memInfos.size(); i<N; i++) {
-            ProcessMemInfo mi = memInfos.get(i);
-
-            if (mi.oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
-                cachedPss += mi.pss;
-            }
-
-            if (mi.oomAdj != ProcessList.NATIVE_ADJ
-                    && (mi.oomAdj < ProcessList.SERVICE_ADJ
-                            || mi.oomAdj == ProcessList.HOME_APP_ADJ
-                            || mi.oomAdj == ProcessList.PREVIOUS_APP_ADJ)) {
-                if (lastOomAdj != mi.oomAdj) {
-                    lastOomAdj = mi.oomAdj;
-                    if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) {
-                        tag.append(" / ");
-                    }
-                    if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ) {
-                        if (firstLine) {
-                            stack.append(":");
-                            firstLine = false;
-                        }
-                        stack.append("\n\t at ");
-                    } else {
-                        stack.append("$");
-                    }
-                } else {
-                    tag.append(" ");
-                    stack.append("$");
-                }
-                if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) {
-                    appendMemBucket(tag, mi.pss, mi.name, false);
-                }
-                appendMemBucket(stack, mi.pss, mi.name, true);
-                if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ
-                        && ((i+1) >= N || memInfos.get(i+1).oomAdj != lastOomAdj)) {
-                    stack.append("(");
-                    for (int k=0; k<DUMP_MEM_OOM_ADJ.length; k++) {
-                        if (DUMP_MEM_OOM_ADJ[k] == mi.oomAdj) {
-                            stack.append(DUMP_MEM_OOM_LABEL[k]);
-                            stack.append(":");
-                            stack.append(DUMP_MEM_OOM_ADJ[k]);
-                        }
-                    }
-                    stack.append(")");
-                }
-            }
-
-            appendMemInfo(fullNativeBuilder, mi);
-            if (mi.oomAdj == ProcessList.NATIVE_ADJ) {
-                // The short form only has native processes that are >= 512K.
-                if (mi.pss >= 512) {
-                    appendMemInfo(shortNativeBuilder, mi);
-                } else {
-                    extraNativeRam += mi.pss;
-                    extraNativeMemtrack += mi.memtrack;
-                }
-            } else {
-                // Short form has all other details, but if we have collected RAM
-                // from smaller native processes let's dump a summary of that.
-                if (extraNativeRam > 0) {
-                    appendBasicMemEntry(shortNativeBuilder, ProcessList.NATIVE_ADJ,
-                            -1, extraNativeRam, extraNativeMemtrack, "(Other native)");
-                    shortNativeBuilder.append('\n');
-                    extraNativeRam = 0;
-                }
-                appendMemInfo(fullJavaBuilder, mi);
-            }
-        }
-
-        fullJavaBuilder.append("           ");
-        ProcessList.appendRamKb(fullJavaBuilder, totalPss);
-        fullJavaBuilder.append(": TOTAL");
-        if (totalMemtrack > 0) {
-            fullJavaBuilder.append(" (");
-            fullJavaBuilder.append(stringifyKBSize(totalMemtrack));
-            fullJavaBuilder.append(" memtrack)");
-        } else {
-        }
-        fullJavaBuilder.append("\n");
-
-        MemInfoReader memInfo = new MemInfoReader();
-        memInfo.readMemInfo();
-        final long[] infos = memInfo.getRawInfo();
-
-        StringBuilder memInfoBuilder = new StringBuilder(1024);
-        Debug.getMemInfo(infos);
-        memInfoBuilder.append("  MemInfo: ");
-        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SLAB])).append(" slab, ");
-        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SHMEM])).append(" shmem, ");
-        memInfoBuilder.append(stringifyKBSize(
-                                  infos[Debug.MEMINFO_VM_ALLOC_USED])).append(" vm alloc, ");
-        memInfoBuilder.append(stringifyKBSize(
-                                  infos[Debug.MEMINFO_PAGE_TABLES])).append(" page tables ");
-        memInfoBuilder.append(stringifyKBSize(
-                                  infos[Debug.MEMINFO_KERNEL_STACK])).append(" kernel stack\n");
-        memInfoBuilder.append("           ");
-        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_BUFFERS])).append(" buffers, ");
-        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_CACHED])).append(" cached, ");
-        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_MAPPED])).append(" mapped, ");
-        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_FREE])).append(" free\n");
-        if (infos[Debug.MEMINFO_ZRAM_TOTAL] != 0) {
-            memInfoBuilder.append("  ZRAM: ");
-            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_ZRAM_TOTAL]));
-            memInfoBuilder.append(" RAM, ");
-            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SWAP_TOTAL]));
-            memInfoBuilder.append(" swap total, ");
-            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SWAP_FREE]));
-            memInfoBuilder.append(" swap free\n");
-        }
-        final long[] ksm = getKsmInfo();
-        if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0
-                || ksm[KSM_VOLATILE] != 0) {
-            memInfoBuilder.append("  KSM: ");
-            memInfoBuilder.append(stringifyKBSize(ksm[KSM_SHARING]));
-            memInfoBuilder.append(" saved from shared ");
-            memInfoBuilder.append(stringifyKBSize(ksm[KSM_SHARED]));
-            memInfoBuilder.append("\n       ");
-            memInfoBuilder.append(stringifyKBSize(ksm[KSM_UNSHARED]));
-            memInfoBuilder.append(" unshared; ");
-            memInfoBuilder.append(stringifyKBSize(ksm[KSM_VOLATILE]));
-            memInfoBuilder.append(" volatile\n");
-        }
-        memInfoBuilder.append("  Free RAM: ");
-        memInfoBuilder.append(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb()
-                + memInfo.getFreeSizeKb()));
-        memInfoBuilder.append("\n");
-        long kernelUsed = memInfo.getKernelUsedSizeKb();
-        final long ionHeap = Debug.getIonHeapsSizeKb();
-        final long ionPool = Debug.getIonPoolsSizeKb();
-        if (ionHeap >= 0 && ionPool >= 0) {
-            final long ionMapped = Debug.getIonMappedSizeKb();
-            final long ionUnmapped = ionHeap - ionMapped;
-            memInfoBuilder.append("       ION: ");
-            memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
-            memInfoBuilder.append("\n");
-            // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
-            // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
-            kernelUsed += ionHeap;
-        }
-        final long gpuUsage = Debug.getGpuTotalUsageKb();
-        if (gpuUsage >= 0) {
-            memInfoBuilder.append("       GPU: ");
-            memInfoBuilder.append(stringifyKBSize(gpuUsage));
-            memInfoBuilder.append("\n");
-        }
-        memInfoBuilder.append("  Used RAM: ");
-        memInfoBuilder.append(stringifyKBSize(
-                                  totalPss - cachedPss + kernelUsed));
-        memInfoBuilder.append("\n");
-        memInfoBuilder.append("  Lost RAM: ");
-        memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
-                - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
-                - kernelUsed - memInfo.getZramTotalSizeKb()));
-        memInfoBuilder.append("\n");
-        Slog.i(TAG, "Low on memory:");
-        Slog.i(TAG, shortNativeBuilder.toString());
-        Slog.i(TAG, fullJavaBuilder.toString());
-        Slog.i(TAG, memInfoBuilder.toString());
-
-        StringBuilder dropBuilder = new StringBuilder(1024);
-        /*
-        StringWriter oomSw = new StringWriter();
-        PrintWriter oomPw = new FastPrintWriter(oomSw, false, 256);
-        StringWriter catSw = new StringWriter();
-        PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
-        String[] emptyArgs = new String[] { };
-        dumpApplicationMemoryUsage(null, oomPw, "  ", emptyArgs, true, catPw);
-        oomPw.flush();
-        String oomString = oomSw.toString();
-        */
-        dropBuilder.append("Low on memory:");
-        dropBuilder.append(stack);
-        dropBuilder.append('\n');
-        dropBuilder.append(fullNativeBuilder);
-        dropBuilder.append(fullJavaBuilder);
-        dropBuilder.append('\n');
-        dropBuilder.append(memInfoBuilder);
-        dropBuilder.append('\n');
-        /*
-        dropBuilder.append(oomString);
-        dropBuilder.append('\n');
-        */
-        StringWriter catSw = new StringWriter();
-        synchronized (ActivityManagerService.this) {
-            PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
-            String[] emptyArgs = new String[] { };
-            catPw.println();
-            dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null, -1);
-            catPw.println();
-            mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
-                    false, null).dumpLocked();
-            catPw.println();
-            mAtmInternal.dump(DUMP_ACTIVITIES_CMD, null, catPw, emptyArgs, 0, false, false, null);
-            catPw.flush();
-        }
-        dropBuilder.append(catSw.toString());
-        FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED);
-        addErrorToDropBox("lowmem", null, "system_server", null,
-                null, null, tag.toString(), dropBuilder.toString(), null, null);
-        //Slog.i(TAG, "Sent to dropbox:");
-        //Slog.i(TAG, dropBuilder.toString());
-        synchronized (ActivityManagerService.this) {
-            long now = SystemClock.uptimeMillis();
-            if (mLastMemUsageReportTime < now) {
-                mLastMemUsageReportTime = now;
-            }
-        }
-    }
-
     /**
      * Searches array of arguments for the specified string
      * @param args array of argument strings
@@ -12178,97 +11330,26 @@
      * app that was passed in must remain on the process lists.
      */
     @GuardedBy("this")
-    final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
+    final boolean cleanUpApplicationRecordLocked(ProcessRecord app, int pid,
             boolean restarting, boolean allowRestart, int index, boolean replacingPid) {
-        if (index >= 0) {
-            removeLruProcessLocked(app);
-            ProcessList.remove(app.pid);
-        }
+        boolean restart;
+        synchronized (mProcLock) {
+            if (index >= 0) {
+                removeLruProcessLocked(app);
+                ProcessList.remove(pid);
+            }
 
-        mProcessesToGc.remove(app);
+            restart = app.onCleanupApplicationRecordLSP(mProcessStats, allowRestart);
+        }
         mAppProfiler.onCleanupApplicationRecordLocked(app);
-
-        // Dismiss any open dialogs.
-        app.getDialogController().clearAllErrorDialogs();
-
-        app.setCrashing(false);
-        app.setNotResponding(false);
-
-        app.resetPackageList(mProcessStats);
-        app.unlinkDeathRecipient();
-        app.makeInactive(mProcessStats);
-        app.waitingToKill = null;
-        app.forcingToImportant = null;
-        updateProcessForegroundLocked(app, false, 0, false);
-        app.setHasForegroundActivities(false);
-        app.hasShownUi = false;
-        app.treatLikeActivity = false;
-        app.hasAboveClient = false;
-        app.setHasClientActivities(false);
-
-        mServices.killServicesLocked(app, allowRestart);
-        mPhantomProcessList.onAppDied(app.pid);
-
-        boolean restart = false;
-
-        // Remove published content providers.
-        for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
-            ContentProviderRecord cpr = app.pubProviders.valueAt(i);
-            if (cpr.proc != app) {
-                // If the hosting process record isn't really us, bail out
-                continue;
-            }
-            final boolean alwaysRemove = app.bad || !allowRestart;
-            final boolean inLaunching = mCpHelper.removeDyingProviderLocked(app, cpr, alwaysRemove);
-            if (!alwaysRemove && inLaunching && cpr.hasConnectionOrHandle()) {
-                // We left the provider in the launching list, need to
-                // restart it.
-                restart = true;
-            }
-
-            cpr.provider = null;
-            cpr.setProcess(null);
-        }
-        app.pubProviders.clear();
-
-        // Take care of any launching providers waiting for this process.
-        if (mCpHelper.cleanupAppInLaunchingProvidersLocked(app, false)) {
-            mProcessList.noteProcessDiedLocked(app);
-            restart = true;
-        }
-
-        // Unregister from connected content providers.
-        if (!app.conProviders.isEmpty()) {
-            for (int i = app.conProviders.size() - 1; i >= 0; i--) {
-                ContentProviderConnection conn = app.conProviders.get(i);
-                conn.provider.connections.remove(conn);
-                stopAssociationLocked(app.uid, app.processName, conn.provider.uid,
-                        conn.provider.appInfo.longVersionCode, conn.provider.name,
-                        conn.provider.info.processName);
-            }
-            app.conProviders.clear();
-        }
-
-        // At this point there may be remaining entries in mLaunchingProviders
-        // where we were the only one waiting, so they are no longer of use.
-        // Look for these and clean up if found.
-        // XXX Commented out for now.  Trying to figure out a way to reproduce
-        // the actual situation to identify what is actually going on.
-        if (false) {
-            mCpHelper.cleanupLaunchingProvidersLocked();
-        }
-
         skipCurrentReceiverLocked(app);
-
-        // Unregister any receivers.
-        for (int i = app.receivers.size() - 1; i >= 0; i--) {
-            removeReceiverLocked(app.receivers.valueAt(i));
-        }
-        app.receivers.clear();
+        updateProcessForegroundLocked(app, false, 0, false);
+        mServices.killServicesLocked(app, allowRestart);
+        mPhantomProcessList.onAppDied(pid);
 
         // If the app is undergoing backup, tell the backup manager about it
         final BackupRecord backupTarget = mBackupTargets.get(app.userId);
-        if (backupTarget != null && app.pid == backupTarget.app.pid) {
+        if (backupTarget != null && pid == backupTarget.app.getPid()) {
             if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App "
                     + backupTarget.appInfo + " died during backup");
             mHandler.post(new Runnable() {
@@ -12285,17 +11366,9 @@
             });
         }
 
-        for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
-            ProcessChangeItem item = mPendingProcessChanges.get(i);
-            if (app.pid > 0 && item.pid == app.pid) {
-                mPendingProcessChanges.remove(i);
-                mAvailProcessChanges.add(item);
-            }
-        }
-        mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED_UI_MSG, app.pid, app.info.uid,
-                null).sendToTarget();
+        mProcessList.scheduleDispatchProcessDiedLocked(pid, app.info.uid);
 
-        // If this is a precede instance of another process instance
+        // If this is a preceding instance of another process instance
         allowRestart = true;
         synchronized (app) {
             if (app.mSuccessor != null) {
@@ -12303,13 +11376,13 @@
                 // because we have created a new one already.
                 allowRestart = false;
                 // If it's persistent, add the successor to mPersistentStartingProcesses
-                if (app.isPersistent() && !app.removed) {
+                if (app.isPersistent() && !app.isRemoved()) {
                     if (mPersistentStartingProcesses.indexOf(app.mSuccessor) < 0) {
                         mPersistentStartingProcesses.add(app.mSuccessor);
                     }
                 }
                 // clean up the field so the successor's proc starter could proceed.
-                app.mSuccessor.mPrecedence = null;
+                app.mSuccessor.mPredecessor = null;
                 app.mSuccessor = null;
                 // Notify if anyone is waiting for it.
                 app.notifyAll();
@@ -12329,7 +11402,7 @@
                 mProcessList.removeProcessNameLocked(app.processName, app.uid, app);
             }
             mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
-        } else if (!app.removed) {
+        } else if (!app.isRemoved()) {
             // This app is persistent, so we need to keep its record around.
             // If it is not already on the pending app list, add it there
             // and start a new process for it.
@@ -12349,7 +11422,7 @@
             // We have components that still need to be running in the
             // process, so re-launch it.
             if (index < 0) {
-                ProcessList.remove(app.pid);
+                ProcessList.remove(pid);
             }
 
             // Remove provider publish timeout because we will start a new timeout when the
@@ -12357,14 +11430,13 @@
             mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, app);
 
             mProcessList.addProcessNameLocked(app);
-            app.pendingStart = false;
-            mProcessList.startProcessLocked(app,
-                    new HostingRecord("restart", app.processName),
+            app.setPendingStart(false);
+            mProcessList.startProcessLocked(app, new HostingRecord("restart", app.processName),
                     ZYGOTE_POLICY_FLAG_EMPTY);
             return true;
-        } else if (app.pid > 0 && app.pid != MY_PID) {
+        } else if (pid > 0 && pid != MY_PID) {
             // Goodbye!
-            removePidLocked(app);
+            removePidLocked(pid, app);
             mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
             mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
             if (app.isolated) {
@@ -12626,7 +11698,7 @@
                 Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
                 throw new IllegalArgumentException("Invalid service token");
             }
-            mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);
+            mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false);
         }
     }
 
@@ -12703,12 +11775,12 @@
             // process after the full backup is done and the ProcessRecord will vaporize anyway.
             if (UserHandle.isApp(app.uid) &&
                     backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) {
-                proc.inFullBackup = true;
+                proc.setInFullBackup(true);
             }
             r.app = proc;
             final BackupRecord backupTarget = mBackupTargets.get(targetUserId);
             oldBackupUid = backupTarget != null ? backupTarget.appInfo.uid : -1;
-            newBackupUid = proc.inFullBackup ? r.appInfo.uid : -1;
+            newBackupUid = proc.isInFullBackup() ? r.appInfo.uid : -1;
             mBackupTargets.put(targetUserId, r);
 
             // Try not to kill the process during backup
@@ -12716,10 +11788,11 @@
 
             // If the process is already attached, schedule the creation of the backup agent now.
             // If it is not yet live, this will be done when it attaches to the framework.
-            if (proc.thread != null) {
+            final IApplicationThread thread = proc.getThread();
+            if (thread != null) {
                 if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "Agent proc already running: " + proc);
                 try {
-                    proc.thread.scheduleCreateBackupAgent(app,
+                    thread.scheduleCreateBackupAgent(app,
                             compatibilityInfoForPackage(app), backupMode, targetUserId,
                             operationType);
                 } catch (RemoteException e) {
@@ -12829,14 +11902,15 @@
                 // Not backing this app up any more; reset its OOM adjustment
                 final ProcessRecord proc = backupTarget.app;
                 updateOomAdjLocked(proc, true, OomAdjuster.OOM_ADJ_REASON_NONE);
-                proc.inFullBackup = false;
+                proc.setInFullBackup(false);
 
                 oldBackupUid = backupTarget != null ? backupTarget.appInfo.uid : -1;
 
                 // If the app crashed during backup, 'thread' will be null here
-                if (proc.thread != null) {
+                final IApplicationThread thread = proc.getThread();
+                if (thread != null) {
                     try {
-                        proc.thread.scheduleDestroyBackupAgent(appInfo,
+                        thread.scheduleDestroyBackupAgent(appInfo,
                                 compatibilityInfoForPackage(appInfo), userId);
                     } catch (Exception e) {
                         Slog.e(TAG, "Exception when unbinding backup agent:");
@@ -12931,21 +12005,21 @@
         boolean instantApp;
         synchronized(this) {
             if (caller != null) {
-                callerApp = getRecordForAppLocked(caller);
+                callerApp = getRecordForAppLOSP(caller);
                 if (callerApp == null) {
                     throw new SecurityException(
                             "Unable to find app for caller " + caller
                             + " (pid=" + Binder.getCallingPid()
                             + ") when registering receiver " + receiver);
                 }
-                if (callerApp.info.uid != SYSTEM_UID &&
-                        !callerApp.pkgList.containsKey(callerPackage) &&
-                        !"android".equals(callerPackage)) {
+                if (callerApp.info.uid != SYSTEM_UID
+                        && !callerApp.getPkgList().containsKey(callerPackage)
+                        && !"android".equals(callerPackage)) {
                     throw new SecurityException("Given caller package " + callerPackage
                             + " is not running in process " + callerApp);
                 }
                 callingUid = callerApp.info.uid;
-                callingPid = callerApp.pid;
+                callingPid = callerApp.getPid();
             } else {
                 callerPackage = null;
                 callingUid = Binder.getCallingUid();
@@ -13014,8 +12088,9 @@
         }
 
         synchronized (this) {
-            if (callerApp != null && (callerApp.thread == null
-                    || callerApp.thread.asBinder() != caller.asBinder())) {
+            IApplicationThread thread;
+            if (callerApp != null && ((thread = callerApp.getThread()) == null
+                    || thread.asBinder() != caller.asBinder())) {
                 // Original caller already died
                 return null;
             }
@@ -13024,13 +12099,13 @@
                 rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                         userId, receiver);
                 if (rl.app != null) {
-                    final int totalReceiversForApp = rl.app.receivers.size();
+                    final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
                     if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
                         throw new IllegalStateException("Too many receivers, total of "
                                 + totalReceiversForApp + ", registered for pid: "
                                 + rl.pid + ", callerPackage: " + callerPackage);
                     }
-                    rl.app.receivers.add(rl);
+                    rl.app.mReceivers.addReceiver(rl);
                 } else {
                     try {
                         receiver.asBinder().linkToDeath(rl, 0);
@@ -13116,7 +12191,7 @@
                     }
 
                     if (rl.app != null) {
-                        rl.app.receivers.remove(rl);
+                        rl.app.mReceivers.removeReceiver(rl);
                     }
                     removeReceiverLocked(rl);
                     if (rl.linkedToDeath) {
@@ -13415,7 +12490,7 @@
                 }
             }
             if (brOptions.isDontSendToRestrictedApps()
-                    && !isUidActiveLocked(callingUid)
+                    && !isUidActiveLOSP(callingUid)
                     && isBackgroundRestrictedNoCheck(callingUid, callerPackage)) {
                 Slog.i(TAG, "Not sending broadcast " + action + " - app " + callerPackage
                         + " has background restrictions");
@@ -13622,12 +12697,14 @@
                                     if (killProcess) {
                                         final int extraUid = intent.getIntExtra(Intent.EXTRA_UID,
                                                 -1);
-                                        mProcessList.killPackageProcessesLocked(ssp,
-                                                UserHandle.getAppId(extraUid),
-                                                userId, ProcessList.INVALID_ADJ,
-                                                ApplicationExitInfo.REASON_USER_REQUESTED,
-                                                ApplicationExitInfo.SUBREASON_UNKNOWN,
-                                                "change " + ssp);
+                                        synchronized (mProcLock) {
+                                            mProcessList.killPackageProcessesLSP(ssp,
+                                                    UserHandle.getAppId(extraUid),
+                                                    userId, ProcessList.INVALID_ADJ,
+                                                    ApplicationExitInfo.REASON_USER_REQUESTED,
+                                                    ApplicationExitInfo.SUBREASON_UNKNOWN,
+                                                    "change " + ssp);
+                                        }
                                     }
                                     cleanupDisabledPackageComponentsLocked(ssp, userId,
                                             intent.getStringArrayExtra(
@@ -13771,7 +12848,7 @@
                     Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
                 final int uid = getUidFromIntent(intent);
                 if (uid != -1) {
-                    final UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
+                    final UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
                     if (uidRec != null) {
                         uidRec.updateHasInternetPermission();
                     }
@@ -14156,7 +13233,7 @@
         synchronized(this) {
             intent = verifyBroadcastLocked(intent);
 
-            final ProcessRecord callerApp = getRecordForAppLocked(caller);
+            final ProcessRecord callerApp = getRecordForAppLOSP(caller);
             final int callingPid = Binder.getCallingPid();
             final int callingUid = Binder.getCallingUid();
 
@@ -14385,38 +13462,40 @@
                     || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0;
             boolean disableTestApiChecks = disableHiddenApiChecks
                     || (flags & INSTR_FLAG_DISABLE_TEST_API_CHECKS) != 0;
+
             if (disableHiddenApiChecks || disableTestApiChecks) {
                 enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS,
                         "disable hidden API checks");
 
-                enableTestApiAccess(ii.packageName);
+                enableTestApiAccess(ai.packageName);
             }
 
             final long origId = Binder.clearCallingIdentity();
 
             ProcessRecord app;
-            if (noRestart) {
-                app = getProcessRecordLocked(ai.processName, ai.uid, true);
-            } else {
-                // Instrumentation can kill and relaunch even persistent processes
-                forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
-                        "start instr");
-                // Inform usage stats to make the target package active
-                if (mUsageStatsService != null) {
-                    mUsageStatsService.reportEvent(ii.targetPackage, userId,
-                            UsageEvents.Event.SYSTEM_INTERACTION);
+            synchronized (mProcLock) {
+                if (noRestart) {
+                    app = getProcessRecordLocked(ai.processName, ai.uid, true);
+                } else {
+                    // Instrumentation can kill and relaunch even persistent processes
+                    forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false,
+                            userId, "start instr");
+                    // Inform usage stats to make the target package active
+                    if (mUsageStatsService != null) {
+                        mUsageStatsService.reportEvent(ii.targetPackage, userId,
+                                UsageEvents.Event.SYSTEM_INTERACTION);
+                    }
+                    app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks, abiOverride,
+                            ZYGOTE_POLICY_FLAG_EMPTY);
                 }
-                app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks,
-                        disableTestApiChecks, abiOverride, ZYGOTE_POLICY_FLAG_EMPTY);
-            }
 
-
-            app.setActiveInstrumentation(activeInstr);
-            activeInstr.mFinished = false;
-            activeInstr.mSourceUid = callingUid;
-            activeInstr.mRunningProcesses.add(app);
-            if (!mActiveInstrumentation.contains(activeInstr)) {
-                mActiveInstrumentation.add(activeInstr);
+                app.setActiveInstrumentation(activeInstr);
+                activeInstr.mFinished = false;
+                activeInstr.mSourceUid = callingUid;
+                activeInstr.mRunningProcesses.add(app);
+                if (!mActiveInstrumentation.contains(activeInstr)) {
+                    mActiveInstrumentation.add(activeInstr);
+                }
             }
 
             if ((flags & INSTR_FLAG_DISABLE_ISOLATED_STORAGE) != 0) {
@@ -14443,7 +13522,7 @@
         }
 
         try {
-            pr.thread.instrumentWithoutRestart(
+            pr.getThread().instrumentWithoutRestart(
                     activeInstr.mClass,
                     activeInstr.mArguments,
                     activeInstr.mWatcher,
@@ -14503,14 +13582,17 @@
         }
 
         synchronized(this) {
-            ProcessRecord app = getRecordForAppLocked(target);
+            ProcessRecord app = getRecordForAppLOSP(target);
             if (app == null) {
                 Slog.w(TAG, "addInstrumentationResults: no app for " + target);
                 return;
             }
             final long origId = Binder.clearCallingIdentity();
-            addInstrumentationResultsLocked(app, results);
-            Binder.restoreCallingIdentity(origId);
+            try {
+                addInstrumentationResultsLocked(app, results);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
         }
     }
 
@@ -14522,36 +13604,38 @@
             return;
         }
 
-        if (!instr.mFinished) {
-            if (instr.mWatcher != null) {
-                Bundle finalResults = instr.mCurResults;
-                if (finalResults != null) {
-                    if (instr.mCurResults != null && results != null) {
-                        finalResults.putAll(results);
+        synchronized (mProcLock) {
+            if (!instr.mFinished) {
+                if (instr.mWatcher != null) {
+                    Bundle finalResults = instr.mCurResults;
+                    if (finalResults != null) {
+                        if (instr.mCurResults != null && results != null) {
+                            finalResults.putAll(results);
+                        }
+                    } else {
+                        finalResults = results;
                     }
-                } else {
-                    finalResults = results;
+                    mInstrumentationReporter.reportFinished(instr.mWatcher,
+                            instr.mClass, resultCode, finalResults);
                 }
-                mInstrumentationReporter.reportFinished(instr.mWatcher,
-                        instr.mClass, resultCode, finalResults);
+
+                // Can't call out of the system process with a lock held, so post a message.
+                if (instr.mUiAutomationConnection != null) {
+                    // Go back to the default mode of denying OP_NO_ISOLATED_STORAGE app op.
+                    mAppOpsService.setMode(AppOpsManager.OP_NO_ISOLATED_STORAGE, app.uid,
+                            app.info.packageName, AppOpsManager.MODE_ERRORED);
+                    mAppOpsService.setAppOpsServiceDelegate(null);
+                    getPermissionManagerInternal().stopShellPermissionIdentityDelegation();
+                    mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG,
+                            instr.mUiAutomationConnection).sendToTarget();
+                }
+                instr.mFinished = true;
             }
 
-            // Can't call out of the system process with a lock held, so post a message.
-            if (instr.mUiAutomationConnection != null) {
-                // Go back to the default mode of denying OP_NO_ISOLATED_STORAGE app op.
-                mAppOpsService.setMode(AppOpsManager.OP_NO_ISOLATED_STORAGE, app.uid,
-                        app.info.packageName, AppOpsManager.MODE_ERRORED);
-                mAppOpsService.setAppOpsServiceDelegate(null);
-                getPermissionManagerInternalLocked().stopShellPermissionIdentityDelegation();
-                mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG,
-                        instr.mUiAutomationConnection).sendToTarget();
-            }
-            instr.mFinished = true;
+            instr.removeProcess(app);
+            app.setActiveInstrumentation(null);
         }
 
-        instr.removeProcess(app);
-        app.setActiveInstrumentation(null);
-
         if (!instr.mNoRestart) {
             forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false,
                     app.userId,
@@ -14587,7 +13671,7 @@
         }
 
         synchronized(this) {
-            ProcessRecord app = getRecordForAppLocked(target);
+            ProcessRecord app = getRecordForAppLOSP(target);
             if (app == null) {
                 Slog.w(TAG, "finishInstrumentation: no app for " + target);
                 return;
@@ -14693,10 +13777,11 @@
     // the current [or imminent] receiver on.
     boolean isReceivingBroadcastLocked(ProcessRecord app,
             ArraySet<BroadcastQueue> receivingQueues) {
-        final int N = app.curReceivers.size();
-        if (N > 0) {
-            for (int i = 0; i < N; i++) {
-                receivingQueues.add(app.curReceivers.valueAt(i).queue);
+        final ProcessReceiverRecord prr = app.mReceivers;
+        final int numOfReceivers = prr.numberOfCurReceivers();
+        if (numOfReceivers > 0) {
+            for (int i = 0; i < numOfReceivers; i++) {
+                receivingQueues.add(prr.getCurReceiverAt(i).queue);
             }
             return true;
         }
@@ -14813,28 +13898,10 @@
     }
 
     /**
-     * Ask a given process to GC right now.
-     */
-    final void performAppGcLocked(ProcessRecord app) {
-        try {
-            app.lastRequestedGc = SystemClock.uptimeMillis();
-            if (app.thread != null) {
-                if (app.reportLowMemory) {
-                    app.reportLowMemory = false;
-                    app.thread.scheduleLowMemory();
-                } else {
-                    app.thread.processInBackground();
-                }
-            }
-        } catch (Exception e) {
-            // whatever.
-        }
-    }
-
-    /**
      * Returns true if things are idle enough to perform GCs.
      */
-    private final boolean canGcNowLocked() {
+    @GuardedBy("this")
+    final boolean canGcNowLocked() {
         for (BroadcastQueue q : mBroadcastQueues) {
             if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isEmpty()) {
                 return false;
@@ -14843,178 +13910,94 @@
         return mAtmInternal.canGcNow();
     }
 
-    /**
-     * Perform GCs on all processes that are waiting for it, but only
-     * if things are idle.
-     */
-    final void performAppGcsLocked() {
-        final int N = mProcessesToGc.size();
-        if (N <= 0) {
-            return;
-        }
-        if (canGcNowLocked()) {
-            while (mProcessesToGc.size() > 0) {
-                ProcessRecord proc = mProcessesToGc.remove(0);
-                if (proc.getCurRawAdj() > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) {
-                    if ((proc.lastRequestedGc+mConstants.GC_MIN_INTERVAL)
-                            <= SystemClock.uptimeMillis()) {
-                        // To avoid spamming the system, we will GC processes one
-                        // at a time, waiting a few seconds between each.
-                        performAppGcLocked(proc);
-                        scheduleAppGcsLocked();
-                        return;
-                    } else {
-                        // It hasn't been long enough since we last GCed this
-                        // process...  put it in the list to wait for its time.
-                        addProcessToGcListLocked(proc);
-                        break;
-                    }
-                }
-            }
-
-            scheduleAppGcsLocked();
-        }
-    }
-
-    /**
-     * If all looks good, perform GCs on all processes waiting for them.
-     */
-    final void performAppGcsIfAppropriateLocked() {
-        if (canGcNowLocked()) {
-            performAppGcsLocked();
-            return;
-        }
-        // Still not idle, wait some more.
-        scheduleAppGcsLocked();
-    }
-
-    /**
-     * Schedule the execution of all pending app GCs.
-     */
-    final void scheduleAppGcsLocked() {
-        mHandler.removeMessages(GC_BACKGROUND_PROCESSES_MSG);
-
-        if (mProcessesToGc.size() > 0) {
-            // Schedule a GC for the time to the next process.
-            ProcessRecord proc = mProcessesToGc.get(0);
-            Message msg = mHandler.obtainMessage(GC_BACKGROUND_PROCESSES_MSG);
-
-            long when = proc.lastRequestedGc + mConstants.GC_MIN_INTERVAL;
-            long now = SystemClock.uptimeMillis();
-            if (when < (now+mConstants.GC_TIMEOUT)) {
-                when = now + mConstants.GC_TIMEOUT;
-            }
-            mHandler.sendMessageAtTime(msg, when);
-        }
-    }
-
-    /**
-     * Add a process to the array of processes waiting to be GCed.  Keeps the
-     * list in sorted order by the last GC time.  The process can't already be
-     * on the list.
-     */
-    final void addProcessToGcListLocked(ProcessRecord proc) {
-        boolean added = false;
-        for (int i=mProcessesToGc.size()-1; i>=0; i--) {
-            if (mProcessesToGc.get(i).lastRequestedGc <
-                    proc.lastRequestedGc) {
-                added = true;
-                mProcessesToGc.add(i+1, proc);
-                break;
-            }
-        }
-        if (!added) {
-            mProcessesToGc.add(0, proc);
-        }
-    }
-
-    /**
-     * Set up to ask a process to GC itself.  This will either do it
-     * immediately, or put it on the list of processes to gc the next
-     * time things are idle.
-     */
-    final void scheduleAppGcLocked(ProcessRecord app) {
-        long now = SystemClock.uptimeMillis();
-        if ((app.lastRequestedGc+mConstants.GC_MIN_INTERVAL) > now) {
-            return;
-        }
-        if (!mProcessesToGc.contains(app)) {
-            addProcessToGcListLocked(app);
-            scheduleAppGcsLocked();
-        }
-    }
-
     private void checkExcessivePowerUsage() {
         updateCpuStatsNow();
 
-        synchronized (this) {
-            boolean doCpuKills = true;
-            if (mLastPowerCheckUptime == 0) {
-                doCpuKills = false;
-            }
+        synchronized (mProcLock) {
+            final boolean doCpuKills = mLastPowerCheckUptime != 0;
             final long curUptime = SystemClock.uptimeMillis();
             final long uptimeSince = curUptime - mLastPowerCheckUptime;
             mLastPowerCheckUptime = curUptime;
-            int i = mProcessList.mLruProcesses.size();
-            while (i > 0) {
-                i--;
-                final ProcessRecord app = mProcessList.mLruProcesses.get(i);
-                if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
+            mProcessList.forEachLruProcessesLOSP(false, app -> {
+                if (app.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_HOME) {
                     int cpuLimit;
-                    long checkDur = curUptime - app.getWhenUnimportant();
+                    long checkDur = curUptime - app.mState.getWhenUnimportant();
                     if (checkDur <= mConstants.POWER_CHECK_INTERVAL) {
                         cpuLimit = mConstants.POWER_CHECK_MAX_CPU_1;
                     } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL * 2)
-                            || app.setProcState <= ActivityManager.PROCESS_STATE_HOME) {
+                            || app.mState.getSetProcState() <= ActivityManager.PROCESS_STATE_HOME) {
                         cpuLimit = mConstants.POWER_CHECK_MAX_CPU_2;
                     } else if (checkDur <= (mConstants.POWER_CHECK_INTERVAL * 3)) {
                         cpuLimit = mConstants.POWER_CHECK_MAX_CPU_3;
                     } else {
                         cpuLimit = mConstants.POWER_CHECK_MAX_CPU_4;
                     }
-                    if (app.lastCpuTime > 0) {
-                        final long cputimeUsed = app.curCpuTime - app.lastCpuTime;
-                        if (checkExcessivePowerUsageLocked(uptimeSince, doCpuKills, cputimeUsed,
-                                app.processName, app.toShortString(), cpuLimit, app)) {
-                            app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince
-                                    + " dur=" + checkDur + " limit=" + cpuLimit,
-                                    ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
-                                    ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
-                                    true);
-                            synchronized (mProcessStats.mLock) {
-                                app.baseProcessTracker.reportExcessiveCpu(app.pkgList.mPkgList);
-                            }
-                        }
-                    }
 
-                    app.lastCpuTime = app.curCpuTime;
+                    updateAppProcessCpuTimeLPr(uptimeSince, doCpuKills, checkDur, cpuLimit, app);
 
                     // Also check the phantom processes if there is any
-                    final long chkDur = checkDur;
-                    final int cpuLmt = cpuLimit;
-                    final boolean doKill = doCpuKills;
-                    mPhantomProcessList.forEachPhantomProcessOfApp(app, r -> {
-                        if (r.mLastCputime > 0) {
-                            final long cputimeUsed = r.mCurrentCputime - r.mLastCputime;
-                            if (checkExcessivePowerUsageLocked(uptimeSince, doKill, cputimeUsed,
-                                    app.processName, r.toString(), cpuLimit, app)) {
-                                mPhantomProcessList.killPhantomProcessGroupLocked(app, r,
-                                        ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
-                                        ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
-                                        "excessive cpu " + cputimeUsed + " during "
-                                        + uptimeSince + " dur=" + chkDur + " limit=" + cpuLmt);
-                                return false;
-                            }
-                        }
-                        r.mLastCputime = r.mCurrentCputime;
-                        return true;
-                    });
+                    updatePhantomProcessCpuTimeLPr(
+                            uptimeSince, doCpuKills, checkDur, cpuLimit, app);
                 }
-            }
+            });
         }
     }
 
-    private boolean checkExcessivePowerUsageLocked(final long uptimeSince, boolean doCpuKills,
+    @GuardedBy("mProcLock")
+    private void updateAppProcessCpuTimeLPr(final long uptimeSince, final boolean doCpuKills,
+            final long checkDur, final int cpuLimit, final ProcessRecord app) {
+        synchronized (mAppProfiler.mProfilerLock) {
+            final ProcessProfileRecord profile = app.mProfile;
+            final long curCpuTime = profile.mCurCpuTime.get();
+            final long lastCpuTime = profile.mLastCpuTime.get();
+            if (lastCpuTime > 0) {
+                final long cpuTimeUsed = curCpuTime - lastCpuTime;
+                if (checkExcessivePowerUsageLPr(uptimeSince, doCpuKills, cpuTimeUsed,
+                            app.processName, app.toShortString(), cpuLimit, app)) {
+                    mHandler.post(() -> {
+                        synchronized (ActivityManagerService.this) {
+                            app.killLocked("excessive cpu " + cpuTimeUsed + " during "
+                                    + uptimeSince + " dur=" + checkDur + " limit=" + cpuLimit,
+                                    ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+                                    ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
+                                    true);
+                        }
+                    });
+                    profile.reportExcessiveCpu();
+                }
+            }
+
+            profile.mLastCpuTime.set(curCpuTime);
+        }
+    }
+
+    @GuardedBy("mProcLock")
+    private void updatePhantomProcessCpuTimeLPr(final long uptimeSince, final boolean doCpuKills,
+            final long checkDur, final int cpuLimit, final ProcessRecord app) {
+        mPhantomProcessList.forEachPhantomProcessOfApp(app, r -> {
+            if (r.mLastCputime > 0) {
+                final long cpuTimeUsed = r.mCurrentCputime - r.mLastCputime;
+                if (checkExcessivePowerUsageLPr(uptimeSince, doCpuKills, cpuTimeUsed,
+                            app.processName, r.toString(), cpuLimit, app)) {
+                    mHandler.post(() -> {
+                        synchronized (ActivityManagerService.this) {
+                            mPhantomProcessList.killPhantomProcessGroupLocked(app, r,
+                                    ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
+                                    ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
+                                    "excessive cpu " + cpuTimeUsed + " during "
+                                    + uptimeSince + " dur=" + checkDur + " limit=" + cpuLimit);
+                        }
+                    });
+                    return false;
+                }
+            }
+            r.mLastCputime = r.mCurrentCputime;
+            return true;
+        });
+    }
+
+    @GuardedBy("mProcLock")
+    private boolean checkExcessivePowerUsageLPr(final long uptimeSince, boolean doCpuKills,
             final long cputimeUsed, final String processName, final String description,
             final int cpuLimit, final ProcessRecord app) {
         if (DEBUG_POWER) {
@@ -15036,15 +14019,14 @@
             if (((cputimeUsed * 100) / uptimeSince) >= cpuLimit) {
                 mBatteryStatsService.reportExcessiveCpu(app.info.uid, app.processName,
                         uptimeSince, cputimeUsed);
-                for (int ipkg = app.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                    ProcessStats.ProcessStateHolder holder = app.pkgList.valueAt(ipkg);
+                app.getPkgList().forEachPackageProcessStats(holder -> {
                     FrameworkStatsLog.write(
                             FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED,
                             app.info.uid,
                             processName,
                             holder.state.getPackage(),
                             holder.appVersion);
-                }
+                });
                 return true;
             }
         }
@@ -15056,23 +14038,24 @@
         if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
             return false;
         }
-        return getPackageManagerInternalLocked().isPackageEphemeral(
+        return getPackageManagerInternal().isPackageEphemeral(
                 UserHandle.getUserId(uid), packages[0]);
     }
 
+    @GuardedBy("this")
     void enqueueUidChangeLocked(UidRecord uidRec, int uid, int change) {
-        uid = uidRec != null ? uidRec.uid : uid;
+        uid = uidRec != null ? uidRec.getUid() : uid;
         if (uid < 0) {
             throw new IllegalArgumentException("No UidRecord or uid");
         }
 
         final int procState = uidRec != null
-                ? uidRec.setProcState : PROCESS_STATE_NONEXISTENT;
+                ? uidRec.getSetProcState() : PROCESS_STATE_NONEXISTENT;
         final long procStateSeq = uidRec != null ? uidRec.curProcStateSeq : 0;
-        final int capability = uidRec != null ? uidRec.setCapability : 0;
-        final boolean ephemeral = uidRec != null ? uidRec.ephemeral : isEphemeralLocked(uid);
+        final int capability = uidRec != null ? uidRec.getSetCapability() : 0;
+        final boolean ephemeral = uidRec != null ? uidRec.isEphemeral() : isEphemeralLocked(uid);
 
-        if (uidRec != null && !uidRec.idle && (change & UidRecord.CHANGE_GONE) != 0) {
+        if (uidRec != null && !uidRec.isIdle() && (change & UidRecord.CHANGE_GONE) != 0) {
             // If this uid is going away, and we haven't yet reported it is gone,
             // then do so now.
             change |= UidRecord.CHANGE_IDLE;
@@ -15081,7 +14064,7 @@
                 uidRec == null ? null : uidRec.pendingChange,
                 uid, change, procState, procStateSeq, capability, ephemeral);
         if (uidRec != null) {
-            uidRec.lastReportedChange = enqueuedChange;
+            uidRec.setLastReportedChange(enqueuedChange);
             uidRec.updateLastDispatchedProcStateSeq(enqueuedChange);
         }
 
@@ -15104,24 +14087,21 @@
         }
     }
 
-    final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
-        synchronized (mProcessStats.mLock) {
-            if (proc.thread != null && proc.baseProcessTracker != null) {
-                final int procState = proc.getReportedProcState();
-                if (procState != PROCESS_STATE_NONEXISTENT) {
-                    proc.baseProcessTracker.setState(
-                            procState, memFactor, now, proc.pkgList.mPkgList);
-                }
-            }
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    final void setProcessTrackerStateLOSP(ProcessRecord proc, int memFactor, long now) {
+        if (proc.getThread() != null) {
+            proc.mProfile.setProcessTrackerState(
+                    proc.mState.getReportedProcState(), memFactor, now);
         }
     }
 
     @GuardedBy("this")
     final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
             int fgServiceTypes, boolean oomAdj) {
-        if (isForeground != proc.hasForegroundServices()
-                || proc.getForegroundServiceTypes() != fgServiceTypes) {
-            proc.setHasForegroundServices(isForeground, fgServiceTypes);
+        final ProcessServiceRecord psr = proc.mServices;
+        if (isForeground != psr.hasForegroundServices()
+                || psr.getForegroundServiceTypes() != fgServiceTypes) {
+            psr.setHasForegroundServices(isForeground, fgServiceTypes);
             ArrayList<ProcessRecord> curProcs = mForegroundPackages.get(proc.info.packageName,
                     proc.info.uid);
             if (isForeground) {
@@ -15147,8 +14127,9 @@
                 }
             }
 
-            proc.setReportedForegroundServiceTypes(fgServiceTypes);
-            ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.pid, proc.info.uid);
+            psr.setReportedForegroundServiceTypes(fgServiceTypes);
+            ProcessChangeItem item = mProcessList.enqueueProcessChangeItemLocked(
+                    proc.getPid(), proc.info.uid);
             item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
             item.foregroundServiceTypes = fgServiceTypes;
         }
@@ -15159,7 +14140,7 @@
 
     // TODO(b/111541062): This method is only used for updating OOM adjustments. We need to update
     // the logic there and in mBatteryStatsService to make them aware of multiple resumed activities
-    ProcessRecord getTopAppLocked() {
+    ProcessRecord getTopApp() {
         final WindowProcessController wpc = mAtmInternal != null ? mAtmInternal.getTopApp() : null;
         final ProcessRecord r = wpc != null ? (ProcessRecord) wpc.mOwner : null;
         String pkg;
@@ -15172,25 +14153,26 @@
             uid = -1;
         }
         // Has the UID or resumed package name changed?
-        if (uid != mCurResumedUid || (pkg != mCurResumedPackage
-                && (pkg == null || !pkg.equals(mCurResumedPackage)))) {
+        synchronized (mCurResumedAppLock) {
+            if (uid != mCurResumedUid || (pkg != mCurResumedPackage
+                        && (pkg == null || !pkg.equals(mCurResumedPackage)))) {
 
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                if (mCurResumedPackage != null) {
-                    mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_TOP_FINISH,
-                            mCurResumedPackage, mCurResumedUid);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    if (mCurResumedPackage != null) {
+                        mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_TOP_FINISH,
+                                mCurResumedPackage, mCurResumedUid);
+                    }
+                    mCurResumedPackage = pkg;
+                    mCurResumedUid = uid;
+                    if (mCurResumedPackage != null) {
+                        mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_TOP_START,
+                                mCurResumedPackage, mCurResumedUid);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
                 }
-                mCurResumedPackage = pkg;
-                mCurResumedUid = uid;
-                if (mCurResumedPackage != null) {
-                    mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_TOP_START,
-                            mCurResumedPackage, mCurResumedUid);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
             }
-
         }
         return r;
     }
@@ -15279,47 +14261,52 @@
         userId = mUserController.handleIncomingUser(callingPid, Binder.getCallingUid(),
                 userId, true, ALLOW_FULL_ONLY, "makePackageIdle", null);
         final long callingId = Binder.clearCallingIdentity();
-        synchronized(this) {
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            int pkgUid = -1;
             try {
-                IPackageManager pm = AppGlobals.getPackageManager();
-                int pkgUid = -1;
-                try {
-                    pkgUid = pm.getPackageUid(packageName, MATCH_UNINSTALLED_PACKAGES
-                            | MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM);
-                } catch (RemoteException e) {
-                }
-                if (pkgUid == -1) {
-                    throw new IllegalArgumentException("Unknown package name " + packageName);
-                }
+                pkgUid = pm.getPackageUid(packageName, MATCH_UNINSTALLED_PACKAGES
+                        | MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM);
+            } catch (RemoteException e) {
+            }
+            if (pkgUid == -1) {
+                throw new IllegalArgumentException("Unknown package name " + packageName);
+            }
 
-                if (mLocalPowerManager != null) {
-                    mLocalPowerManager.startUidChanges();
-                }
-                final int appId = UserHandle.getAppId(pkgUid);
-                final int N = mProcessList.mActiveUids.size();
-                for (int i = N - 1; i >= 0; i--) {
-                    final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
-                    final long bgTime = uidRec.lastBackgroundTime;
-                    if (bgTime > 0 && !uidRec.idle) {
-                        if (UserHandle.getAppId(uidRec.uid) == appId) {
-                            if (userId == UserHandle.USER_ALL ||
-                                    userId == UserHandle.getUserId(uidRec.uid)) {
-                                EventLogTags.writeAmUidIdle(uidRec.uid);
-                                uidRec.idle = true;
-                                uidRec.setIdle = true;
-                                Slog.w(TAG, "Idling uid " + UserHandle.formatUid(uidRec.uid)
-                                        + " from package " + packageName + " user " + userId);
-                                doStopUidLocked(uidRec.uid, uidRec);
+            synchronized (this) {
+                try {
+                    if (mLocalPowerManager != null) {
+                        mLocalPowerManager.startUidChanges();
+                    }
+                    final int appId = UserHandle.getAppId(pkgUid);
+                    for (int i = mProcessList.mActiveUids.size() - 1; i >= 0; i--) {
+                        final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
+                        final long bgTime = uidRec.getLastBackgroundTime();
+                        if (bgTime > 0 && !uidRec.isIdle()) {
+                            final int uid = uidRec.getUid();
+                            if (UserHandle.getAppId(uid) == appId) {
+                                if (userId == UserHandle.USER_ALL
+                                        || userId == UserHandle.getUserId(uid)) {
+                                    EventLogTags.writeAmUidIdle(uid);
+                                    synchronized (mProcLock) {
+                                        uidRec.setIdle(true);
+                                        uidRec.setSetIdle(true);
+                                    }
+                                    Slog.w(TAG, "Idling uid " + UserHandle.formatUid(uid)
+                                            + " from package " + packageName + " user " + userId);
+                                    doStopUidLocked(uid, uidRec);
+                                }
                             }
                         }
                     }
+                } finally {
+                    if (mLocalPowerManager != null) {
+                        mLocalPowerManager.finishUidChanges();
+                    }
                 }
-            } finally {
-                if (mLocalPowerManager != null) {
-                    mLocalPowerManager.finishUidChanges();
-                }
-                Binder.restoreCallingIdentity(callingId);
             }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
         }
     }
 
@@ -15332,11 +14319,11 @@
 
     final void runInBackgroundDisabled(int uid) {
         synchronized (this) {
-            UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
+            UidRecord uidRec = mProcessList.getUidRecordLOSP(uid);
             if (uidRec != null) {
                 // This uid is actually running...  should it be considered background now?
-                if (uidRec.idle) {
-                    doStopUidLocked(uidRec.uid, uidRec);
+                if (uidRec.isIdle()) {
+                    doStopUidLocked(uidRec.getUid(), uidRec);
                 }
             } else {
                 // This uid isn't actually running...  still send a report about it being "stopped".
@@ -15373,24 +14360,24 @@
     }
 
     /**
-     * Whitelists {@code targetUid} to temporarily bypass Power Save mode.
+     * Allowlists {@code targetUid} to temporarily bypass Power Save mode.
      */
     @GuardedBy("this")
-    void tempWhitelistForPendingIntentLocked(int callerPid, int callerUid, int targetUid,
+    void tempAllowlistForPendingIntentLocked(int callerPid, int callerUid, int targetUid,
             long duration, int type, String tag) {
         if (DEBUG_WHITELISTS) {
-            Slog.d(TAG, "tempWhitelistForPendingIntentLocked(" + callerPid + ", " + callerUid + ", "
+            Slog.d(TAG, "tempAllowlistForPendingIntentLocked(" + callerPid + ", " + callerUid + ", "
                     + targetUid + ", " + duration + ", " + type + ")");
         }
 
         synchronized (mPidsSelfLocked) {
             final ProcessRecord pr = mPidsSelfLocked.get(callerPid);
             if (pr == null) {
-                Slog.w(TAG, "tempWhitelistForPendingIntentLocked() no ProcessRecord for pid "
+                Slog.w(TAG, "tempAllowlistForPendingIntentLocked() no ProcessRecord for pid "
                         + callerPid);
                 return;
             }
-            if (!pr.whitelistManager) {
+            if (!pr.mServices.mAllowlistManager) {
                 if (checkPermission(CHANGE_DEVICE_IDLE_TEMP_WHITELIST, callerPid, callerUid)
                         != PackageManager.PERMISSION_GRANTED
                         && checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callerPid, callerUid)
@@ -15398,7 +14385,7 @@
                         && checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callerPid,
                         callerUid) != PackageManager.PERMISSION_GRANTED) {
                     if (DEBUG_WHITELISTS) {
-                        Slog.d(TAG, "tempWhitelistForPendingIntentLocked() for target " + targetUid
+                        Slog.d(TAG, "tempAllowlistForPendingIntentLocked() for target " + targetUid
                                 + ": pid " + callerPid + " is not allowed");
                     }
                     return;
@@ -15406,35 +14393,37 @@
             }
         }
 
-        tempWhitelistUidLocked(targetUid, duration, tag, type);
+        tempAllowlistUidLocked(targetUid, duration, tag, type);
     }
 
     /**
-     * Whitelists {@code targetUid} to temporarily bypass Power Save mode.
+     * Allowlists {@code targetUid} to temporarily bypass Power Save mode.
      */
     @GuardedBy("this")
-    void tempWhitelistUidLocked(int targetUid, long duration, String tag, int type) {
-        mPendingTempWhitelist.put(targetUid,
-                new PendingTempWhitelist(targetUid, duration, tag, type));
-        setUidTempWhitelistStateLocked(targetUid, true);
-        mUiHandler.obtainMessage(PUSH_TEMP_WHITELIST_UI_MSG).sendToTarget();
+    void tempAllowlistUidLocked(int targetUid, long duration, String tag, int type) {
+        synchronized (mProcLock) {
+            mPendingTempAllowlist.put(targetUid,
+                    new PendingTempAllowlist(targetUid, duration, tag, type));
+            setUidTempAllowlistStateLSP(targetUid, true);
+            mUiHandler.obtainMessage(PUSH_TEMP_ALLOWLIST_UI_MSG).sendToTarget();
 
-        if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
-            mFgsStartTempAllowList.add(targetUid, duration);
+            if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
+                mFgsStartTempAllowList.add(targetUid, duration);
+            }
         }
     }
 
-    void pushTempWhitelist() {
+    void pushTempAllowlist() {
         final int N;
-        final PendingTempWhitelist[] list;
+        final PendingTempAllowlist[] list;
 
         // First copy out the pending changes...  we need to leave them in the map for now,
         // in case someone needs to check what is coming up while we don't have the lock held.
-        synchronized(this) {
-            N = mPendingTempWhitelist.size();
-            list = new PendingTempWhitelist[N];
+        synchronized (mProcLock) {
+            N = mPendingTempAllowlist.size();
+            list = new PendingTempAllowlist[N];
             for (int i = 0; i < N; i++) {
-                list[i] = mPendingTempWhitelist.valueAt(i);
+                list[i] = mPendingTempAllowlist.valueAt(i);
             }
         }
 
@@ -15443,32 +14432,34 @@
         // device idle policy anyway at this phase.
         if (mLocalDeviceIdleController != null) {
             for (int i = 0; i < N; i++) {
-                PendingTempWhitelist ptw = list[i];
+                PendingTempAllowlist ptw = list[i];
                 mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid,
                         ptw.duration, ptw.type, true, ptw.tag);
             }
         }
 
         // And now we can safely remove them from the map.
-        synchronized(this) {
-            for (int i = 0; i < N; i++) {
-                PendingTempWhitelist ptw = list[i];
-                int index = mPendingTempWhitelist.indexOfKey(ptw.targetUid);
-                if (index >= 0 && mPendingTempWhitelist.valueAt(index) == ptw) {
-                    mPendingTempWhitelist.removeAt(index);
+        synchronized (this) {
+            synchronized (mProcLock) {
+                for (int i = 0; i < N; i++) {
+                    PendingTempAllowlist ptw = list[i];
+                    int index = mPendingTempAllowlist.indexOfKey(ptw.targetUid);
+                    if (index >= 0 && mPendingTempAllowlist.valueAt(index) == ptw) {
+                        mPendingTempAllowlist.removeAt(index);
+                    }
                 }
             }
         }
     }
 
-    @GuardedBy("this")
-    final void setAppIdTempWhitelistStateLocked(int uid, boolean onWhitelist) {
-        mOomAdjuster.setAppIdTempWhitelistStateLocked(uid, onWhitelist);
+    @GuardedBy({"this", "mProcLock"})
+    final void setAppIdTempAllowlistStateLSP(int uid, boolean onAllowlist) {
+        mOomAdjuster.setAppIdTempAllowlistStateLSP(uid, onAllowlist);
     }
 
-    @GuardedBy("this")
-    final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) {
-        mOomAdjuster.setUidTempWhitelistStateLocked(uid, onWhitelist);
+    @GuardedBy({"this", "mProcLock"})
+    final void setUidTempAllowlistStateLSP(int uid, boolean onAllowlist) {
+        mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
     }
 
     private void trimApplications(boolean forceFullOomAdj, String oomAdjReason) {
@@ -15485,26 +14476,28 @@
         for (int i = mProcessList.mRemovedProcesses.size() - 1; i >= 0; i--) {
             final ProcessRecord app = mProcessList.mRemovedProcesses.get(i);
             if (!app.hasActivitiesOrRecentTasks()
-                    && app.curReceivers.isEmpty() && app.numberOfRunningServices() == 0) {
-                Slog.i(
-                    TAG, "Exiting empty application process "
-                    + app.toShortString() + " ("
-                    + (app.thread != null ? app.thread.asBinder() : null)
-                    + ")\n");
-                if (app.pid > 0 && app.pid != MY_PID) {
-                    app.kill("empty",
+                    && app.mReceivers.numberOfCurReceivers() == 0
+                    && app.mServices.numberOfRunningServices() == 0) {
+                final IApplicationThread thread = app.getThread();
+                Slog.i(TAG, "Exiting empty application process "
+                        + app.toShortString() + " ("
+                        + (thread != null ? thread.asBinder() : null)
+                        + ")\n");
+                final int pid = app.getPid();
+                if (pid > 0 && pid != MY_PID) {
+                    app.killLocked("empty",
                             ApplicationExitInfo.REASON_OTHER,
                             ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
                             false);
-                } else if (app.thread != null) {
+                } else if (thread != null) {
                     try {
-                        app.thread.scheduleExit();
+                        thread.scheduleExit();
                     } catch (Exception e) {
                         // Ignore exceptions.
                     }
                 }
                 didSomething = true;
-                cleanUpApplicationRecordLocked(app, false, true, -1, false /*replacingPid*/);
+                cleanUpApplicationRecordLocked(app, pid, false, true, -1, false /*replacingPid*/);
                 mProcessList.mRemovedProcesses.remove(i);
 
                 if (app.isPersistent()) {
@@ -15525,56 +14518,58 @@
     }
 
     /** This method sends the specified signal to each of the persistent apps */
-    public void signalPersistentProcesses(int sig) throws RemoteException {
+    public void signalPersistentProcesses(final int sig) throws RemoteException {
         if (sig != SIGNAL_USR1) {
             throw new SecurityException("Only SIGNAL_USR1 is allowed");
         }
 
-        synchronized (this) {
-            if (checkCallingPermission(android.Manifest.permission.SIGNAL_PERSISTENT_PROCESSES)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires permission "
-                        + android.Manifest.permission.SIGNAL_PERSISTENT_PROCESSES);
-            }
+        if (checkCallingPermission(android.Manifest.permission.SIGNAL_PERSISTENT_PROCESSES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SIGNAL_PERSISTENT_PROCESSES);
+        }
 
-            for (int i = mProcessList.mLruProcesses.size() - 1 ; i >= 0 ; i--) {
-                ProcessRecord r = mProcessList.mLruProcesses.get(i);
-                if (r.thread != null && r.isPersistent()) {
-                    sendSignal(r.pid, sig);
+        synchronized (mProcLock) {
+            mProcessList.forEachLruProcessesLOSP(false, app -> {
+                if (app.getThread() != null && app.isPersistent()) {
+                    sendSignal(app.getPid(), sig);
                 }
-            }
+            });
         }
     }
 
     public boolean profileControl(String process, int userId, boolean start,
             ProfilerInfo profilerInfo, int profileType) throws RemoteException {
-        synchronized (this) {
-            // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
-            // its own permission.
-            if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires permission "
-                        + android.Manifest.permission.SET_ACTIVITY_WATCHER);
-            }
+        // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
+        // its own permission.
+        if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+        }
 
-            if (start && (profilerInfo == null || profilerInfo.profileFd == null)) {
-                throw new IllegalArgumentException("null profile info or fd");
-            }
+        if (start && (profilerInfo == null || profilerInfo.profileFd == null)) {
+            throw new IllegalArgumentException("null profile info or fd");
+        }
 
-            ProcessRecord proc = null;
+        ProcessRecord proc = null;
+        synchronized (mProcLock) {
             if (process != null) {
-                proc = findProcessLocked(process, userId, "profileControl");
+                proc = findProcessLOSP(process, userId, "profileControl");
             }
 
-            if (start && (proc == null || proc.thread == null)) {
+            if (start && (proc == null || proc.getThread() == null)) {
                 throw new IllegalArgumentException("Unknown process: " + process);
             }
+        }
 
-            return mAppProfiler.profileControlLocked(proc, start, profilerInfo, profileType);
+        synchronized (mAppProfiler.mProfilerLock) {
+            return mAppProfiler.profileControlLPf(proc, start, profilerInfo, profileType);
         }
     }
 
-    private ProcessRecord findProcessLocked(String process, int userId, String callName) {
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    private ProcessRecord findProcessLOSP(String process, int userId, String callName) {
         userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                 userId, true, ALLOW_FULL_ONLY, callName, null);
         ProcessRecord proc = null;
@@ -15587,8 +14582,8 @@
         }
 
         if (proc == null) {
-            ArrayMap<String, SparseArray<ProcessRecord>> all
-                    = mProcessList.mProcessNames.getMap();
+            ArrayMap<String, SparseArray<ProcessRecord>> all =
+                    mProcessList.getProcessNamesLOSP().getMap();
             SparseArray<ProcessRecord> procs = all.get(process);
             if (procs != null && procs.size() > 0) {
                 proc = procs.valueAt(0);
@@ -15610,23 +14605,23 @@
     @Override
     public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
             boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
-
         try {
-            synchronized (this) {
-                // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
-                // its own permission (same as profileControl).
-                if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    throw new SecurityException("Requires permission "
-                            + android.Manifest.permission.SET_ACTIVITY_WATCHER);
-                }
+            // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
+            // its own permission (same as profileControl).
+            if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires permission "
+                        + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+            }
 
-                if (fd == null) {
-                    throw new IllegalArgumentException("null fd");
-                }
+            if (fd == null) {
+                throw new IllegalArgumentException("null fd");
+            }
 
-                ProcessRecord proc = findProcessLocked(process, userId, "dumpHeap");
-                if (proc == null || proc.thread == null) {
+            synchronized (mProcLock) {
+                ProcessRecord proc = findProcessLOSP(process, userId, "dumpHeap");
+                IApplicationThread thread;
+                if (proc == null || (thread = proc.getThread()) == null) {
                     throw new IllegalArgumentException("Unknown process: " + process);
                 }
 
@@ -15648,7 +14643,7 @@
                         }
                     }, null);
 
-                proc.thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
+                thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
                 fd = null;
                 return true;
             }
@@ -15683,7 +14678,7 @@
                 }
                 processName = proc.processName;
                 uid = proc.uid;
-                if (reportPackage != null && !proc.pkgList.containsKey(reportPackage)) {
+                if (reportPackage != null && !proc.getPkgList().containsKey(reportPackage)) {
                     throw new SecurityException("Package " + reportPackage + " is not running in "
                             + proc);
                 }
@@ -15703,8 +14698,8 @@
     }
 
     void onCoreSettingsChange(Bundle settings) {
-        synchronized (this) {
-            mProcessList.updateCoreSettingsLocked(settings);
+        synchronized (mProcLock) {
+            mProcessList.updateCoreSettingsLOSP(settings);
         }
     }
 
@@ -15856,8 +14851,9 @@
         return info;
     }
 
-    private boolean processSanityChecksLocked(ProcessRecord process) {
-        if (process == null || process.thread == null) {
+    @GuardedBy("mProcLock")
+    private boolean processSanityChecksLPr(ProcessRecord process, IApplicationThread thread) {
+        if (process == null || thread == null) {
             return false;
         }
 
@@ -15871,52 +14867,55 @@
     }
 
     public boolean startBinderTracking() throws RemoteException {
-        synchronized (this) {
-            mBinderTransactionTrackingEnabled = true;
-            // TODO: hijacking SET_ACTIVITY_WATCHER, but should be changed to its own
-            // permission (same as profileControl).
-            if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
-                    != PackageManager.PERMISSION_GRANTED) {
-                throw new SecurityException("Requires permission "
-                        + android.Manifest.permission.SET_ACTIVITY_WATCHER);
-            }
+        // TODO: hijacking SET_ACTIVITY_WATCHER, but should be changed to its own
+        // permission (same as profileControl).
+        if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+        }
 
-            for (int i = 0; i < mProcessList.mLruProcesses.size(); i++) {
-                ProcessRecord process = mProcessList.mLruProcesses.get(i);
-                if (!processSanityChecksLocked(process)) {
-                    continue;
+        synchronized (mProcLock) {
+            mBinderTransactionTrackingEnabled = true;
+            mProcessList.forEachLruProcessesLOSP(true, process -> {
+                final IApplicationThread thread = process.getThread();
+                if (!processSanityChecksLPr(process, thread)) {
+                    return;
                 }
                 try {
-                    process.thread.startBinderTracking();
+                    thread.startBinderTracking();
                 } catch (RemoteException e) {
                     Log.v(TAG, "Process disappared");
                 }
-            }
-            return true;
+            });
         }
+        return true;
     }
 
-    public boolean stopBinderTrackingAndDump(ParcelFileDescriptor fd) throws RemoteException {
-        try {
-            synchronized (this) {
-                mBinderTransactionTrackingEnabled = false;
-                // TODO: hijacking SET_ACTIVITY_WATCHER, but should be changed to its own
-                // permission (same as profileControl).
-                if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
-                        != PackageManager.PERMISSION_GRANTED) {
-                    throw new SecurityException("Requires permission "
-                            + android.Manifest.permission.SET_ACTIVITY_WATCHER);
-                }
+    @Override
+    public boolean stopBinderTrackingAndDump(final ParcelFileDescriptor fd) throws RemoteException {
+        // TODO: hijacking SET_ACTIVITY_WATCHER, but should be changed to its own
+        // permission (same as profileControl).
+        if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+        }
 
+        boolean closeFd = true;
+        try {
+            synchronized (mProcLock) {
                 if (fd == null) {
                     throw new IllegalArgumentException("null fd");
                 }
+                mBinderTransactionTrackingEnabled = false;
 
                 PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd.getFileDescriptor()));
                 pw.println("Binder transaction traces for all processes.\n");
-                for (ProcessRecord process : mProcessList.mLruProcesses) {
-                    if (!processSanityChecksLocked(process)) {
-                        continue;
+                mProcessList.forEachLruProcessesLOSP(true, process -> {
+                    final IApplicationThread thread = process.getThread();
+                    if (!processSanityChecksLPr(process, thread)) {
+                        return;
                     }
 
                     pw.println("Traces for process: " + process.processName);
@@ -15924,7 +14923,7 @@
                     try {
                         TransferPipe tp = new TransferPipe();
                         try {
-                            process.thread.stopBinderTrackingAndDump(tp.getWriteFd());
+                            thread.stopBinderTrackingAndDump(tp.getWriteFd());
                             tp.go(fd.getFileDescriptor());
                         } finally {
                             tp.kill();
@@ -15938,12 +14937,12 @@
                                 process + ".  Exception: " + e);
                         pw.flush();
                     }
-                }
-                fd = null;
+                });
+                closeFd = false;
                 return true;
             }
         } finally {
-            if (fd != null) {
+            if (fd != null && closeFd) {
                 try {
                     fd.close();
                 } catch (IOException e) {
@@ -15988,12 +14987,12 @@
 
         @Override
         public void killForegroundAppsForUser(@UserIdInt int userId) {
-            synchronized (ActivityManagerService.this) {
-                final ArrayList<ProcessRecord> procs = new ArrayList<>();
-                final int NP = mProcessList.mProcessNames.getMap().size();
-                for (int ip = 0; ip < NP; ip++) {
+            final ArrayList<ProcessRecord> procs = new ArrayList<>();
+            synchronized (mProcLock) {
+                final int numOfProcs = mProcessList.getProcessNamesLOSP().getMap().size();
+                for (int ip = 0; ip < numOfProcs; ip++) {
                     final SparseArray<ProcessRecord> apps =
-                            mProcessList.mProcessNames.getMap().valueAt(ip);
+                            mProcessList.getProcessNamesLOSP().getMap().valueAt(ip);
                     final int NA = apps.size();
                     for (int ia = 0; ia < NA; ia++) {
                         final ProcessRecord app = apps.valueAt(ia);
@@ -16001,19 +15000,23 @@
                             // We don't kill persistent processes.
                             continue;
                         }
-                        if (app.removed
-                                || (app.userId == userId && app.hasForegroundActivities())) {
+                        if (app.isRemoved()
+                                || (app.userId == userId && app.mState.hasForegroundActivities())) {
                             procs.add(app);
                         }
                     }
                 }
+            }
 
-                final int N = procs.size();
-                for (int i = 0; i < N; i++) {
-                    mProcessList.removeProcessLocked(procs.get(i), false, true,
-                            ApplicationExitInfo.REASON_OTHER,
-                            ApplicationExitInfo.SUBREASON_KILL_ALL_FG,
-                            "kill all fg");
+            final int numOfProcs = procs.size();
+            if (numOfProcs > 0) {
+                synchronized (ActivityManagerService.this) {
+                    for (int i = 0; i < numOfProcs; i++) {
+                        mProcessList.removeProcessLocked(procs.get(i), false, true,
+                                ApplicationExitInfo.REASON_OTHER,
+                                ApplicationExitInfo.SUBREASON_KILL_ALL_FG,
+                                "kill all fg");
+                    }
                 }
             }
         }
@@ -16059,22 +15062,26 @@
         @Override
         public void setDeviceIdleWhitelist(int[] allAppids, int[] exceptIdleAppids) {
             synchronized (ActivityManagerService.this) {
-                mDeviceIdleWhitelist = allAppids;
-                mDeviceIdleExceptIdleWhitelist = exceptIdleAppids;
+                synchronized (mProcLock) {
+                    mDeviceIdleAllowlist = allAppids;
+                    mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
+                }
             }
         }
 
         @Override
         public void updateDeviceIdleTempWhitelist(int[] appids, int changingUid, boolean adding,
-                long durationMs, @BroadcastOptions.TempAllowListType int type) {
+                long durationMs, @TempAllowListType int type) {
             synchronized (ActivityManagerService.this) {
-                mDeviceIdleTempWhitelist = appids;
-                if (adding) {
-                    if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
-                        mFgsStartTempAllowList.add(changingUid, durationMs);
+                synchronized (mProcLock) {
+                    mDeviceIdleTempAllowlist = appids;
+                    if (adding) {
+                        if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
+                            mFgsStartTempAllowList.add(changingUid, durationMs);
+                        }
                     }
+                    setAppIdTempAllowlistStateLSP(changingUid, adding);
                 }
-                setAppIdTempWhitelistStateLocked(changingUid, adding);
             }
         }
 
@@ -16112,10 +15119,10 @@
                         return;
                     }
                 }
-                if (pr.hasOverlayUi() == hasOverlayUi) {
+                if (pr.mState.hasOverlayUi() == hasOverlayUi) {
                     return;
                 }
-                pr.setHasOverlayUi(hasOverlayUi);
+                pr.mState.setHasOverlayUi(hasOverlayUi);
                 //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
                 updateOomAdjLocked(pr, true, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
             }
@@ -16133,8 +15140,8 @@
                         + uid + " seq: " + procStateSeq);
             }
             UidRecord record;
-            synchronized (ActivityManagerService.this) {
-                record = mProcessList.getUidRecordLocked(uid);
+            synchronized (mProcLock) {
+                record = mProcessList.getUidRecordLOSP(uid);
                 if (record == null) {
                     if (DEBUG_NETWORK) {
                         Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
@@ -16197,19 +15204,21 @@
 
         @Override
         public boolean isUidActive(int uid) {
-            synchronized (ActivityManagerService.this) {
-                return isUidActiveLocked(uid);
+            synchronized (mProcLock) {
+                return isUidActiveLOSP(uid);
             }
         }
 
         @Override
         public List<ProcessMemoryState> getMemoryStateForProcesses() {
             List<ProcessMemoryState> processMemoryStates = new ArrayList<>();
-            synchronized (mPidsSelfLocked) {
-                for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
-                    final ProcessRecord r = mPidsSelfLocked.valueAt(i);
-                    processMemoryStates.add(
-                            new ProcessMemoryState(r.uid, r.pid, r.processName, r.curAdj));
+            synchronized (mProcLock) {
+                synchronized (mPidsSelfLocked) {
+                    for (int i = 0, size = mPidsSelfLocked.size(); i < size; i++) {
+                        final ProcessRecord r = mPidsSelfLocked.valueAt(i);
+                        processMemoryStates.add(new ProcessMemoryState(
+                                r.uid, r.getPid(), r.processName, r.mState.getCurAdj()));
+                    }
                 }
             }
             return processMemoryStates;
@@ -16249,14 +15258,14 @@
                     final WindowProcessController wpc =
                             (WindowProcessController) procsToKill.get(i);
                     final ProcessRecord pr = (ProcessRecord) wpc.mOwner;
-                    if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                            && pr.curReceivers.isEmpty()) {
-                        pr.kill("remove task", ApplicationExitInfo.REASON_USER_REQUESTED,
+                    if (pr.mState.getSetSchedGroup() == ProcessList.SCHED_GROUP_BACKGROUND
+                            && pr.mReceivers.numberOfCurReceivers() == 0) {
+                        pr.killLocked("remove task", ApplicationExitInfo.REASON_USER_REQUESTED,
                                 ApplicationExitInfo.SUBREASON_UNKNOWN, true);
                     } else {
                         // We delay killing processes that are not in the background or running a
                         // receiver.
-                        pr.waitingToKill = "remove task";
+                        pr.setWaitingToKill("remove task");
                     }
                 }
             }
@@ -16278,18 +15287,15 @@
         public boolean hasRunningActivity(int uid, @Nullable String packageName) {
             if (packageName == null) return false;
 
-            synchronized (ActivityManagerService.this) {
-                for (int i = 0; i < mProcessList.mLruProcesses.size(); i++) {
-                    final ProcessRecord pr = mProcessList.mLruProcesses.get(i);
-                    if (pr.uid != uid) {
-                        continue;
+            synchronized (mProcLock) {
+                return mProcessList.searchEachLruProcessesLOSP(true, app -> {
+                    if (app.uid == uid
+                            && app.getWindowProcessController().hasRunningActivity(packageName)) {
+                        return Boolean.TRUE;
                     }
-                    if (pr.getWindowProcessController().hasRunningActivity(packageName)) {
-                        return true;
-                    }
-                }
+                    return null;
+                }) != null;
             }
-            return false;
         }
 
         @Override
@@ -16301,26 +15307,20 @@
 
         @Override
         public void updateCpuStats() {
-            synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.updateCpuStatsLocked();
-            }
+            ActivityManagerService.this.updateCpuStats();
         }
 
         @Override
         public void updateBatteryStats(ComponentName activity, int uid, int userId,
                 boolean resumed) {
-            synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.updateBatteryStats(activity, uid, userId, resumed);
-            }
+            ActivityManagerService.this.updateBatteryStats(activity, uid, userId, resumed);
         }
 
         @Override
         public void updateActivityUsageStats(ComponentName activity, int userId, int event,
                 IBinder appToken, ComponentName taskRoot) {
-            synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
-                        appToken, taskRoot);
-            }
+            ActivityManagerService.this.updateActivityUsageStats(activity, userId, event,
+                    appToken, taskRoot);
         }
 
         @Override
@@ -16397,16 +15397,14 @@
 
         @Override
         public void scheduleAppGcs() {
-            synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.scheduleAppGcsLocked();
+            synchronized (mAppProfiler.mProfilerLock) {
+                mAppProfiler.scheduleAppGcsLPf();
             }
         }
 
         @Override
         public int getTaskIdForActivity(IBinder token, boolean onlyRoot) {
-            synchronized (ActivityManagerService.this) {
-                return ActivityManagerService.this.getTaskForActivity(token, onlyRoot);
-            }
+            return ActivityManagerService.this.getTaskForActivity(token, onlyRoot);
         }
 
         @Override
@@ -16446,7 +15444,7 @@
         public void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid,
                 long duration, int type, String tag) {
             synchronized (ActivityManagerService.this) {
-                ActivityManagerService.this.tempWhitelistForPendingIntentLocked(
+                ActivityManagerService.this.tempAllowlistForPendingIntentLocked(
                         callerPid, callerUid, targetUid, duration, type, tag);
             }
         }
@@ -16527,7 +15525,8 @@
                     (ActivityServiceConnectionsHolder) connectionHolder;
             synchronized (ActivityManagerService.this) {
                 holder.forEachConnection(cr -> mServices.removeConnectionLocked(
-                        (ConnectionRecord) cr, null /* skipApp */, holder /* skipAct */));
+                        (ConnectionRecord) cr, null /* skipApp */, holder /* skipAct */,
+                        false /* enqueueOomAdj */));
             }
         }
 
@@ -16727,7 +15726,7 @@
             }
             synchronized (mPidsSelfLocked) {
                 final ProcessRecord pr = mPidsSelfLocked.get(pid);
-                return pr == null ? Zygote.MOUNT_EXTERNAL_NONE : pr.mountMode;
+                return pr == null ? Zygote.MOUNT_EXTERNAL_NONE : pr.getMountMode();
             }
         }
 
@@ -16758,19 +15757,17 @@
         @Override
         public boolean hasRunningForegroundService(int uid, int foregroundServicetype) {
             synchronized (ActivityManagerService.this) {
-                for (int i = 0; i < mProcessList.mLruProcesses.size(); i++) {
-                    final ProcessRecord pr = mProcessList.mLruProcesses.get(i);
-                    if (pr.uid != uid) {
-                        continue;
+                return mProcessList.searchEachLruProcessesLOSP(true, app -> {
+                    if (app.uid != uid) {
+                        return null;
                     }
 
-                    if ((pr.getForegroundServiceTypes() & foregroundServicetype) != 0) {
-                        return true;
+                    if ((app.mServices.getForegroundServiceTypes() & foregroundServicetype) != 0) {
+                        return Boolean.TRUE;
                     }
-                }
+                    return null;
+                }) != null;
             }
-
-            return false;
         }
 
         @Override
@@ -16785,7 +15782,7 @@
 
         @Override
         public boolean isUidCurrentlyInstrumented(int uid) {
-            synchronized (ActivityManagerService.this) {
+            synchronized (mProcLock) {
                 for (int i = mActiveInstrumentation.size() - 1; i >= 0; i--) {
                     ActiveInstrumentation activeInst = mActiveInstrumentation.get(i);
                     if (!activeInst.mFinished && activeInst.mTargetInfo != null
@@ -16865,6 +15862,16 @@
             // PackageManagerService.
             return mConstants.mBootTimeTempAllowlistDuration;
         }
+
+        @Override
+        public void registerAnrController(AnrController controller) {
+            mActivityTaskManager.registerAnrController(controller);
+        }
+
+        @Override
+        public void unregisterAnrController(AnrController controller) {
+            mActivityTaskManager.unregisterAnrController(controller);
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
@@ -16872,14 +15879,11 @@
             throw new SecurityException("Requires permission " + FILTER_EVENTS);
         }
         ProcessRecord proc;
-        long timeoutMillis;
-        synchronized (this) {
-            synchronized (mPidsSelfLocked) {
-                proc = mPidsSelfLocked.get(pid);
-            }
-            timeoutMillis = proc != null ? proc.getInputDispatchingTimeoutMillis() :
-                    DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+        synchronized (mPidsSelfLocked) {
+            proc = mPidsSelfLocked.get(pid);
         }
+        final long timeoutMillis = proc != null ? proc.getInputDispatchingTimeoutMillis() :
+                DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
         if (inputDispatchingTimedOut(proc, null, null, null, null, aboveSystem, reason)) {
             return 0;
@@ -16940,8 +15944,8 @@
             Slog.d(TAG_NETWORK, "Called from " + callingUid + " to wait for seq: " + procStateSeq);
         }
         UidRecord record;
-        synchronized (this) {
-            record = mProcessList.getUidRecordLocked(callingUid);
+        synchronized (mProcLock) {
+            record = mProcessList.getUidRecordLOSP(callingUid);
             if (record == null) {
                 return;
             }
@@ -17057,11 +16061,13 @@
         }
         try {
             synchronized(this) {
-                mProcessList.killPackageProcessesLocked(packageName, UserHandle.getAppId(pkgUid),
-                        userId, ProcessList.FOREGROUND_APP_ADJ,
-                        ApplicationExitInfo.REASON_DEPENDENCY_DIED,
-                        ApplicationExitInfo.SUBREASON_UNKNOWN,
-                        "dep: " + packageName);
+                synchronized (mProcLock) {
+                    mProcessList.killPackageProcessesLSP(packageName, UserHandle.getAppId(pkgUid),
+                            userId, ProcessList.FOREGROUND_APP_ADJ,
+                            ApplicationExitInfo.REASON_DEPENDENCY_DIED,
+                            ApplicationExitInfo.SUBREASON_UNKNOWN,
+                            "dep: " + packageName);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(callingId);
@@ -17078,10 +16084,10 @@
         enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
                 "scheduleApplicationInfoChanged()");
 
-        synchronized (this) {
+        synchronized (mProcLock) {
             final long origId = Binder.clearCallingIdentity();
             try {
-                updateApplicationInfoLocked(packageNames, userId);
+                updateApplicationInfoLOSP(packageNames, userId);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -17093,22 +16099,20 @@
      * resources and overlaid values are available immediately.
      */
     public void updateSystemUiContext() {
-        PackageManagerInternal packageManagerInternal;
-        synchronized (this) {
-            packageManagerInternal = getPackageManagerInternalLocked();
-        }
+        final PackageManagerInternal packageManagerInternal = getPackageManagerInternal();
 
         ApplicationInfo ai = packageManagerInternal.getApplicationInfo("android",
                 GET_SHARED_LIBRARY_FILES, Binder.getCallingUid(), UserHandle.USER_SYSTEM);
         ActivityThread.currentActivityThread().handleSystemApplicationInfoChanged(ai);
     }
 
-    void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
+    @GuardedBy(anyOf = {"this", "mProcLock"})
+    private void updateApplicationInfoLOSP(@NonNull List<String> packagesToUpdate, int userId) {
         final boolean updateFrameworkRes = packagesToUpdate.contains("android");
         if (updateFrameworkRes) {
             PackageParser.readConfigUseRoundIcon(null);
         }
-        mProcessList.updateApplicationInfoLocked(packagesToUpdate, userId, updateFrameworkRes);
+        mProcessList.updateApplicationInfoLOSP(packagesToUpdate, userId, updateFrameworkRes);
 
         if (updateFrameworkRes) {
             // Update system server components that need to know about changed overlays. Because the
@@ -17137,7 +16141,7 @@
             final int batchSize;
             final float threshold;
             final BinderCallHeavyHitterListener listener;
-            synchronized (ActivityManagerService.this) {
+            synchronized (mProcLock) {
                 if (mConstants.BINDER_HEAVY_HITTER_WATCHER_ENABLED) {
                     // Default watcher takes precedence, ignore the auto sampler.
                     mHandler.removeMessages(BINDER_HEAVYHITTER_AUTOSAMPLER_TIMEOUT_MSG);
@@ -17174,7 +16178,7 @@
             final int batchSize;
             final float threshold;
             final long now;
-            synchronized (ActivityManagerService.this) {
+            synchronized (mProcLock) {
                 if (!mConstants.BINDER_HEAVY_HITTER_AUTO_SAMPLER_ENABLED) {
                     // It's configured OFF
                     return;
@@ -17208,7 +16212,7 @@
      * Stop the binder heavy hitter auto sampler after given timeout.
      */
     private void handleBinderHeavyHitterAutoSamplerTimeOut() {
-        synchronized (ActivityManagerService.this) {
+        synchronized (mProcLock) {
             if (mConstants.BINDER_HEAVY_HITTER_WATCHER_ENABLED) {
                 // The default watcher is ON, don't bother to stop it.
                 return;
@@ -17254,10 +16258,11 @@
      */
     public void attachAgent(String process, String path) {
         try {
-            synchronized (this) {
-                ProcessRecord proc = findProcessLocked(process, UserHandle.USER_SYSTEM,
+            synchronized (mProcLock) {
+                ProcessRecord proc = findProcessLOSP(process, UserHandle.USER_SYSTEM,
                         "attachAgent");
-                if (proc == null || proc.thread == null) {
+                IApplicationThread thread;
+                if (proc == null || (thread = proc.getThread()) == null) {
                     throw new IllegalArgumentException("Unknown process: " + process);
                 }
 
@@ -17267,7 +16272,7 @@
                     }
                 }
 
-                proc.thread.attachAgent(path);
+                thread.attachAgent(path);
             }
         } catch (RemoteException e) {
             throw new IllegalStateException("Process disappeared");
@@ -17279,10 +16284,8 @@
      * like persisting database etc.
      */
     public void prepareForPossibleShutdown() {
-        synchronized (this) {
-            if (mUsageStatsService != null) {
-                mUsageStatsService.prepareForPossibleShutdown();
-            }
+        if (mUsageStatsService != null) {
+            mUsageStatsService.prepareForPossibleShutdown();
         }
     }
 
@@ -17338,7 +16341,7 @@
         }
 
         // We allow delegation only to one instrumentation started from the shell
-        synchronized (ActivityManagerService.this) {
+        synchronized (mProcLock) {
             // If the delegate is already set up for the target UID, nothing to do.
             if (mAppOpsService.getAppOpsServiceDelegate() != null) {
                 if (!(mAppOpsService.getAppOpsServiceDelegate() instanceof ShellDelegate)) {
@@ -17371,7 +16374,7 @@
                 final String packageName = instr.mTargetInfo.packageName;
                 final List<String> permissionNames = permissions != null ?
                         Arrays.asList(permissions) : null;
-                getPermissionManagerInternalLocked().startShellPermissionIdentityDelegation(
+                getPermissionManagerInternal().startShellPermissionIdentityDelegation(
                         delegateUid, packageName, permissionNames);
                 return;
             }
@@ -17384,9 +16387,9 @@
                 && UserHandle.getCallingAppId() != Process.ROOT_UID) {
             throw new SecurityException("Only the shell can delegate its permissions");
         }
-        synchronized (ActivityManagerService.this) {
+        synchronized (mProcLock) {
             mAppOpsService.setAppOpsServiceDelegate(null);
-            getPermissionManagerInternalLocked().stopShellPermissionIdentityDelegation();
+            getPermissionManagerInternal().stopShellPermissionIdentityDelegation();
         }
     }
 
@@ -17506,7 +16509,7 @@
         if (!isCallerShell()) {
             throw new SecurityException("Only shell can call it");
         }
-        synchronized (this) {
+        synchronized (mProcLock) {
             try {
                 if (mLifeMonitorFds == null) {
                     mLifeMonitorFds = ParcelFileDescriptor.createPipe();
@@ -17525,7 +16528,7 @@
     public void setActivityLocusContext(ComponentName activity, LocusId locusId, IBinder appToken) {
         final int callingUid = Binder.getCallingUid();
         final int userId = UserHandle.getCallingUserId();
-        if (getPackageManagerInternalLocked().getPackageUid(activity.getPackageName(),
+        if (getPackageManagerInternal().getPackageUid(activity.getPackageName(),
                 /*flags=*/ 0, userId) != callingUid) {
             throw new SecurityException("Calling uid " + callingUid + " cannot set locusId"
                     + "for package " + activity.getPackageName());
@@ -17555,9 +16558,7 @@
     @Override
     public void resetAppErrors() {
         enforceCallingPermission(Manifest.permission.RESET_APP_ERRORS, "resetAppErrors");
-        synchronized (this) {
-            mAppErrors.resetStateLocked();
-        }
+        mAppErrors.resetState();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index a7119d1..34d9a60 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -160,7 +160,7 @@
         }
 
         void appNotResponding(boolean onlyDumpSelf) {
-            mApp.appNotResponding(mActivityShortComponentName, mAppInfo,
+            mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
                     mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,
                     onlyDumpSelf);
         }
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index d76e2d7..48222cb 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -38,6 +38,7 @@
 final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListener {
 
     private final ActivityManagerService mService;
+    private final ActivityManagerGlobalLock mProcLock;
     private final AppErrorResult mResult;
     private final ProcessRecord mProc;
     private final boolean mIsRestartable;
@@ -63,6 +64,7 @@
         Resources res = context.getResources();
 
         mService = service;
+        mProcLock = service.mProcLock;
         mProc = data.proc;
         mResult = data.result;
         mIsRestartable = (data.taskId != INVALID_TASK_ID || data.isRestartableForService)
@@ -71,8 +73,8 @@
         BidiFormatter bidi = BidiFormatter.getInstance();
 
         CharSequence name;
-        if ((mProc.pkgList.size() == 1) &&
-                (name = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
+        if (mProc.getPkgList().size() == 1
+                && (name = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
             setTitle(res.getString(
                     data.repeating ? com.android.internal.R.string.aerr_application_repeated
                             : com.android.internal.R.string.aerr_application,
@@ -112,7 +114,7 @@
         LayoutInflater.from(context).inflate(
                 com.android.internal.R.layout.app_error_dialog, frame, true);
 
-        final boolean hasReceiver = mProc.errorReportReceiver != null;
+        final boolean hasReceiver = mProc.mErrorState.getErrorReportReceiver() != null;
 
         final TextView restart = findViewById(com.android.internal.R.id.aerr_restart);
         restart.setOnClickListener(this);
@@ -166,11 +168,11 @@
     }
 
     private void setResult(int result) {
-        synchronized (mService) {
+        synchronized (mProcLock) {
             if (mProc != null) {
                 // Don't dismiss again since it leads to recursive call between dismiss and this
                 // method.
-                mProc.getDialogController().clearCrashDialogs(false /* needDismiss */);
+                mProc.mErrorState.getDialogController().clearCrashDialogs(false /* needDismiss */);
             }
         }
         mResult.set(result);
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 35f4689..e5a5cff 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -52,6 +52,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ProcessMap;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
@@ -71,30 +72,36 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "AppErrors" : TAG_AM;
 
     private final ActivityManagerService mService;
+    private final ActivityManagerGlobalLock mProcLock;
     private final Context mContext;
     private final PackageWatchdog mPackageWatchdog;
 
+    @GuardedBy("mBadProcessLock")
     private ArraySet<String> mAppsNotReportingCrashes;
 
     /**
      * The last time that various processes have crashed since they were last explicitly started.
      */
+    @GuardedBy("mBadProcessLock")
     private final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<>();
 
     /**
      * The last time that various processes have crashed (not reset even when explicitly started).
      */
+    @GuardedBy("mBadProcessLock")
     private final ProcessMap<Long> mProcessCrashTimesPersistent = new ProcessMap<>();
 
     /**
      * The last time that various processes have crashed and shown an error dialog.
      */
+    @GuardedBy("mBadProcessLock")
     private final ProcessMap<Long> mProcessCrashShowDialogTimes = new ProcessMap<>();
 
     /**
      * A pairing between how many times various processes have crashed since a given time.
      * Entry and exit conditions for this map are similar to mProcessCrashTimes.
      */
+    @GuardedBy("mBadProcessLock")
     private final ProcessMap<Pair<Long, Integer>> mProcessCrashCounts = new ProcessMap<>();
 
     /**
@@ -116,29 +123,41 @@
      * lock operations from the simple lookup cases.
      */
     private volatile ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<>();
+
+    /**
+     * Dedicated lock for {@link #mAppsNotReportingCrashes}, {@link #mProcessCrashTimes},
+     * {@link #mProcessCrashTimesPersistent}, {@link #mProcessCrashShowDialogTimes},
+     * {@link #mProcessCrashCounts} and {@link #mBadProcesses}.
+     *
+     * <p>The naming convention of the function with this lock should be "-LBp"</b>
+     *
+     * @See mBadProcesses
+     */
     private final Object mBadProcessLock = new Object();
 
     AppErrors(Context context, ActivityManagerService service, PackageWatchdog watchdog) {
         context.assertRuntimeOverlayThemable();
         mService = service;
+        mProcLock = service.mProcLock;
         mContext = context;
         mPackageWatchdog = watchdog;
     }
 
     /** Resets the current state but leaves the constructor-provided fields unchanged. */
-    public void resetStateLocked() {
+    public void resetState() {
         Slog.i(TAG, "Resetting AppErrors");
-        mAppsNotReportingCrashes.clear();
-        mProcessCrashTimes.clear();
-        mProcessCrashTimesPersistent.clear();
-        mProcessCrashShowDialogTimes.clear();
-        mProcessCrashCounts.clear();
         synchronized (mBadProcessLock) {
+            mAppsNotReportingCrashes.clear();
+            mProcessCrashTimes.clear();
+            mProcessCrashTimesPersistent.clear();
+            mProcessCrashShowDialogTimes.clear();
+            mProcessCrashCounts.clear();
             mBadProcesses = new ProcessMap<>();
         }
     }
 
-    void dumpDebug(ProtoOutputStream proto, long fieldId, String dumpPackage) {
+    @GuardedBy("mProcLock")
+    void dumpDebugLPr(ProtoOutputStream proto, long fieldId, String dumpPackage) {
         final ProcessMap<BadProcessInfo> badProcesses = mBadProcesses;
         if (mProcessCrashTimes.getMap().isEmpty() && badProcesses.getMap().isEmpty()) {
             return;
@@ -148,34 +167,6 @@
         final long now = SystemClock.uptimeMillis();
         proto.write(AppErrorsProto.NOW_UPTIME_MS, now);
 
-        if (!mProcessCrashTimes.getMap().isEmpty()) {
-            final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
-            final int procCount = pmap.size();
-            for (int ip = 0; ip < procCount; ip++) {
-                final long ctoken = proto.start(AppErrorsProto.PROCESS_CRASH_TIMES);
-                final String pname = pmap.keyAt(ip);
-                final SparseArray<Long> uids = pmap.valueAt(ip);
-                final int uidCount = uids.size();
-
-                proto.write(AppErrorsProto.ProcessCrashTime.PROCESS_NAME, pname);
-                for (int i = 0; i < uidCount; i++) {
-                    final int puid = uids.keyAt(i);
-                    final ProcessRecord r = mService.getProcessNames().get(pname, puid);
-                    if (dumpPackage != null
-                            && (r == null || !r.pkgList.containsKey(dumpPackage))) {
-                        continue;
-                    }
-                    final long etoken = proto.start(AppErrorsProto.ProcessCrashTime.ENTRIES);
-                    proto.write(AppErrorsProto.ProcessCrashTime.Entry.UID, puid);
-                    proto.write(AppErrorsProto.ProcessCrashTime.Entry.LAST_CRASHED_AT_MS,
-                            uids.valueAt(i));
-                    proto.end(etoken);
-                }
-                proto.end(ctoken);
-            }
-
-        }
-
         if (!badProcesses.getMap().isEmpty()) {
             final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = badProcesses.getMap();
             final int processCount = pmap.size();
@@ -188,9 +179,9 @@
                 proto.write(AppErrorsProto.BadProcess.PROCESS_NAME, pname);
                 for (int i = 0; i < uidCount; i++) {
                     final int puid = uids.keyAt(i);
-                    final ProcessRecord r = mService.getProcessNames().get(pname, puid);
+                    final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
                     if (dumpPackage != null && (r == null
-                            || !r.pkgList.containsKey(dumpPackage))) {
+                            || !r.getPkgList().containsKey(dumpPackage))) {
                         continue;
                     }
                     final BadProcessInfo info = uids.valueAt(i);
@@ -206,67 +197,100 @@
             }
         }
 
-        proto.end(token);
-    }
+        synchronized (mBadProcessLock) {
+            if (!mProcessCrashTimes.getMap().isEmpty()) {
+                final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
+                final int procCount = pmap.size();
+                for (int ip = 0; ip < procCount; ip++) {
+                    final long ctoken = proto.start(AppErrorsProto.PROCESS_CRASH_TIMES);
+                    final String pname = pmap.keyAt(ip);
+                    final SparseArray<Long> uids = pmap.valueAt(ip);
+                    final int uidCount = uids.size();
 
-    boolean dumpLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String dumpPackage) {
-        final long now = SystemClock.uptimeMillis();
-        if (!mProcessCrashTimes.getMap().isEmpty()) {
-            boolean printed = false;
-            final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
-            final int processCount = pmap.size();
-            for (int ip = 0; ip < processCount; ip++) {
-                final String pname = pmap.keyAt(ip);
-                final SparseArray<Long> uids = pmap.valueAt(ip);
-                final int uidCount = uids.size();
-                for (int i = 0; i < uidCount; i++) {
-                    final int puid = uids.keyAt(i);
-                    final ProcessRecord r = mService.getProcessNames().get(pname, puid);
-                    if (dumpPackage != null && (r == null
-                            || !r.pkgList.containsKey(dumpPackage))) {
-                        continue;
+                    proto.write(AppErrorsProto.ProcessCrashTime.PROCESS_NAME, pname);
+                    for (int i = 0; i < uidCount; i++) {
+                        final int puid = uids.keyAt(i);
+                        final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
+                        if (dumpPackage != null
+                                && (r == null || !r.getPkgList().containsKey(dumpPackage))) {
+                            continue;
+                        }
+                        final long etoken = proto.start(AppErrorsProto.ProcessCrashTime.ENTRIES);
+                        proto.write(AppErrorsProto.ProcessCrashTime.Entry.UID, puid);
+                        proto.write(AppErrorsProto.ProcessCrashTime.Entry.LAST_CRASHED_AT_MS,
+                                uids.valueAt(i));
+                        proto.end(etoken);
                     }
-                    if (!printed) {
-                        if (needSep) pw.println();
-                        needSep = true;
-                        pw.println("  Time since processes crashed:");
-                        printed = true;
-                    }
-                    pw.print("    Process "); pw.print(pname);
-                    pw.print(" uid "); pw.print(puid);
-                    pw.print(": last crashed ");
-                    TimeUtils.formatDuration(now-uids.valueAt(i), pw);
-                    pw.println(" ago");
+                    proto.end(ctoken);
                 }
             }
         }
 
-        if (!mProcessCrashCounts.getMap().isEmpty()) {
-            boolean printed = false;
-            final ArrayMap<String, SparseArray<Pair<Long, Integer>>> pmap =
-                    mProcessCrashCounts.getMap();
-            final int processCount = pmap.size();
-            for (int ip = 0; ip < processCount; ip++) {
-                final String pname = pmap.keyAt(ip);
-                final SparseArray<Pair<Long, Integer>> uids = pmap.valueAt(ip);
-                final int uidCount = uids.size();
-                for (int i = 0; i < uidCount; i++) {
-                    final int puid = uids.keyAt(i);
-                    final ProcessRecord r = mService.getProcessNames().get(pname, puid);
-                    if (dumpPackage != null && (r == null || !r.pkgList.containsKey(dumpPackage))) {
-                        continue;
+        proto.end(token);
+    }
+
+    @GuardedBy("mProcLock")
+    boolean dumpLPr(FileDescriptor fd, PrintWriter pw, boolean needSep, String dumpPackage) {
+        final long now = SystemClock.uptimeMillis();
+        synchronized (mBadProcessLock) {
+            if (!mProcessCrashTimes.getMap().isEmpty()) {
+                boolean printed = false;
+                final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
+                final int processCount = pmap.size();
+                for (int ip = 0; ip < processCount; ip++) {
+                    final String pname = pmap.keyAt(ip);
+                    final SparseArray<Long> uids = pmap.valueAt(ip);
+                    final int uidCount = uids.size();
+                    for (int i = 0; i < uidCount; i++) {
+                        final int puid = uids.keyAt(i);
+                        final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
+                        if (dumpPackage != null
+                                && (r == null || !r.getPkgList().containsKey(dumpPackage))) {
+                            continue;
+                        }
+                        if (!printed) {
+                            if (needSep) pw.println();
+                            needSep = true;
+                            pw.println("  Time since processes crashed:");
+                            printed = true;
+                        }
+                        pw.print("    Process "); pw.print(pname);
+                        pw.print(" uid "); pw.print(puid);
+                        pw.print(": last crashed ");
+                        TimeUtils.formatDuration(now - uids.valueAt(i), pw);
+                        pw.println(" ago");
                     }
-                    if (!printed) {
-                        if (needSep) pw.println();
-                        needSep = true;
-                        pw.println("  First time processes crashed and counts:");
-                        printed = true;
+                }
+            }
+
+            if (!mProcessCrashCounts.getMap().isEmpty()) {
+                boolean printed = false;
+                final ArrayMap<String, SparseArray<Pair<Long, Integer>>> pmap =
+                        mProcessCrashCounts.getMap();
+                final int processCount = pmap.size();
+                for (int ip = 0; ip < processCount; ip++) {
+                    final String pname = pmap.keyAt(ip);
+                    final SparseArray<Pair<Long, Integer>> uids = pmap.valueAt(ip);
+                    final int uidCount = uids.size();
+                    for (int i = 0; i < uidCount; i++) {
+                        final int puid = uids.keyAt(i);
+                        final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
+                        if (dumpPackage != null
+                                && (r == null || !r.getPkgList().containsKey(dumpPackage))) {
+                            continue;
+                        }
+                        if (!printed) {
+                            if (needSep) pw.println();
+                            needSep = true;
+                            pw.println("  First time processes crashed and counts:");
+                            printed = true;
+                        }
+                        pw.print("    Process "); pw.print(pname);
+                        pw.print(" uid "); pw.print(puid);
+                        pw.print(": first crashed ");
+                        TimeUtils.formatDuration(now - uids.valueAt(i).first, pw);
+                        pw.print(" ago; crashes since then: "); pw.println(uids.valueAt(i).second);
                     }
-                    pw.print("    Process "); pw.print(pname);
-                    pw.print(" uid "); pw.print(puid);
-                    pw.print(": first crashed ");
-                    TimeUtils.formatDuration(now - uids.valueAt(i).first, pw);
-                    pw.print(" ago; crashes since then: "); pw.println(uids.valueAt(i).second);
                 }
             }
         }
@@ -282,9 +306,9 @@
                 final int uidCount = uids.size();
                 for (int i = 0; i < uidCount; i++) {
                     final int puid = uids.keyAt(i);
-                    final ProcessRecord r = mService.getProcessNames().get(pname, puid);
+                    final ProcessRecord r = mService.getProcessNamesLOSP().get(pname, puid);
                     if (dumpPackage != null && (r == null
-                            || !r.pkgList.containsKey(dumpPackage))) {
+                            || !r.getPkgList().containsKey(dumpPackage))) {
                         continue;
                     }
                     if (!printed) {
@@ -309,14 +333,14 @@
                         for (int pos = 0; pos < info.stack.length(); pos++) {
                             if (info.stack.charAt(pos) == '\n') {
                                 pw.print("        ");
-                                pw.write(info.stack, lastPos, pos-lastPos);
+                                pw.write(info.stack, lastPos, pos - lastPos);
                                 pw.println();
-                                lastPos = pos+1;
+                                lastPos = pos + 1;
                             }
                         }
                         if (lastPos < info.stack.length()) {
                             pw.print("        ");
-                            pw.write(info.stack, lastPos, info.stack.length()-lastPos);
+                            pw.write(info.stack, lastPos, info.stack.length() - lastPos);
                             pw.println();
                         }
                     }
@@ -349,33 +373,38 @@
         }
     }
 
-    void resetProcessCrashTimeLocked(final String processName, final int uid) {
-        mProcessCrashTimes.remove(processName, uid);
-        mProcessCrashCounts.remove(processName, uid);
+    void resetProcessCrashTime(final String processName, final int uid) {
+        synchronized (mBadProcessLock) {
+            mProcessCrashTimes.remove(processName, uid);
+            mProcessCrashCounts.remove(processName, uid);
+        }
     }
 
-    void resetProcessCrashTimeLocked(boolean resetEntireUser, int appId, int userId) {
-        final ArrayMap<String, SparseArray<Long>> pTimeMap = mProcessCrashTimes.getMap();
-        for (int ip = pTimeMap.size() - 1; ip >= 0; ip--) {
-            SparseArray<Long> ba = pTimeMap.valueAt(ip);
-            resetProcessCrashMapLocked(ba, resetEntireUser, appId, userId);
-            if (ba.size() == 0) {
-                pTimeMap.removeAt(ip);
+    void resetProcessCrashTime(boolean resetEntireUser, int appId, int userId) {
+        synchronized (mBadProcessLock) {
+            final ArrayMap<String, SparseArray<Long>> pTimeMap = mProcessCrashTimes.getMap();
+            for (int ip = pTimeMap.size() - 1; ip >= 0; ip--) {
+                SparseArray<Long> ba = pTimeMap.valueAt(ip);
+                resetProcessCrashMapLBp(ba, resetEntireUser, appId, userId);
+                if (ba.size() == 0) {
+                    pTimeMap.removeAt(ip);
+                }
             }
-        }
 
-        final ArrayMap<String, SparseArray<Pair<Long, Integer>>> pCountMap =
-                                                                    mProcessCrashCounts.getMap();
-        for (int ip = pCountMap.size() - 1; ip >= 0; ip--) {
-            SparseArray<Pair<Long, Integer>> ba = pCountMap.valueAt(ip);
-            resetProcessCrashMapLocked(ba, resetEntireUser, appId, userId);
-            if (ba.size() == 0) {
-                pCountMap.removeAt(ip);
+            final ArrayMap<String, SparseArray<Pair<Long, Integer>>> pCountMap =
+                    mProcessCrashCounts.getMap();
+            for (int ip = pCountMap.size() - 1; ip >= 0; ip--) {
+                SparseArray<Pair<Long, Integer>> ba = pCountMap.valueAt(ip);
+                resetProcessCrashMapLBp(ba, resetEntireUser, appId, userId);
+                if (ba.size() == 0) {
+                    pCountMap.removeAt(ip);
+                }
             }
         }
     }
 
-    private void resetProcessCrashMapLocked(SparseArray<?> ba, boolean resetEntireUser,
+    @GuardedBy("mBadProcessLock")
+    private void resetProcessCrashMapLBp(SparseArray<?> ba, boolean resetEntireUser,
             int appId, int userId) {
         for (int i = ba.size() - 1; i >= 0; i--) {
             boolean remove = false;
@@ -399,42 +428,50 @@
         }
     }
 
-    void loadAppsNotReportingCrashesFromConfigLocked(String appsNotReportingCrashesConfig) {
+    void loadAppsNotReportingCrashesFromConfig(String appsNotReportingCrashesConfig) {
         if (appsNotReportingCrashesConfig != null) {
             final String[] split = appsNotReportingCrashesConfig.split(",");
             if (split.length > 0) {
-                mAppsNotReportingCrashes = new ArraySet<>();
-                Collections.addAll(mAppsNotReportingCrashes, split);
+                synchronized (mBadProcessLock) {
+                    mAppsNotReportingCrashes = new ArraySet<>();
+                    Collections.addAll(mAppsNotReportingCrashes, split);
+                }
             }
         }
     }
 
+    @GuardedBy("mService")
     void killAppAtUserRequestLocked(ProcessRecord app) {
-        ProcessRecord.ErrorDialogController controller =
-                app.getDialogController();
+        ErrorDialogController controller = app.mErrorState.getDialogController();
 
         int reasonCode = ApplicationExitInfo.REASON_ANR;
         int subReason = ApplicationExitInfo.SUBREASON_UNKNOWN;
-        if (controller.hasDebugWaitingDialog()) {
-            reasonCode = ApplicationExitInfo.REASON_OTHER;
-            subReason = ApplicationExitInfo.SUBREASON_WAIT_FOR_DEBUGGER;
+        synchronized (mProcLock) {
+            if (controller.hasDebugWaitingDialog()) {
+                reasonCode = ApplicationExitInfo.REASON_OTHER;
+                subReason = ApplicationExitInfo.SUBREASON_WAIT_FOR_DEBUGGER;
+            }
+            controller.clearAllErrorDialogs();
+            killAppImmediateLSP(app, reasonCode, subReason,
+                    "user-terminated", "user request after error");
         }
-
-        controller.clearAllErrorDialogs();
-        killAppImmediateLocked(app, reasonCode, subReason,
-                "user-terminated", "user request after error");
     }
 
-    private void killAppImmediateLocked(ProcessRecord app, int reasonCode, int subReason,
+    @GuardedBy({"mService", "mProcLock"})
+    private void killAppImmediateLSP(ProcessRecord app, int reasonCode, int subReason,
             String reason, String killReason) {
-        app.setCrashing(false);
-        app.crashingReport = null;
-        app.setNotResponding(false);
-        app.notRespondingReport = null;
-        if (app.pid > 0 && app.pid != MY_PID) {
-            handleAppCrashLocked(app, reason,
-                    null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
-            app.kill(killReason, reasonCode, subReason, true);
+        final ProcessErrorStateRecord errState = app.mErrorState;
+        errState.setCrashing(false);
+        errState.setCrashingReport(null);
+        errState.setNotResponding(false);
+        errState.setNotRespondingReport(null);
+        final int pid = errState.mApp.getPid();
+        if (pid > 0 && pid != MY_PID) {
+            synchronized (mBadProcessLock) {
+                handleAppCrashLSPB(app, reason,
+                        null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
+            }
+            app.killLocked(killReason, reasonCode, subReason, true);
         }
     }
 
@@ -461,11 +498,11 @@
                 if (uid >= 0 && p.uid != uid) {
                     continue;
                 }
-                if (p.pid == initialPid) {
+                if (p.getPid() == initialPid) {
                     proc = p;
                     break;
                 }
-                if (p.pkgList.containsKey(packageName)
+                if (p.getPkgList().containsKey(packageName)
                         && (userId < 0 || p.userId == userId)) {
                     proc = p;
                 }
@@ -480,7 +517,7 @@
             return;
         }
 
-        proc.scheduleCrash(message);
+        proc.scheduleCrashLocked(message);
         if (force) {
             // If the app is responsive, the scheduled crash will happen as expected
             // and then the delayed summary kill will be a no-op.
@@ -488,9 +525,11 @@
             mService.mHandler.postDelayed(
                     () -> {
                         synchronized (mService) {
-                            killAppImmediateLocked(p, ApplicationExitInfo.REASON_OTHER,
-                                    ApplicationExitInfo.SUBREASON_INVALID_STATE,
-                                    "forced", "killed for invalid state");
+                            synchronized (mProcLock) {
+                                killAppImmediateLSP(p, ApplicationExitInfo.REASON_OTHER,
+                                        ApplicationExitInfo.SUBREASON_INVALID_STATE,
+                                        "forced", "killed for invalid state");
+                            }
                         }
                     },
                     5000L);
@@ -516,7 +555,7 @@
         }
     }
 
-    void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
+    private void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
             int callingPid, int callingUid) {
         long timeMillis = System.currentTimeMillis();
         String shortMsg = crashInfo.exceptionClassName;
@@ -599,13 +638,17 @@
         if (res == AppErrorDialog.TIMEOUT || res == AppErrorDialog.CANCEL) {
             res = AppErrorDialog.FORCE_QUIT;
         }
-        synchronized (mService) {
-            if (res == AppErrorDialog.MUTE) {
-                stopReportingCrashesLocked(r);
-            }
-            if (res == AppErrorDialog.RESTART) {
-                mService.mProcessList.removeProcessLocked(r, false, true,
-                        ApplicationExitInfo.REASON_CRASH, "crash");
+        switch (res) {
+            case AppErrorDialog.MUTE:
+                synchronized (mBadProcessLock) {
+                    stopReportingCrashesLBp(r);
+                }
+                break;
+            case AppErrorDialog.RESTART:
+                synchronized (mService) {
+                    mService.mProcessList.removeProcessLocked(r, false, true,
+                            ApplicationExitInfo.REASON_CRASH, "crash");
+                }
                 if (taskId != INVALID_TASK_ID) {
                     try {
                         mService.startActivityFromRecents(taskId,
@@ -616,29 +659,33 @@
                         Slog.e(TAG, "Could not restart taskId=" + taskId, e);
                     }
                 }
-            }
-            if (res == AppErrorDialog.FORCE_QUIT) {
+                break;
+            case AppErrorDialog.FORCE_QUIT:
                 final long orig = Binder.clearCallingIdentity();
                 try {
                     // Kill it with fire!
                     mService.mAtmInternal.onHandleAppCrash(r.getWindowProcessController());
                     if (!r.isPersistent()) {
-                        mService.mProcessList.removeProcessLocked(r, false, false,
-                                ApplicationExitInfo.REASON_CRASH, "crash");
+                        synchronized (mService) {
+                            mService.mProcessList.removeProcessLocked(r, false, false,
+                                    ApplicationExitInfo.REASON_CRASH, "crash");
+                        }
                         mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(orig);
                 }
-            }
-            if (res == AppErrorDialog.APP_INFO) {
+                break;
+            case AppErrorDialog.APP_INFO:
                 appErrorIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                 appErrorIntent.setData(Uri.parse("package:" + r.info.packageName));
                 appErrorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            }
-            if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
-                appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
-            }
+                break;
+            case AppErrorDialog.FORCE_QUIT_AND_REPORT:
+                synchronized (mProcLock) {
+                    appErrorIntent = createAppErrorIntentLOSP(r, timeMillis, crashInfo);
+                }
+                break;
         }
 
         if (appErrorIntent != null) {
@@ -650,13 +697,14 @@
         }
     }
 
+    @GuardedBy("mService")
     private boolean handleAppCrashInActivityController(ProcessRecord r,
                                                        ApplicationErrorReport.CrashInfo crashInfo,
                                                        String shortMsg, String longMsg,
                                                        String stackTrace, long timeMillis,
                                                        int callingPid, int callingUid) {
         String name = r != null ? r.processName : null;
-        int pid = r != null ? r.pid : callingPid;
+        int pid = r != null ? r.getPid() : callingPid;
         int uid = r != null ? r.info.uid : callingUid;
 
         return mService.mAtmInternal.handleAppCrashInActivityController(
@@ -669,7 +717,7 @@
                     Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request");
                     if (r != null) {
                         if (!makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, null)) {
-                            r.kill("crash", ApplicationExitInfo.REASON_CRASH, true);
+                            r.killLocked("crash", ApplicationExitInfo.REASON_CRASH, true);
                         }
                     } else {
                         // Huh.
@@ -684,15 +732,22 @@
         });
     }
 
+    @GuardedBy("mService")
     private boolean makeAppCrashingLocked(ProcessRecord app,
             String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
-        app.setCrashing(true);
-        app.crashingReport = generateProcessError(app,
-                ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
-        app.startAppProblemLocked();
-        app.getWindowProcessController().stopFreezingActivities();
-        return handleAppCrashLocked(app, "force-crash" /*reason*/, shortMsg, longMsg, stackTrace,
-                data);
+        synchronized (mProcLock) {
+            final ProcessErrorStateRecord errState = app.mErrorState;
+            errState.setCrashing(true);
+            errState.setCrashingReport(generateProcessError(app,
+                    ActivityManager.ProcessErrorStateInfo.CRASHED,
+                    null, shortMsg, longMsg, stackTrace));
+            errState.startAppProblemLSP();
+            app.getWindowProcessController().stopFreezingActivities();
+            synchronized (mBadProcessLock) {
+                return handleAppCrashLSPB(app, "force-crash" /*reason*/, shortMsg, longMsg,
+                        stackTrace, data);
+            }
+        }
     }
 
     /**
@@ -714,7 +769,7 @@
 
         report.condition = condition;
         report.processName = app.processName;
-        report.pid = app.pid;
+        report.pid = app.getPid();
         report.uid = app.info.uid;
         report.tag = activity;
         report.shortMsg = shortMsg;
@@ -724,119 +779,111 @@
         return report;
     }
 
-    Intent createAppErrorIntentLocked(ProcessRecord r,
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    Intent createAppErrorIntentLOSP(ProcessRecord r,
             long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
-        ApplicationErrorReport report = createAppErrorReportLocked(r, timeMillis, crashInfo);
+        ApplicationErrorReport report = createAppErrorReportLOSP(r, timeMillis, crashInfo);
         if (report == null) {
             return null;
         }
         Intent result = new Intent(Intent.ACTION_APP_ERROR);
-        result.setComponent(r.errorReportReceiver);
+        result.setComponent(r.mErrorState.getErrorReportReceiver());
         result.putExtra(Intent.EXTRA_BUG_REPORT, report);
         result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         return result;
     }
 
-    private ApplicationErrorReport createAppErrorReportLocked(ProcessRecord r,
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    private ApplicationErrorReport createAppErrorReportLOSP(ProcessRecord r,
             long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
-        if (r.errorReportReceiver == null) {
+        final ProcessErrorStateRecord errState = r.mErrorState;
+        if (errState.getErrorReportReceiver() == null) {
             return null;
         }
 
-        if (!r.isCrashing() && !r.isNotResponding() && !r.forceCrashReport) {
+        if (!errState.isCrashing() && !errState.isNotResponding()
+                && !errState.isForceCrashReport()) {
             return null;
         }
 
         ApplicationErrorReport report = new ApplicationErrorReport();
         report.packageName = r.info.packageName;
-        report.installerPackageName = r.errorReportReceiver.getPackageName();
+        report.installerPackageName = errState.getErrorReportReceiver().getPackageName();
         report.processName = r.processName;
         report.time = timeMillis;
         report.systemApp = (r.info.flags & FLAG_SYSTEM) != 0;
 
-        if (r.isCrashing() || r.forceCrashReport) {
+        if (errState.isCrashing() || errState.isForceCrashReport()) {
             report.type = ApplicationErrorReport.TYPE_CRASH;
             report.crashInfo = crashInfo;
-        } else if (r.isNotResponding()) {
+        } else if (errState.isNotResponding()) {
             report.type = ApplicationErrorReport.TYPE_ANR;
             report.anrInfo = new ApplicationErrorReport.AnrInfo();
 
-            report.anrInfo.activity = r.notRespondingReport.tag;
-            report.anrInfo.cause = r.notRespondingReport.shortMsg;
-            report.anrInfo.info = r.notRespondingReport.longMsg;
+            report.anrInfo.activity = errState.getNotRespondingReport().tag;
+            report.anrInfo.cause = errState.getNotRespondingReport().shortMsg;
+            report.anrInfo.info = errState.getNotRespondingReport().longMsg;
         }
 
         return report;
     }
 
-    boolean handleAppCrashLocked(ProcessRecord app, String reason,
+    @GuardedBy({"mService", "mProcLock", "mBadProcessLock"})
+    private boolean handleAppCrashLSPB(ProcessRecord app, String reason,
             String shortMsg, String longMsg, String stackTrace, AppErrorDialog.Data data) {
         final long now = SystemClock.uptimeMillis();
         final boolean showBackground = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.ANR_SHOW_BACKGROUND, 0,
                 mService.mUserController.getCurrentUserId()) != 0;
 
-        final boolean procIsBoundForeground =
-            (app.getCurProcState() == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
-
         Long crashTime;
         Long crashTimePersistent;
-        boolean tryAgain = false;
+        final String processName = app.processName;
+        final int uid = app.uid;
+        final int userId = app.userId;
+        final boolean isolated = app.isolated;
+        final boolean persistent = app.isPersistent();
+        final WindowProcessController proc = app.getWindowProcessController();
+        final ProcessErrorStateRecord errState = app.mErrorState;
 
         if (!app.isolated) {
-            crashTime = mProcessCrashTimes.get(app.processName, app.uid);
-            crashTimePersistent = mProcessCrashTimesPersistent.get(app.processName, app.uid);
+            crashTime = mProcessCrashTimes.get(processName, uid);
+            crashTimePersistent = mProcessCrashTimesPersistent.get(processName, uid);
         } else {
             crashTime = crashTimePersistent = null;
         }
 
         // Bump up the crash count of any services currently running in the proc.
-        for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
-            // Any services running in the application need to be placed
-            // back in the pending list.
-            ServiceRecord sr = app.getRunningServiceAt(i);
-            // If the service was restarted a while ago, then reset crash count, else increment it.
-            if (now > sr.restartTime + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
-                sr.crashCount = 1;
-            } else {
-                sr.crashCount++;
-            }
-            // Allow restarting for started or bound foreground services that are crashing.
-            // This includes wallpapers.
-            if (sr.crashCount < mService.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY
-                    && (sr.isForeground || procIsBoundForeground)) {
-                tryAgain = true;
-            }
-        }
+        boolean tryAgain = app.mServices.incServiceCrashCountLocked(now);
 
         final boolean quickCrash = crashTime != null
                 && now < crashTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
-        if (quickCrash || isProcOverCrashLimit(app, now)) {
+        if (quickCrash || isProcOverCrashLimitLBp(app, now)) {
             // The process either crashed again very quickly or has been crashing periodically in
             // the last few hours. If it was a bound foreground service, let's try to restart again
             // in a while, otherwise the process loses!
-            Slog.w(TAG, "Process " + app.processName + " has crashed too many times, killing!"
+            Slog.w(TAG, "Process " + processName + " has crashed too many times, killing!"
                     + " Reason: " + (quickCrash ? "crashed quickly" : "over process crash limit"));
             EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
-                    app.userId, app.processName, app.uid);
-            mService.mAtmInternal.onHandleAppCrash(app.getWindowProcessController());
-            if (!app.isPersistent()) {
+                    userId, processName, uid);
+            mService.mAtmInternal.onHandleAppCrash(proc);
+            if (!persistent) {
                 // We don't want to start this process again until the user
                 // explicitly does so...  but for persistent process, we really
                 // need to keep it running.  If a persistent process is actually
                 // repeatedly crashing, then badness for everyone.
-                EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
-                        app.processName);
-                if (!app.isolated) {
+                EventLog.writeEvent(EventLogTags.AM_PROC_BAD, userId, uid,
+                        processName);
+                if (!isolated) {
                     // XXX We don't have a way to mark isolated processes
                     // as bad, since they don't have a persistent identity.
-                    markBadProcess(app.processName, app.uid,
+                    markBadProcess(processName, app.uid,
                             new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
-                    mProcessCrashTimes.remove(app.processName, app.uid);
-                    mProcessCrashCounts.remove(app.processName, app.uid);
+                    mProcessCrashTimes.remove(processName, app.uid);
+                    mProcessCrashCounts.remove(processName, app.uid);
                 }
-                app.bad = true;
-                app.removed = true;
+                errState.setBad(true);
+                app.setRemoved(true);
                 // Don't let services in this process be restarted and potentially
                 // annoy the user repeatedly.  Unless it is persistent, since those
                 // processes run critical code.
@@ -850,7 +897,7 @@
             mService.mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
         } else {
             final int affectedTaskId = mService.mAtmInternal.finishTopCrashedActivities(
-                            app.getWindowProcessController(), reason);
+                            proc, reason);
             if (data != null) {
                 data.taskId = affectedTaskId;
             }
@@ -868,24 +915,26 @@
         // replaced by a third-party app, clear the package preferred activities from packages
         // with a home activity running in the process to prevent a repeatedly crashing app
         // from blocking the user to manually clear the list.
-        final WindowProcessController proc = app.getWindowProcessController();
         if (proc.isHomeProcess() && proc.hasActivities() && (app.info.flags & FLAG_SYSTEM) == 0) {
             proc.clearPackagePreferredForHomeActivities();
         }
 
-        if (!app.isolated) {
+        if (!isolated) {
             // XXX Can't keep track of crash times for isolated processes,
             // because they don't have a persistent identity.
-            mProcessCrashTimes.put(app.processName, app.uid, now);
-            mProcessCrashTimesPersistent.put(app.processName, app.uid, now);
-            updateProcessCrashCount(app.processName, app.uid, now);
+            mProcessCrashTimes.put(processName, uid, now);
+            mProcessCrashTimesPersistent.put(processName, uid, now);
+            updateProcessCrashCountLBp(processName, uid, now);
         }
 
-        if (app.crashHandler != null) mService.mHandler.post(app.crashHandler);
+        if (errState.getCrashHandler() != null) {
+            mService.mHandler.post(errState.getCrashHandler());
+        }
         return true;
     }
 
-    private void updateProcessCrashCount(String processName, int uid, long now) {
+    @GuardedBy("mBadProcessLock")
+    private void updateProcessCrashCountLBp(String processName, int uid, long now) {
         Pair<Long, Integer> count = mProcessCrashCounts.get(processName, uid);
         if (count == null || (count.first + PROCESS_CRASH_COUNT_RESET_INTERVAL) < now) {
             count = new Pair<>(now, 1);
@@ -895,7 +944,8 @@
         mProcessCrashCounts.put(processName, uid, count);
     }
 
-    private boolean isProcOverCrashLimit(ProcessRecord app, long now) {
+    @GuardedBy("mBadProcessLock")
+    private boolean isProcOverCrashLimitLBp(ProcessRecord app, long now) {
         final Pair<Long, Integer> crashCount = mProcessCrashCounts.get(app.processName, app.uid);
         return !app.isolated && crashCount != null
                 && now < (crashCount.first + PROCESS_CRASH_COUNT_RESET_INTERVAL)
@@ -909,15 +959,16 @@
                 mService.mUserController.getCurrentUserId()) != 0;
 
         final int userId;
-        synchronized (mService) {
+        synchronized (mProcLock) {
             final ProcessRecord proc = data.proc;
             final AppErrorResult res = data.result;
             if (proc == null) {
                 Slog.e(TAG, "handleShowAppErrorUi: proc is null");
                 return;
             }
+            final ProcessErrorStateRecord errState = proc.mErrorState;
             userId = proc.userId;
-            if (proc.getDialogController().hasCrashDialogs()) {
+            if (errState.getDialogController().hasCrashDialogs()) {
                 Slog.e(TAG, "App already has crash dialog: " + proc);
                 if (res != null) {
                     res.set(AppErrorDialog.ALREADY_SHOWING);
@@ -926,7 +977,7 @@
             }
             boolean isBackground = (UserHandle.getAppId(proc.uid)
                     >= Process.FIRST_APPLICATION_UID
-                    && proc.pid != MY_PID);
+                    && proc.getPid() != MY_PID);
             for (int profileId : mService.mUserController.getCurrentProfileIds()) {
                 isBackground &= (userId != profileId);
             }
@@ -938,41 +989,44 @@
                 return;
             }
             Long crashShowErrorTime = null;
-            if (!proc.isolated) {
-                crashShowErrorTime = mProcessCrashShowDialogTimes.get(proc.processName,
-                        proc.uid);
-            }
-            final boolean showFirstCrash = Settings.Global.getInt(
-                    mContext.getContentResolver(),
-                    Settings.Global.SHOW_FIRST_CRASH_DIALOG, 0) != 0;
-            final boolean showFirstCrashDevOption = Settings.Secure.getIntForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
-                    0,
-                    mService.mUserController.getCurrentUserId()) != 0;
-            final boolean crashSilenced = mAppsNotReportingCrashes != null &&
-                    mAppsNotReportingCrashes.contains(proc.info.packageName);
-            final long now = SystemClock.uptimeMillis();
-            final boolean shouldThottle = crashShowErrorTime != null
-                    && now < crashShowErrorTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
-            if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
-                    && !crashSilenced && !shouldThottle
-                    && (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
-                proc.getDialogController().showCrashDialogs(data);
+            synchronized (mBadProcessLock) {
                 if (!proc.isolated) {
-                    mProcessCrashShowDialogTimes.put(proc.processName, proc.uid, now);
+                    crashShowErrorTime = mProcessCrashShowDialogTimes.get(proc.processName,
+                            proc.uid);
                 }
-            } else {
-                // The device is asleep, so just pretend that the user
-                // saw a crash dialog and hit "force quit".
-                if (res != null) {
-                    res.set(AppErrorDialog.CANT_SHOW);
+                final boolean showFirstCrash = Settings.Global.getInt(
+                        mContext.getContentResolver(),
+                        Settings.Global.SHOW_FIRST_CRASH_DIALOG, 0) != 0;
+                final boolean showFirstCrashDevOption = Settings.Secure.getIntForUser(
+                        mContext.getContentResolver(),
+                        Settings.Secure.SHOW_FIRST_CRASH_DIALOG_DEV_OPTION,
+                        0,
+                        mService.mUserController.getCurrentUserId()) != 0;
+                final boolean crashSilenced = mAppsNotReportingCrashes != null
+                        && mAppsNotReportingCrashes.contains(proc.info.packageName);
+                final long now = SystemClock.uptimeMillis();
+                final boolean shouldThottle = crashShowErrorTime != null
+                        && now < crashShowErrorTime + ActivityManagerConstants.MIN_CRASH_INTERVAL;
+                if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
+                        && !crashSilenced && !shouldThottle
+                        && (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
+                    errState.getDialogController().showCrashDialogs(data);
+                    if (!proc.isolated) {
+                        mProcessCrashShowDialogTimes.put(proc.processName, proc.uid, now);
+                    }
+                } else {
+                    // The device is asleep, so just pretend that the user
+                    // saw a crash dialog and hit "force quit".
+                    if (res != null) {
+                        res.set(AppErrorDialog.CANT_SHOW);
+                    }
                 }
             }
         }
     }
 
-    private void stopReportingCrashesLocked(ProcessRecord proc) {
+    @GuardedBy("mBadProcessLock")
+    private void stopReportingCrashesLBp(ProcessRecord proc) {
         if (mAppsNotReportingCrashes == null) {
             mAppsNotReportingCrashes = new ArraySet<>();
         }
@@ -981,17 +1035,19 @@
 
     void handleShowAnrUi(Message msg) {
         List<VersionedPackage> packageList = null;
-        synchronized (mService) {
-            AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
-            final ProcessRecord proc = data.proc;
-            if (proc == null) {
-                Slog.e(TAG, "handleShowAnrUi: proc is null");
-                return;
-            }
+        boolean doKill = false;
+        AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
+        final ProcessRecord proc = data.proc;
+        if (proc == null) {
+            Slog.e(TAG, "handleShowAnrUi: proc is null");
+            return;
+        }
+        synchronized (mProcLock) {
+            final ProcessErrorStateRecord errState = proc.mErrorState;
             if (!proc.isPersistent()) {
                 packageList = proc.getPackageListWithVersionCode();
             }
-            if (proc.getDialogController().hasAnrDialogs()) {
+            if (errState.getDialogController().hasAnrDialogs()) {
                 Slog.e(TAG, "App already has anr dialog: " + proc);
                 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
                         AppNotRespondingDialog.ALREADY_SHOWING);
@@ -1002,14 +1058,17 @@
                     Settings.Secure.ANR_SHOW_BACKGROUND, 0,
                     mService.mUserController.getCurrentUserId()) != 0;
             if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) {
-                proc.getDialogController().showAnrDialogs(data);
+                errState.getDialogController().showAnrDialogs(data);
             } else {
                 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
                         AppNotRespondingDialog.CANT_SHOW);
                 // Just kill the app if there is no dialog to be shown.
-                mService.killAppAtUsersRequest(proc);
+                doKill = true;
             }
         }
+        if (doKill) {
+            mService.killAppAtUsersRequest(proc);
+        }
         // Notify PackageWatchdog without the lock held
         if (packageList != null) {
             mPackageWatchdog.onPackageFailure(packageList,
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 20cad18..17be210 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -79,6 +79,7 @@
 import java.util.Date;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -159,8 +160,7 @@
      * persistent storage.
      */
     @VisibleForTesting
-    @GuardedBy("mLock")
-    boolean mAppExitInfoLoaded = false;
+    AtomicBoolean mAppExitInfoLoaded = new AtomicBoolean();
 
     /**
      * Temporary list being used to filter/sort intermediate results in {@link #getExitInfo}.
@@ -273,51 +273,45 @@
             return;
         }
 
-        synchronized (mLock) {
-            if (!mAppExitInfoLoaded) {
-                return;
-            }
-            mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecordLocked(app))
-                    .sendToTarget();
+        if (!mAppExitInfoLoaded.get()) {
+            return;
         }
+        mKillHandler.obtainMessage(KillHandler.MSG_PROC_DIED, obtainRawRecord(app))
+                .sendToTarget();
     }
 
     void scheduleNoteAppKill(final ProcessRecord app, final @Reason int reason,
             final @SubReason int subReason, final String msg) {
-        synchronized (mLock) {
-            if (!mAppExitInfoLoaded) {
-                return;
-            }
-            if (app == null || app.info == null) {
-                return;
-            }
-
-            ApplicationExitInfo raw = obtainRawRecordLocked(app);
-            raw.setReason(reason);
-            raw.setSubReason(subReason);
-            raw.setDescription(msg);
-            mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
+        if (!mAppExitInfoLoaded.get()) {
+            return;
         }
+        if (app == null || app.info == null) {
+            return;
+        }
+
+        ApplicationExitInfo raw = obtainRawRecord(app);
+        raw.setReason(reason);
+        raw.setSubReason(subReason);
+        raw.setDescription(msg);
+        mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
     }
 
     void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
             final @SubReason int subReason, final String msg) {
-        synchronized (mLock) {
-            if (!mAppExitInfoLoaded) {
-                return;
+        if (!mAppExitInfoLoaded.get()) {
+            return;
+        }
+        ProcessRecord app;
+        synchronized (mService.mPidsSelfLocked) {
+            app = mService.mPidsSelfLocked.get(pid);
+        }
+        if (app == null) {
+            if (DEBUG_PROCESSES) {
+                Slog.w(TAG, "Skipping saving the kill reason for pid " + pid
+                        + "(uid=" + uid + ") since its process record is not found");
             }
-            ProcessRecord app;
-            synchronized (mService.mPidsSelfLocked) {
-                app = mService.mPidsSelfLocked.get(pid);
-            }
-            if (app == null) {
-                if (DEBUG_PROCESSES) {
-                    Slog.w(TAG, "Skipping saving the kill reason for pid " + pid
-                            + "(uid=" + uid + ") since its process record is not found");
-                }
-            } else {
-                scheduleNoteAppKill(app, reason, subReason, msg);
-            }
+        } else {
+            scheduleNoteAppKill(app, reason, subReason, msg);
         }
     }
 
@@ -414,7 +408,7 @@
 
     @GuardedBy("mLock")
     private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
-        if (!mAppExitInfoLoaded) {
+        if (!mAppExitInfoLoaded.get()) {
             Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
             return null;
         }
@@ -627,9 +621,7 @@
     @VisibleForTesting
     void loadExistingProcessExitInfo() {
         if (!mProcExitInfoFile.canRead()) {
-            synchronized (mLock) {
-                mAppExitInfoLoaded = true;
-            }
+            mAppExitInfoLoaded.set(true);
             return;
         }
 
@@ -665,7 +657,7 @@
         }
         synchronized (mLock) {
             pruneAnrTracesIfNecessaryLocked();
-            mAppExitInfoLoaded = true;
+            mAppExitInfoLoaded.set(true);
         }
     }
 
@@ -834,7 +826,7 @@
         container.addExitInfoLocked(info);
     }
 
-    @GuardedBy("mLocked")
+    @GuardedBy("mLock")
     private void forEachPackageLocked(
             BiFunction<String, SparseArray<AppExitInfoContainer>, Integer> callback) {
         if (callback != null) {
@@ -859,7 +851,7 @@
         }
     }
 
-    @GuardedBy("mLocked")
+    @GuardedBy("mLock")
     private void removePackageLocked(String packageName, int uid, boolean removeUid, int userId) {
         if (removeUid) {
             mActiveAppStateSummary.remove(uid);
@@ -896,7 +888,7 @@
         }
     }
 
-    @GuardedBy("mLocked")
+    @GuardedBy("mLock")
     private void removeByUserIdLocked(final int userId) {
         if (userId == UserHandle.USER_ALL) {
             mData.getMap().clear();
@@ -922,35 +914,36 @@
     }
 
     @VisibleForTesting
-    @GuardedBy("mLock")
-    ApplicationExitInfo obtainRawRecordLocked(ProcessRecord app) {
+    ApplicationExitInfo obtainRawRecord(ProcessRecord app) {
         ApplicationExitInfo info = mRawRecordsPool.acquire();
         if (info == null) {
             info = new ApplicationExitInfo();
         }
 
-        final int definingUid = app.hostingRecord != null ? app.hostingRecord.getDefiningUid() : 0;
-        info.setPid(app.pid);
-        info.setRealUid(app.uid);
-        info.setPackageUid(app.info.uid);
-        info.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
-        info.setProcessName(app.processName);
-        info.setConnectionGroup(app.connectionGroup);
-        info.setPackageName(app.info.packageName);
-        info.setPackageList(app.getPackageList());
-        info.setReason(ApplicationExitInfo.REASON_UNKNOWN);
-        info.setStatus(0);
-        info.setImportance(procStateToImportance(app.setProcState));
-        info.setPss(app.lastPss);
-        info.setRss(app.mLastRss);
-        info.setTimestamp(System.currentTimeMillis());
+        synchronized (mService.mProcLock) {
+            final int definingUid = app.getHostingRecord() != null
+                    ? app.getHostingRecord().getDefiningUid() : 0;
+            info.setPid(app.getPid());
+            info.setRealUid(app.uid);
+            info.setPackageUid(app.info.uid);
+            info.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
+            info.setProcessName(app.processName);
+            info.setConnectionGroup(app.mServices.getConnectionGroup());
+            info.setPackageName(app.info.packageName);
+            info.setPackageList(app.getPackageList());
+            info.setReason(ApplicationExitInfo.REASON_UNKNOWN);
+            info.setStatus(0);
+            info.setImportance(procStateToImportance(app.mState.getSetProcState()));
+            info.setPss(app.mProfile.getLastPss());
+            info.setRss(app.mProfile.getLastRss());
+            info.setTimestamp(System.currentTimeMillis());
+        }
 
         return info;
     }
 
     @VisibleForTesting
-    @GuardedBy("mLock")
-    void recycleRawRecordLocked(ApplicationExitInfo info) {
+    void recycleRawRecord(ApplicationExitInfo info) {
         info.setProcessName(null);
         info.setDescription(null);
         info.setPackageList(null);
@@ -1549,16 +1542,16 @@
                     ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
                     synchronized (mLock) {
                         handleNoteProcessDiedLocked(raw);
-                        recycleRawRecordLocked(raw);
                     }
+                    recycleRawRecord(raw);
                 }
                 break;
                 case MSG_APP_KILL: {
                     ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
                     synchronized (mLock) {
                         handleNoteAppKillLocked(raw);
-                        recycleRawRecordLocked(raw);
                     }
+                    recycleRawRecord(raw);
                 }
                 break;
                 default:
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index dac5325..77d2898 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -63,8 +63,8 @@
                 ? data.aInfo.loadLabel(context.getPackageManager())
                 : null;
         CharSequence name2 = null;
-        if ((mProc.pkgList.size() == 1) &&
-                (name2=context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
+        if (mProc.getPkgList().size() == 1
+                && (name2 = context.getPackageManager().getApplicationLabel(mProc.info)) != null) {
             if (name1 != null) {
                 resid = com.android.internal.R.string.anr_activity_application;
             } else {
@@ -108,7 +108,7 @@
 
         final TextView report = findViewById(com.android.internal.R.id.aerr_report);
         report.setOnClickListener(this);
-        final boolean hasReceiver = mProc.errorReportReceiver != null;
+        final boolean hasReceiver = mProc.mErrorState.getErrorReportReceiver() != null;
         report.setVisibility(hasReceiver ? View.VISIBLE : View.GONE);
         final TextView close = findViewById(com.android.internal.R.id.aerr_close);
         close.setOnClickListener(this);
@@ -152,15 +152,18 @@
                     // Continue waiting for the application.
                     synchronized (mService) {
                         ProcessRecord app = mProc;
+                        final ProcessErrorStateRecord errState = app.mErrorState;
 
                         if (msg.what == WAIT_AND_REPORT) {
-                            appErrorIntent = mService.mAppErrors.createAppErrorIntentLocked(app,
+                            appErrorIntent = mService.mAppErrors.createAppErrorIntentLOSP(app,
                                     System.currentTimeMillis(), null);
                         }
 
-                        app.setNotResponding(false);
-                        app.notRespondingReport = null;
-                        app.getDialogController().clearAnrDialogs();
+                        synchronized (mService.mProcLock) {
+                            errState.setNotResponding(false);
+                            errState.setNotRespondingReport(null);
+                            errState.getDialogController().clearAnrDialogs();
+                        }
                         mService.mServices.scheduleServiceTimeoutLocked(app);
                     }
                     break;
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index cace260..8f11a5a 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -29,8 +29,22 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.DUMP_MEM_OOM_ADJ;
+import static com.android.server.am.ActivityManagerService.DUMP_MEM_OOM_LABEL;
+import static com.android.server.am.ActivityManagerService.GC_BACKGROUND_PROCESSES_MSG;
+import static com.android.server.am.ActivityManagerService.KSM_SHARED;
+import static com.android.server.am.ActivityManagerService.KSM_SHARING;
+import static com.android.server.am.ActivityManagerService.KSM_UNSHARED;
+import static com.android.server.am.ActivityManagerService.KSM_VOLATILE;
+import static com.android.server.am.ActivityManagerService.REPORT_MEM_USAGE_MSG;
+import static com.android.server.am.ActivityManagerService.appendBasicMemEntry;
+import static com.android.server.am.ActivityManagerService.appendMemBucket;
+import static com.android.server.am.ActivityManagerService.appendMemInfo;
+import static com.android.server.am.ActivityManagerService.getKsmInfo;
+import static com.android.server.am.ActivityManagerService.stringifyKBSize;
 import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.wm.ActivityTaskManagerService.DUMP_ACTIVITIES_CMD;
 
 import android.annotation.BroadcastBehavior;
 import android.annotation.NonNull;
@@ -75,16 +89,19 @@
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.server.am.LowMemDetector.MemFactor;
-import com.android.server.am.ProcessList.ProcStateMemTracker;
 import com.android.server.utils.PriorityDump;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -162,8 +179,8 @@
     /**
      * Processes we want to collect PSS data from.
      */
-    @GuardedBy("mService")
-    private final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>();
+    @GuardedBy("mProfilerLock")
+    private final ArrayList<ProcessProfileRecord> mPendingPssProfiles = new ArrayList<>();
 
     /**
      * Depth of overlapping activity-start PSS deferral notes
@@ -173,14 +190,14 @@
     /**
      * Last time we requested PSS data of all processes.
      */
-    @GuardedBy("mService")
+    @GuardedBy("mProfilerLock")
     private long mLastFullPssTime = SystemClock.uptimeMillis();
 
     /**
      * If set, the next time we collect PSS data we should do a full collection
      * with data from native processes and the kernel.
      */
-    @GuardedBy("mService")
+    @GuardedBy("mProfilerLock")
     private boolean mFullPssPending = false;
 
     /**
@@ -188,10 +205,8 @@
      * much more rapidly to try to collect better data when the tests are rapidly
      * running through apps.
      */
-    @GuardedBy("mService")
-    private boolean mTestPssMode = false;
+    private volatile boolean mTestPssMode = false;
 
-    @GuardedBy("mService")
     private final LowMemDetector mLowMemDetector;
 
     /**
@@ -223,31 +238,63 @@
     /**
      * Total time spent with RAM that has been added in the past since the last idle time.
      */
-    @GuardedBy("mService")
+    @GuardedBy("mProcLock")
     private long mLowRamTimeSinceLastIdle = 0;
 
     /**
      * If RAM is currently low, when that horrible situation started.
      */
-    @GuardedBy("mService")
+    @GuardedBy("mProcLock")
     private long mLowRamStartTime = 0;
 
     /**
+     * Last time we report a memory usage.
+     */
+    @GuardedBy("mService")
+    private long mLastMemUsageReportTime = 0;
+
+    /**
+     * List of processes that should gc as soon as things are idle.
+     */
+    @GuardedBy("mProfilerLock")
+    private final ArrayList<ProcessRecord> mProcessesToGc = new ArrayList<>();
+
+    /**
      * Stores a map of process name -> agent string. When a process is started and mAgentAppMap
      * is not null, this map is checked and the mapped agent installed during bind-time. Note:
      * A non-null agent in mProfileInfo overrides this.
      */
+    @GuardedBy("mProfilerLock")
     private @Nullable Map<String, String> mAppAgentMap = null;
 
+    @GuardedBy("mProfilerLock")
     private int mProfileType = 0;
+
+    @GuardedBy("mProfilerLock")
+    private final ProfileData mProfileData = new ProfileData();
+
+    @GuardedBy("mProfilerLock")
     private final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>();
+
+    @GuardedBy("mProfilerLock")
     private String mMemWatchDumpProcName;
+
+    @GuardedBy("mProfilerLock")
     private Uri mMemWatchDumpUri;
+
+    @GuardedBy("mProfilerLock")
     private int mMemWatchDumpPid;
+
+    @GuardedBy("mProfilerLock")
     private int mMemWatchDumpUid;
+
+    @GuardedBy("mProfilerLock")
     private boolean mMemWatchIsUserInitiated;
 
+    @GuardedBy("mService")
     boolean mHasHomeProcess;
+
+    @GuardedBy("mService")
     boolean mHasPreviousProcess;
 
     /**
@@ -263,8 +310,7 @@
     private final AtomicBoolean mProcessCpuMutexFree = new AtomicBoolean(true);
     private final CountDownLatch mProcessCpuInitLatch = new CountDownLatch(1);
 
-    private long mLastWriteTime = 0;
-    private final ProfileData mProfileData = new ProfileData();
+    private volatile long mLastWriteTime = 0;
 
     /**
      * Runtime CPU use collection thread.  This object's lock is used to
@@ -276,6 +322,17 @@
     private final Handler mBgHandler;
 
     /**
+     * The lock to guard some of the profiling data here and {@link ProcessProfileRecord}.
+     *
+     * <p>
+     * The function suffix with this lock would be "-LPf" (Locked with Profiler lock).
+     * </p>
+     */
+    final Object mProfilerLock = new Object();
+
+    final ActivityManagerGlobalLock mProcLock;
+
+    /**
      * Observe DeviceConfig changes to the PSS calculation interval
      */
     private final DeviceConfig.OnPropertiesChangedListener mPssDelayConfigListener =
@@ -359,7 +416,7 @@
     private void collectPssInBackground() {
         long start = SystemClock.uptimeMillis();
         MemInfoReader memInfo = null;
-        synchronized (mService) {
+        synchronized (mProfilerLock) {
             if (mFullPssPending) {
                 mFullPssPending = false;
                 memInfo = new MemInfoReader();
@@ -404,65 +461,67 @@
         int num = 0;
         long[] tmp = new long[3];
         do {
-            ProcessRecord proc;
+            ProcessProfileRecord profile;
             int procState;
             int statType;
             int pid = -1;
             long lastPssTime;
-            synchronized (mService) {
-                if (mPendingPssProcesses.size() <= 0) {
+            synchronized (mProfilerLock) {
+                if (mPendingPssProfiles.size() <= 0) {
                     if (mTestPssMode || DEBUG_PSS) {
                         Slog.d(TAG_PSS,
                                 "Collected pss of " + num + " processes in "
                                 + (SystemClock.uptimeMillis() - start) + "ms");
                     }
-                    mPendingPssProcesses.clear();
+                    mPendingPssProfiles.clear();
                     return;
                 }
-                proc = mPendingPssProcesses.remove(0);
-                procState = proc.pssProcState;
-                statType = proc.pssStatType;
-                lastPssTime = proc.lastPssTime;
+                profile = mPendingPssProfiles.remove(0);
+                procState = profile.getPssProcState();
+                statType = profile.getPssStatType();
+                lastPssTime = profile.getLastPssTime();
                 long now = SystemClock.uptimeMillis();
-                if (proc.thread != null && procState == proc.setProcState
+                if (profile.getThread() != null && procState == profile.getSetProcState()
                         && (lastPssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) < now) {
-                    pid = proc.pid;
+                    pid = profile.getPid();
                 } else {
-                    abortNextPssTime(proc.procStateMemTracker);
+                    profile.abortNextPssTime();
                     if (DEBUG_PSS) {
                         Slog.d(TAG_PSS, "Skipped pss collection of " + pid
                                 + ": still need "
                                 + (lastPssTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE - now)
                                 + "ms until safe");
                     }
-                    proc = null;
+                    profile = null;
                     pid = 0;
                 }
             }
-            if (proc != null) {
+            if (profile != null) {
                 long startTime = SystemClock.currentThreadTimeMillis();
                 // skip background PSS calculation of apps that are capturing
                 // camera imagery
-                final boolean usingCamera = mService.isCameraActiveForUid(proc.uid);
+                final boolean usingCamera = mService.isCameraActiveForUid(profile.mApp.uid);
                 long pss = usingCamera ? 0 : Debug.getPss(pid, tmp, null);
                 long endTime = SystemClock.currentThreadTimeMillis();
-                synchronized (mService) {
-                    if (pss != 0 && proc.thread != null && proc.setProcState == procState
-                            && proc.pid == pid && proc.lastPssTime == lastPssTime) {
+                synchronized (mProfilerLock) {
+                    if (pss != 0 && profile.getThread() != null
+                            && profile.getSetProcState() == procState
+                            && profile.getPid() == pid && profile.getLastPssTime() == lastPssTime) {
                         num++;
-                        commitNextPssTime(proc.procStateMemTracker);
-                        recordPssSampleLocked(proc, procState, pss, tmp[0], tmp[1], tmp[2],
+                        profile.commitNextPssTime();
+                        recordPssSampleLPf(profile, procState, pss, tmp[0], tmp[1], tmp[2],
                                 statType, endTime - startTime, SystemClock.uptimeMillis());
                     } else {
-                        abortNextPssTime(proc.procStateMemTracker);
+                        profile.abortNextPssTime();
                         if (DEBUG_PSS) {
                             Slog.d(TAG_PSS, "Skipped pss collection of " + pid
-                                    + ": " + (proc.thread == null ? "NO_THREAD " : "")
+                                    + ": " + (profile.getThread() == null ? "NO_THREAD " : "")
                                     + (usingCamera ? "CAMERA " : "")
-                                    + (proc.pid != pid ? "PID_CHANGED " : "")
+                                    + (profile.getPid() != pid ? "PID_CHANGED " : "")
                                     + " initState=" + procState + " curState="
-                                    + proc.setProcState + " "
-                                    + (proc.lastPssTime != lastPssTime ? "TIME_CHANGED" : ""));
+                                    + profile.getSetProcState() + " "
+                                    + (profile.getLastPssTime() != lastPssTime
+                                    ? "TIME_CHANGED" : ""));
                         }
                     }
                 }
@@ -470,73 +529,61 @@
         } while (true);
     }
 
-    private static void commitNextPssTime(ProcStateMemTracker tracker) {
-        if (tracker.mPendingMemState >= 0) {
-            tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState;
-            tracker.mScalingFactor[tracker.mPendingMemState] = tracker.mPendingScalingFactor;
-            tracker.mTotalHighestMem = tracker.mPendingHighestMemState;
-            tracker.mPendingMemState = -1;
-        }
-    }
-
-    private static void abortNextPssTime(ProcStateMemTracker tracker) {
-        tracker.mPendingMemState = -1;
-    }
-
-    @GuardedBy("mService")
-    void updateNextPssTimeLocked(int procState, ProcessRecord app, long now, boolean forceUpdate) {
+    @GuardedBy("mProfilerLock")
+    void updateNextPssTimeLPf(int procState, ProcessProfileRecord profile, long now,
+            boolean forceUpdate) {
         if (!forceUpdate) {
-            if (now <= app.nextPssTime
-                    && now <= Math.max(app.lastPssTime + ProcessList.PSS_MAX_INTERVAL,
-                    app.lastStateTime + ProcessList.minTimeFromStateChange(mTestPssMode))) {
+            if (now <= profile.getNextPssTime() && now <= Math.max(profile.getLastPssTime()
+                    + ProcessList.PSS_MAX_INTERVAL, profile.getLastStateTime()
+                    + ProcessList.minTimeFromStateChange(mTestPssMode))) {
                 // update is not due, ignore it.
                 return;
             }
-            if (!requestPssLocked(app, app.setProcState)) {
+            if (!requestPssLPf(profile, procState)) {
                 return;
             }
         }
-        app.nextPssTime = ProcessList.computeNextPssTime(procState, app.procStateMemTracker,
-                mTestPssMode, mService.mAtmInternal.isSleeping(), now);
+        profile.setNextPssTime(profile.computeNextPssTime(procState,
+                mTestPssMode, mService.mAtmInternal.isSleeping(), now));
     }
 
     /**
      * Record new PSS sample for a process.
      */
-    @GuardedBy("mService")
-    private void recordPssSampleLocked(ProcessRecord proc, int procState, long pss, long uss,
+    @GuardedBy("mProfilerLock")
+    private void recordPssSampleLPf(ProcessProfileRecord profile, int procState, long pss, long uss,
             long swapPss, long rss, int statType, long pssDuration, long now) {
-        EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024,
+        final ProcessRecord proc = profile.mApp;
+        EventLogTags.writeAmPss(
+                profile.getPid(), proc.uid, proc.processName, pss * 1024, uss * 1024,
                 swapPss * 1024, rss * 1024, statType, procState, pssDuration);
-        proc.lastPssTime = now;
-        synchronized (mService.mProcessStats.mLock) {
-            proc.baseProcessTracker.addPss(
-                    pss, uss, rss, true, statType, pssDuration, proc.pkgList.mPkgList);
-        }
-        for (int ipkg = proc.pkgList.mPkgList.size() - 1; ipkg >= 0; ipkg--) {
-            ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
+        profile.setLastPssTime(now);
+        profile.addPss(pss, uss, rss, true, statType, pssDuration);
+        proc.getPkgList().forEachPackageProcessStats(holder -> {
             FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
                     proc.info.uid,
                     holder.state.getName(),
                     holder.state.getPackage(),
-                    pss, uss, rss, statType, pssDuration,
+                    pss, uss, rss,
+                    statType, pssDuration,
                     holder.appVersion);
-        }
+        });
         if (DEBUG_PSS) {
             Slog.d(TAG_PSS,
-                    "pss of " + proc.toShortString() + ": " + pss + " lastPss=" + proc.lastPss
+                    "pss of " + proc.toShortString() + ": " + pss
+                    + " lastPss=" + profile.getLastPss()
                     + " state=" + ProcessList.makeProcStateString(procState));
         }
-        if (proc.initialIdlePss == 0) {
-            proc.initialIdlePss = pss;
+        if (profile.getInitialIdlePss() == 0) {
+            profile.setInitialIdlePss(pss);
         }
-        proc.lastPss = pss;
-        proc.lastSwapPss = swapPss;
+        profile.setLastPss(pss);
+        profile.setLastSwapPss(swapPss);
         if (procState >= ActivityManager.PROCESS_STATE_HOME) {
-            proc.lastCachedPss = pss;
-            proc.lastCachedSwapPss = swapPss;
+            profile.setLastCachedPss(pss);
+            profile.setLastCachedSwapPss(swapPss);
         }
-        proc.mLastRss = rss;
+        profile.setLastRss(rss);
 
         final SparseArray<Pair<Long, String>> watchUids =
                 mMemWatchProcesses.getMap().get(proc.processName);
@@ -551,7 +598,8 @@
             }
         }
         if (check != null) {
-            if ((pss * 1024) >= check && proc.thread != null && mMemWatchDumpProcName == null) {
+            if ((pss * 1024) >= check && profile.getThread() != null
+                    && mMemWatchDumpProcName == null) {
                 boolean isDebuggable = Build.IS_DEBUGGABLE;
                 if (!isDebuggable) {
                     if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
@@ -560,7 +608,7 @@
                 }
                 if (isDebuggable) {
                     Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check + "; reporting");
-                    startHeapDumpLocked(proc, false);
+                    startHeapDumpLPf(profile, false);
                 } else {
                     Slog.w(TAG, "Process " + proc + " exceeded pss limit " + check
                             + ", but debugging not enabled");
@@ -570,12 +618,13 @@
     }
 
     private final class RecordPssRunnable implements Runnable {
-        private final ProcessRecord mProc;
+        private final ProcessProfileRecord mProfile;
         private final Uri mDumpUri;
         private final ContentResolver mContentResolver;
 
-        RecordPssRunnable(ProcessRecord proc, Uri dumpUri, ContentResolver contentResolver) {
-            mProc = proc;
+        RecordPssRunnable(ProcessProfileRecord profile, Uri dumpUri,
+                ContentResolver contentResolver) {
+            mProfile = profile;
             mDumpUri = dumpUri;
             mContentResolver = contentResolver;
         }
@@ -583,12 +632,12 @@
         @Override
         public void run() {
             try (ParcelFileDescriptor fd = mContentResolver.openFileDescriptor(mDumpUri, "rw")) {
-                IApplicationThread thread = mProc.thread;
+                IApplicationThread thread = mProfile.getThread();
                 if (thread != null) {
                     try {
                         if (DEBUG_PSS) {
                             Slog.d(TAG_PSS, "Requesting dump heap from "
-                                    + mProc + " to " + mDumpUri.getPath());
+                                    + mProfile.mApp + " to " + mDumpUri.getPath());
                         }
                         thread.dumpHeap(/* managed= */ true,
                                 /* mallocInfo= */ false, /* runGc= */ false,
@@ -601,16 +650,17 @@
                 Slog.e(TAG, "Failed to dump heap", e);
                 // Need to clear the heap dump variables, otherwise no further heap dumps will be
                 // attempted.
-                abortHeapDump(mProc.processName);
+                abortHeapDump(mProfile.mApp.processName);
             }
         }
     }
 
-    @GuardedBy("mService")
-    void startHeapDumpLocked(ProcessRecord proc, boolean isUserInitiated) {
+    @GuardedBy("mProfilerLock")
+    void startHeapDumpLPf(ProcessProfileRecord profile, boolean isUserInitiated) {
+        final ProcessRecord proc = profile.mApp;
         mMemWatchDumpProcName = proc.processName;
         mMemWatchDumpUri = makeHeapDumpUri(proc.processName);
-        mMemWatchDumpPid = proc.pid;
+        mMemWatchDumpPid = profile.getPid();
         mMemWatchDumpUid = proc.uid;
         mMemWatchIsUserInitiated = isUserInitiated;
         Context ctx;
@@ -621,11 +671,11 @@
             throw new RuntimeException("android package not found.");
         }
         BackgroundThread.getHandler().post(
-                new RecordPssRunnable(proc, mMemWatchDumpUri, ctx.getContentResolver()));
+                new RecordPssRunnable(profile, mMemWatchDumpUri, ctx.getContentResolver()));
     }
 
     void dumpHeapFinished(String path, int callerPid) {
-        synchronized (mService) {
+        synchronized (mProfilerLock) {
             if (callerPid != mMemWatchDumpPid) {
                 Slog.w(TAG, "dumpHeapFinished: Calling pid " + Binder.getCallingPid()
                         + " does not match last pid " + mMemWatchDumpPid);
@@ -651,7 +701,7 @@
         final long memLimit;
         final String reportPackage;
         final boolean isUserInitiated;
-        synchronized (mService) {
+        synchronized (mProfilerLock) {
             uid = mMemWatchDumpUid;
             procName = mMemWatchDumpProcName;
             Pair<Long, String> val = mMemWatchProcesses.get(procName, uid);
@@ -695,7 +745,7 @@
 
     void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize,
             String reportPackage) {
-        synchronized (mService) {
+        synchronized (mProfilerLock) {
             if (maxMemSize > 0) {
                 mMemWatchProcesses.put(processName, uid, new Pair(maxMemSize, reportPackage));
             } else {
@@ -717,7 +767,7 @@
 
     void handleAbortDumpHeap(String procName) {
         if (procName != null) {
-            synchronized (mService) {
+            synchronized (mProfilerLock) {
                 if (procName.equals(mMemWatchDumpProcName)) {
                     mMemWatchDumpProcName = null;
                     mMemWatchDumpUri = null;
@@ -736,24 +786,24 @@
     /**
      * Schedule PSS collection of a process.
      */
-    @GuardedBy("mService")
-    private boolean requestPssLocked(ProcessRecord proc, int procState) {
-        if (mPendingPssProcesses.contains(proc)) {
+    @GuardedBy("mProfilerLock")
+    private boolean requestPssLPf(ProcessProfileRecord profile, int procState) {
+        if (mPendingPssProfiles.contains(profile)) {
             return false;
         }
-        if (mPendingPssProcesses.size() == 0) {
+        if (mPendingPssProfiles.size() == 0) {
             final long deferral = (mPssDeferralTime > 0 && mActivityStartingNesting.get() > 0)
                     ? mPssDeferralTime : 0;
             if (DEBUG_PSS && deferral > 0) {
-                Slog.d(TAG_PSS, "requestPssLocked() deferring PSS request by "
+                Slog.d(TAG_PSS, "requestPssLPf() deferring PSS request by "
                         + deferral + " ms");
             }
             mBgHandler.sendEmptyMessageDelayed(BgHandler.COLLECT_PSS_BG_MSG, deferral);
         }
-        if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + proc);
-        proc.pssProcState = procState;
-        proc.pssStatType = ProcessStats.ADD_PSS_INTERNAL_SINGLE;
-        mPendingPssProcesses.add(proc);
+        if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + profile.mApp);
+        profile.setPssProcState(procState);
+        profile.setPssStatType(ProcessStats.ADD_PSS_INTERNAL_SINGLE);
+        mPendingPssProfiles.add(profile);
         return true;
     }
 
@@ -761,9 +811,9 @@
      * Re-defer a posted PSS collection pass, if one exists.  Assumes deferral is
      * currently active policy when called.
      */
-    @GuardedBy("mService")
-    private void deferPssIfNeededLocked() {
-        if (mPendingPssProcesses.size() > 0) {
+    @GuardedBy("mProfilerLock")
+    private void deferPssIfNeededLPf() {
+        if (mPendingPssProfiles.size() > 0) {
             mBgHandler.removeMessages(BgHandler.COLLECT_PSS_BG_MSG);
             mBgHandler.sendEmptyMessageDelayed(BgHandler.COLLECT_PSS_BG_MSG, mPssDeferralTime);
         }
@@ -774,7 +824,9 @@
             if (DEBUG_PSS) {
                 Slog.d(TAG_PSS, "Deferring PSS collection for activity start");
             }
-            deferPssIfNeededLocked();
+            synchronized (mProfilerLock) {
+                deferPssIfNeededLPf();
+            }
             mActivityStartingNesting.getAndIncrement();
             mBgHandler.sendEmptyMessageDelayed(BgHandler.STOP_DEFERRING_PSS_MSG, mPssDeferralTime);
         }
@@ -801,58 +853,61 @@
     /**
      * Schedule PSS collection of all processes.
      */
-    @GuardedBy("mService")
-    void requestPssAllProcsLocked(long now, boolean always, boolean memLowered) {
-        if (!always) {
-            if (now < (mLastFullPssTime
-                    + (memLowered ? mService.mConstants.FULL_PSS_LOWERED_INTERVAL
-                    : mService.mConstants.FULL_PSS_MIN_INTERVAL))) {
-                return;
+    @GuardedBy("mProcLock")
+    void requestPssAllProcsLPr(long now, boolean always, boolean memLowered) {
+        synchronized (mProfilerLock) {
+            if (!always) {
+                if (now < (mLastFullPssTime
+                            + (memLowered ? mService.mConstants.FULL_PSS_LOWERED_INTERVAL
+                                : mService.mConstants.FULL_PSS_MIN_INTERVAL))) {
+                    return;
+                }
             }
-        }
-        if (DEBUG_PSS) {
-            Slog.d(TAG_PSS, "Requesting pss of all procs!  memLowered=" + memLowered);
-        }
-        mLastFullPssTime = now;
-        mFullPssPending = true;
-        for (int i = mPendingPssProcesses.size() - 1; i >= 0; i--) {
-            abortNextPssTime(mPendingPssProcesses.get(i).procStateMemTracker);
-        }
-        mPendingPssProcesses.ensureCapacity(mService.mProcessList.getLruSizeLocked());
-        mPendingPssProcesses.clear();
-        for (int i = mService.mProcessList.getLruSizeLocked() - 1; i >= 0; i--) {
-            ProcessRecord app = mService.mProcessList.mLruProcesses.get(i);
-            if (app.thread == null || app.getCurProcState() == PROCESS_STATE_NONEXISTENT) {
-                continue;
+            if (DEBUG_PSS) {
+                Slog.d(TAG_PSS, "Requesting pss of all procs!  memLowered=" + memLowered);
             }
-            if (memLowered || (always && now
-                    > app.lastStateTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE)
-                    || now > (app.lastStateTime + ProcessList.PSS_ALL_INTERVAL)) {
-                app.pssProcState = app.setProcState;
-                app.pssStatType = always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL
-                        : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM;
-                updateNextPssTimeLocked(app.getCurProcState(), app, now, true);
-                mPendingPssProcesses.add(app);
+            mLastFullPssTime = now;
+            mFullPssPending = true;
+            for (int i = mPendingPssProfiles.size() - 1; i >= 0; i--) {
+                mPendingPssProfiles.get(i).abortNextPssTime();
             }
-        }
-        if (!mBgHandler.hasMessages(BgHandler.COLLECT_PSS_BG_MSG)) {
-            mBgHandler.sendEmptyMessage(BgHandler.COLLECT_PSS_BG_MSG);
+            mPendingPssProfiles.ensureCapacity(mService.mProcessList.getLruSizeLOSP());
+            mPendingPssProfiles.clear();
+            mService.mProcessList.forEachLruProcessesLOSP(false, app -> {
+                final ProcessProfileRecord profile = app.mProfile;
+                if (profile.getThread() == null
+                        || profile.getSetProcState() == PROCESS_STATE_NONEXISTENT) {
+                    return;
+                }
+                final long lastStateTime = profile.getLastStateTime();
+                if (memLowered || (always
+                            && now > lastStateTime + ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE)
+                        || now > (lastStateTime + ProcessList.PSS_ALL_INTERVAL)) {
+                    profile.setPssProcState(profile.getSetProcState());
+                    profile.setPssStatType(always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL
+                            : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM);
+                    updateNextPssTimeLPf(profile.getSetProcState(), profile, now, true);
+                    mPendingPssProfiles.add(profile);
+                }
+            });
+            if (!mBgHandler.hasMessages(BgHandler.COLLECT_PSS_BG_MSG)) {
+                mBgHandler.sendEmptyMessage(BgHandler.COLLECT_PSS_BG_MSG);
+            }
         }
     }
 
     void setTestPssMode(boolean enabled) {
-        synchronized (mService) {
+        synchronized (mProcLock) {
             mTestPssMode = enabled;
             if (enabled) {
                 // Whenever we enable the mode, we want to take a snapshot all of current
                 // process mem use.
-                requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, true);
+                requestPssAllProcsLPr(SystemClock.uptimeMillis(), true, true);
             }
         }
     }
 
-    @GuardedBy("mService")
-    boolean getTestPssModeLocked() {
+    boolean getTestPssMode() {
         return mTestPssMode;
     }
 
@@ -866,8 +921,8 @@
         return mLastMemoryLevel <= ADJ_MEM_FACTOR_NORMAL;
     }
 
-    @GuardedBy("mService")
-    void updateLowRamTimestampLocked(long now) {
+    @GuardedBy("mProcLock")
+    void updateLowRamTimestampLPr(long now) {
         mLowRamTimeSinceLastIdle = 0;
         if (mLowRamStartTime != 0) {
             mLowRamStartTime = now;
@@ -884,9 +939,8 @@
         mMemFactorOverride = factor;
     }
 
-    @GuardedBy("mService")
-    boolean updateLowMemStateLocked(int numCached, int numEmpty, int numTrimming) {
-        final int numOfLru = mService.mProcessList.getLruSizeLocked();
+    @GuardedBy({"mService", "mProcLock"})
+    boolean updateLowMemStateLSP(int numCached, int numEmpty, int numTrimming) {
         final long now = SystemClock.uptimeMillis();
         int memFactor;
         if (mLowMemDetector != null && mLowMemDetector.isAvailable()) {
@@ -918,7 +972,7 @@
         if (DEBUG_OOM_ADJ) {
             Slog.d(TAG_OOM_ADJ, "oom: memFactor=" + memFactor + " override=" + mMemFactorOverride
                     + " last=" + mLastMemoryLevel + " allowLow=" + mAllowLowerMemLevel
-                    + " numProcs=" + mService.mProcessList.getLruSizeLocked()
+                    + " numProcs=" + mService.mProcessList.getLruSizeLOSP()
                     + " last=" + mLastNumProcesses);
         }
         boolean override;
@@ -927,7 +981,7 @@
         }
         if (memFactor > mLastMemoryLevel) {
             if (!override && (!mAllowLowerMemLevel
-                    || mService.mProcessList.getLruSizeLocked() >= mLastNumProcesses)) {
+                    || mService.mProcessList.getLruSizeLOSP() >= mLastNumProcesses)) {
                 memFactor = mLastMemoryLevel;
                 if (DEBUG_OOM_ADJ) Slog.d(TAG_OOM_ADJ, "Keeping last mem factor!");
             }
@@ -937,7 +991,7 @@
             FrameworkStatsLog.write(FrameworkStatsLog.MEMORY_FACTOR_STATE_CHANGED, memFactor);
         }
         mLastMemoryLevel = memFactor;
-        mLastNumProcesses = mService.mProcessList.getLruSizeLocked();
+        mLastNumProcesses = mService.mProcessList.getLruSizeLOSP();
         boolean allChanged;
         int trackerMemFactor;
         synchronized (mService.mProcessStats.mLock) {
@@ -949,7 +1003,6 @@
             if (mLowRamStartTime == 0) {
                 mLowRamStartTime = now;
             }
-            int step = 0;
             int fgTrimLevel;
             switch (memFactor) {
                 case ADJ_MEM_FACTOR_CRITICAL:
@@ -967,145 +1020,587 @@
             if (mHasHomeProcess) minFactor++;
             if (mHasPreviousProcess) minFactor++;
             if (factor < minFactor) factor = minFactor;
-            int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
-            for (int i = 0; i < numOfLru; i++) {
-                ProcessRecord app = mService.mProcessList.mLruProcesses.get(i);
-                if (allChanged || app.procStateChanged) {
-                    mService.setProcessTrackerStateLocked(app, trackerMemFactor, now);
-                    app.procStateChanged = false;
+            final int actualFactor = factor;
+            final int[] step = {0};
+            final int[] curLevel = {ComponentCallbacks2.TRIM_MEMORY_COMPLETE};
+            mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
+                final ProcessProfileRecord profile = app.mProfile;
+                final int trimMemoryLevel = profile.getTrimMemoryLevel();
+                final ProcessStateRecord state = app.mState;
+                final int curProcState = state.getCurProcState();
+                IApplicationThread thread;
+                if (allChanged || state.hasProcStateChanged()) {
+                    mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);
+                    state.setProcStateChanged(false);
                 }
-                if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
-                        && !app.killedByAm) {
-                    if (app.trimMemoryLevel < curLevel && app.thread != null) {
+                if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {
+                    if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) {
                         try {
                             if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
                                 Slog.v(TAG_OOM_ADJ,
                                         "Trimming memory of " + app.processName
-                                        + " to " + curLevel);
+                                        + " to " + curLevel[0]);
                             }
-                            app.thread.scheduleTrimMemory(curLevel);
+                            thread.scheduleTrimMemory(curLevel[0]);
                         } catch (RemoteException e) {
                         }
                     }
-                    app.trimMemoryLevel = curLevel;
-                    step++;
-                    if (step >= factor) {
-                        step = 0;
-                        switch (curLevel) {
+                    profile.setTrimMemoryLevel(curLevel[0]);
+                    step[0]++;
+                    if (step[0] >= actualFactor) {
+                        step[0] = 0;
+                        switch (curLevel[0]) {
                             case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
-                                curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
+                                curLevel[0] = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
                                 break;
                             case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
-                                curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+                                curLevel[0] = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
                                 break;
                         }
                     }
-                } else if (app.getCurProcState() == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
-                        && !app.killedByAm) {
-                    if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
-                            && app.thread != null) {
+                } else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+                        && !app.isKilledByAm()) {
+                    if (trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
+                            && (thread = app.getThread()) != null) {
                         try {
                             if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
                                 Slog.v(TAG_OOM_ADJ,
                                         "Trimming memory of heavy-weight " + app.processName
                                         + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
                             }
-                            app.thread.scheduleTrimMemory(
+                            thread.scheduleTrimMemory(
                                     ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
                         } catch (RemoteException e) {
                         }
                     }
-                    app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+                    profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
                 } else {
-                    if ((app.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
-                            || app.systemNoUi) && app.hasPendingUiClean()) {
+                    if ((curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+                                || state.isSystemNoUi()) && profile.hasPendingUiClean()) {
                         // If this application is now in the background and it
                         // had done UI, then give it the special trim level to
                         // have it free UI resources.
                         final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
-                        if (app.trimMemoryLevel < level && app.thread != null) {
+                        if (trimMemoryLevel < level && (thread = app.getThread()) != null) {
                             try {
                                 if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
                                     Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
                                             + app.processName + " to " + level);
                                 }
-                                app.thread.scheduleTrimMemory(level);
+                                thread.scheduleTrimMemory(level);
                             } catch (RemoteException e) {
                             }
                         }
-                        app.setPendingUiClean(false);
+                        profile.setPendingUiClean(false);
                     }
-                    if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) {
+                    if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) {
                         try {
                             if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
                                 Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName
                                         + " to " + fgTrimLevel);
                             }
-                            app.thread.scheduleTrimMemory(fgTrimLevel);
+                            thread.scheduleTrimMemory(fgTrimLevel);
                         } catch (RemoteException e) {
                         }
                     }
-                    app.trimMemoryLevel = fgTrimLevel;
+                    profile.setTrimMemoryLevel(fgTrimLevel);
                 }
-            }
+            });
         } else {
             if (mLowRamStartTime != 0) {
                 mLowRamTimeSinceLastIdle += now - mLowRamStartTime;
                 mLowRamStartTime = 0;
             }
-            for (int i = 0; i < numOfLru; i++) {
-                ProcessRecord app = mService.mProcessList.mLruProcesses.get(i);
-                if (allChanged || app.procStateChanged) {
-                    mService.setProcessTrackerStateLocked(app, trackerMemFactor, now);
-                    app.procStateChanged = false;
+            mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
+                final ProcessProfileRecord profile = app.mProfile;
+                final IApplicationThread thread;
+                final ProcessStateRecord state = app.mState;
+                if (allChanged || state.hasProcStateChanged()) {
+                    mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);
+                    state.setProcStateChanged(false);
                 }
-                if ((app.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
-                        || app.systemNoUi) && app.hasPendingUiClean()) {
-                    if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
-                            && app.thread != null) {
+                if ((state.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+                            || state.isSystemNoUi()) && profile.hasPendingUiClean()) {
+                    if (profile.getTrimMemoryLevel() < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
+                            && (thread = app.getThread()) != null) {
                         try {
                             if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
                                 Slog.v(TAG_OOM_ADJ,
                                         "Trimming memory of ui hidden " + app.processName
                                         + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
                             }
-                            app.thread.scheduleTrimMemory(
-                                    ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+                            thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
                         } catch (RemoteException e) {
                         }
                     }
-                    app.setPendingUiClean(false);
+                    profile.setPendingUiClean(false);
                 }
-                app.trimMemoryLevel = 0;
-            }
+                profile.setTrimMemoryLevel(0);
+            });
         }
         return allChanged;
     }
 
-    @GuardedBy("mService")
-    long getLowRamTimeSinceIdleLocked(long now) {
+    @GuardedBy("mProcLock")
+    long getLowRamTimeSinceIdleLPr(long now) {
         return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0);
     }
 
+    /**
+     * Ask a given process to GC right now.
+     */
+    @GuardedBy("mProfilerLock")
+    private void performAppGcLPf(ProcessRecord app) {
+        try {
+            final ProcessProfileRecord profile = app.mProfile;
+            profile.setLastRequestedGc(SystemClock.uptimeMillis());
+            IApplicationThread thread = profile.getThread();
+            if (thread != null) {
+                if (profile.getReportLowMemory()) {
+                    profile.setReportLowMemory(false);
+                    thread.scheduleLowMemory();
+                } else {
+                    thread.processInBackground();
+                }
+            }
+        } catch (Exception e) {
+            // whatever.
+        }
+    }
+
+    /**
+     * Perform GCs on all processes that are waiting for it, but only
+     * if things are idle.
+     */
+    @GuardedBy("mProfilerLock")
+    private void performAppGcsLPf() {
+        if (mProcessesToGc.size() <= 0) {
+            return;
+        }
+        while (mProcessesToGc.size() > 0) {
+            final ProcessRecord proc = mProcessesToGc.remove(0);
+            final ProcessProfileRecord profile = proc.mProfile;
+            if (profile.getCurRawAdj() > ProcessList.PERCEPTIBLE_APP_ADJ
+                    || profile.getReportLowMemory()) {
+                if ((profile.getLastRequestedGc() + mService.mConstants.GC_MIN_INTERVAL)
+                        <= SystemClock.uptimeMillis()) {
+                    // To avoid spamming the system, we will GC processes one
+                    // at a time, waiting a few seconds between each.
+                    performAppGcLPf(proc);
+                    scheduleAppGcsLPf();
+                    return;
+                } else {
+                    // It hasn't been long enough since we last GCed this
+                    // process...  put it in the list to wait for its time.
+                    addProcessToGcListLPf(proc);
+                    break;
+                }
+            }
+        }
+
+        scheduleAppGcsLPf();
+    }
+
+    /**
+     * If all looks good, perform GCs on all processes waiting for them.
+     */
     @GuardedBy("mService")
-    private void stopProfilerLocked(ProcessRecord proc, int profileType) {
+    final void performAppGcsIfAppropriateLocked() {
+        synchronized (mProfilerLock) {
+            if (mService.canGcNowLocked()) {
+                performAppGcsLPf();
+                return;
+            }
+            // Still not idle, wait some more.
+            scheduleAppGcsLPf();
+        }
+    }
+
+    /**
+     * Schedule the execution of all pending app GCs.
+     */
+    @GuardedBy("mProfilerLock")
+    final void scheduleAppGcsLPf() {
+        mService.mHandler.removeMessages(GC_BACKGROUND_PROCESSES_MSG);
+
+        if (mProcessesToGc.size() > 0) {
+            // Schedule a GC for the time to the next process.
+            ProcessRecord proc = mProcessesToGc.get(0);
+            Message msg = mService.mHandler.obtainMessage(GC_BACKGROUND_PROCESSES_MSG);
+
+            long when = proc.mProfile.getLastRequestedGc() + mService.mConstants.GC_MIN_INTERVAL;
+            long now = SystemClock.uptimeMillis();
+            if (when < (now + mService.mConstants.GC_TIMEOUT)) {
+                when = now + mService.mConstants.GC_TIMEOUT;
+            }
+            mService.mHandler.sendMessageAtTime(msg, when);
+        }
+    }
+
+    /**
+     * Add a process to the array of processes waiting to be GCed.  Keeps the
+     * list in sorted order by the last GC time.  The process can't already be
+     * on the list.
+     */
+    @GuardedBy("mProfilerLock")
+    private void addProcessToGcListLPf(ProcessRecord proc) {
+        boolean added = false;
+        for (int i = mProcessesToGc.size() - 1; i >= 0; i--) {
+            if (mProcessesToGc.get(i).mProfile.getLastRequestedGc()
+                    < proc.mProfile.getLastRequestedGc()) {
+                added = true;
+                mProcessesToGc.add(i + 1, proc);
+                break;
+            }
+        }
+        if (!added) {
+            mProcessesToGc.add(0, proc);
+        }
+    }
+
+    @GuardedBy("mService")
+    final void doLowMemReportIfNeededLocked(ProcessRecord dyingProc) {
+        // If there are no longer any background processes running,
+        // and the app that died was not running instrumentation,
+        // then tell everyone we are now low on memory.
+        if (!mService.mProcessList.haveBackgroundProcessLOSP()) {
+            boolean doReport = Build.IS_DEBUGGABLE;
+            final long now = SystemClock.uptimeMillis();
+            if (doReport) {
+                if (now < (mLastMemUsageReportTime + 5 * 60 * 1000)) {
+                    doReport = false;
+                } else {
+                    mLastMemUsageReportTime = now;
+                }
+            }
+            final int lruSize = mService.mProcessList.getLruSizeLOSP();
+            final ArrayList<ProcessMemInfo> memInfos = doReport
+                    ? new ArrayList<ProcessMemInfo>(lruSize) : null;
+            EventLogTags.writeAmLowMemory(lruSize);
+            mService.mProcessList.forEachLruProcessesLOSP(false, rec -> {
+                if (rec == dyingProc || rec.getThread() == null) {
+                    return;
+                }
+                final ProcessStateRecord state = rec.mState;
+                if (memInfos != null) {
+                    memInfos.add(new ProcessMemInfo(rec.processName, rec.getPid(),
+                                state.getSetAdj(), state.getSetProcState(),
+                                state.getAdjType(), state.makeAdjReason()));
+                }
+                final ProcessProfileRecord profile = rec.mProfile;
+                if ((profile.getLastLowMemory() + mService.mConstants.GC_MIN_INTERVAL) <= now) {
+                    // The low memory report is overriding any current
+                    // state for a GC request.  Make sure to do
+                    // heavy/important/visible/foreground processes first.
+                    synchronized (mProfilerLock) {
+                        if (state.getSetAdj() <= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+                            profile.setLastRequestedGc(0);
+                        } else {
+                            profile.setLastRequestedGc(profile.getLastLowMemory());
+                        }
+                        profile.setReportLowMemory(true);
+                        profile.setLastLowMemory(now);
+                        mProcessesToGc.remove(rec);
+                        addProcessToGcListLPf(rec);
+                    }
+                }
+            });
+            if (doReport) {
+                Message msg = mService.mHandler.obtainMessage(REPORT_MEM_USAGE_MSG, memInfos);
+                mService.mHandler.sendMessage(msg);
+            }
+        }
+        synchronized (mProfilerLock) {
+            scheduleAppGcsLPf();
+        }
+    }
+
+    void reportMemUsage(ArrayList<ProcessMemInfo> memInfos) {
+        final SparseArray<ProcessMemInfo> infoMap = new SparseArray<>(memInfos.size());
+        for (int i = 0, size = memInfos.size(); i < size; i++) {
+            ProcessMemInfo mi = memInfos.get(i);
+            infoMap.put(mi.pid, mi);
+        }
+        updateCpuStatsNow();
+        long[] memtrackTmp = new long[1];
+        long[] swaptrackTmp = new long[2];
+        // Get a list of Stats that have vsize > 0
+        final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0);
+        final int statsCount = stats.size();
+        for (int i = 0; i < statsCount; i++) {
+            ProcessCpuTracker.Stats st = stats.get(i);
+            long pss = Debug.getPss(st.pid, swaptrackTmp, memtrackTmp);
+            if (pss > 0) {
+                if (infoMap.indexOfKey(st.pid) < 0) {
+                    ProcessMemInfo mi = new ProcessMemInfo(st.name, st.pid,
+                            ProcessList.NATIVE_ADJ, -1, "native", null);
+                    mi.pss = pss;
+                    mi.swapPss = swaptrackTmp[1];
+                    mi.memtrack = memtrackTmp[0];
+                    memInfos.add(mi);
+                }
+            }
+        }
+
+        long totalPss = 0;
+        long totalSwapPss = 0;
+        long totalMemtrack = 0;
+        for (int i = 0, size = memInfos.size(); i < size; i++) {
+            ProcessMemInfo mi = memInfos.get(i);
+            if (mi.pss == 0) {
+                mi.pss = Debug.getPss(mi.pid, swaptrackTmp, memtrackTmp);
+                mi.swapPss = swaptrackTmp[1];
+                mi.memtrack = memtrackTmp[0];
+            }
+            totalPss += mi.pss;
+            totalSwapPss += mi.swapPss;
+            totalMemtrack += mi.memtrack;
+        }
+        Collections.sort(memInfos, new Comparator<ProcessMemInfo>() {
+            @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) {
+                if (lhs.oomAdj != rhs.oomAdj) {
+                    return lhs.oomAdj < rhs.oomAdj ? -1 : 1;
+                }
+                if (lhs.pss != rhs.pss) {
+                    return lhs.pss < rhs.pss ? 1 : -1;
+                }
+                return 0;
+            }
+        });
+
+        StringBuilder tag = new StringBuilder(128);
+        StringBuilder stack = new StringBuilder(128);
+        tag.append("Low on memory -- ");
+        appendMemBucket(tag, totalPss, "total", false);
+        appendMemBucket(stack, totalPss, "total", true);
+
+        StringBuilder fullNativeBuilder = new StringBuilder(1024);
+        StringBuilder shortNativeBuilder = new StringBuilder(1024);
+        StringBuilder fullJavaBuilder = new StringBuilder(1024);
+
+        boolean firstLine = true;
+        int lastOomAdj = Integer.MIN_VALUE;
+        long extraNativeRam = 0;
+        long extraNativeMemtrack = 0;
+        long cachedPss = 0;
+        for (int i = 0, size = memInfos.size(); i < size; i++) {
+            ProcessMemInfo mi = memInfos.get(i);
+
+            if (mi.oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                cachedPss += mi.pss;
+            }
+
+            if (mi.oomAdj != ProcessList.NATIVE_ADJ
+                    && (mi.oomAdj < ProcessList.SERVICE_ADJ
+                            || mi.oomAdj == ProcessList.HOME_APP_ADJ
+                            || mi.oomAdj == ProcessList.PREVIOUS_APP_ADJ)) {
+                if (lastOomAdj != mi.oomAdj) {
+                    lastOomAdj = mi.oomAdj;
+                    if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+                        tag.append(" / ");
+                    }
+                    if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ) {
+                        if (firstLine) {
+                            stack.append(":");
+                            firstLine = false;
+                        }
+                        stack.append("\n\t at ");
+                    } else {
+                        stack.append("$");
+                    }
+                } else {
+                    tag.append(" ");
+                    stack.append("$");
+                }
+                if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+                    appendMemBucket(tag, mi.pss, mi.name, false);
+                }
+                appendMemBucket(stack, mi.pss, mi.name, true);
+                if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ
+                        && ((i + 1) >= size || memInfos.get(i + 1).oomAdj != lastOomAdj)) {
+                    stack.append("(");
+                    for (int k = 0; k < DUMP_MEM_OOM_ADJ.length; k++) {
+                        if (DUMP_MEM_OOM_ADJ[k] == mi.oomAdj) {
+                            stack.append(DUMP_MEM_OOM_LABEL[k]);
+                            stack.append(":");
+                            stack.append(DUMP_MEM_OOM_ADJ[k]);
+                        }
+                    }
+                    stack.append(")");
+                }
+            }
+
+            appendMemInfo(fullNativeBuilder, mi);
+            if (mi.oomAdj == ProcessList.NATIVE_ADJ) {
+                // The short form only has native processes that are >= 512K.
+                if (mi.pss >= 512) {
+                    appendMemInfo(shortNativeBuilder, mi);
+                } else {
+                    extraNativeRam += mi.pss;
+                    extraNativeMemtrack += mi.memtrack;
+                }
+            } else {
+                // Short form has all other details, but if we have collected RAM
+                // from smaller native processes let's dump a summary of that.
+                if (extraNativeRam > 0) {
+                    appendBasicMemEntry(shortNativeBuilder, ProcessList.NATIVE_ADJ,
+                            -1, extraNativeRam, extraNativeMemtrack, "(Other native)");
+                    shortNativeBuilder.append('\n');
+                    extraNativeRam = 0;
+                }
+                appendMemInfo(fullJavaBuilder, mi);
+            }
+        }
+
+        fullJavaBuilder.append("           ");
+        ProcessList.appendRamKb(fullJavaBuilder, totalPss);
+        fullJavaBuilder.append(": TOTAL");
+        if (totalMemtrack > 0) {
+            fullJavaBuilder.append(" (");
+            fullJavaBuilder.append(stringifyKBSize(totalMemtrack));
+            fullJavaBuilder.append(" memtrack)");
+        }
+        fullJavaBuilder.append("\n");
+
+        MemInfoReader memInfo = new MemInfoReader();
+        memInfo.readMemInfo();
+        final long[] infos = memInfo.getRawInfo();
+
+        StringBuilder memInfoBuilder = new StringBuilder(1024);
+        Debug.getMemInfo(infos);
+        memInfoBuilder.append("  MemInfo: ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SLAB])).append(" slab, ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SHMEM])).append(" shmem, ");
+        memInfoBuilder.append(stringifyKBSize(
+                                  infos[Debug.MEMINFO_VM_ALLOC_USED])).append(" vm alloc, ");
+        memInfoBuilder.append(stringifyKBSize(
+                                  infos[Debug.MEMINFO_PAGE_TABLES])).append(" page tables ");
+        memInfoBuilder.append(stringifyKBSize(
+                                  infos[Debug.MEMINFO_KERNEL_STACK])).append(" kernel stack\n");
+        memInfoBuilder.append("           ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_BUFFERS])).append(" buffers, ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_CACHED])).append(" cached, ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_MAPPED])).append(" mapped, ");
+        memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_FREE])).append(" free\n");
+        if (infos[Debug.MEMINFO_ZRAM_TOTAL] != 0) {
+            memInfoBuilder.append("  ZRAM: ");
+            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_ZRAM_TOTAL]));
+            memInfoBuilder.append(" RAM, ");
+            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SWAP_TOTAL]));
+            memInfoBuilder.append(" swap total, ");
+            memInfoBuilder.append(stringifyKBSize(infos[Debug.MEMINFO_SWAP_FREE]));
+            memInfoBuilder.append(" swap free\n");
+        }
+        final long[] ksm = getKsmInfo();
+        if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0
+                || ksm[KSM_VOLATILE] != 0) {
+            memInfoBuilder.append("  KSM: ");
+            memInfoBuilder.append(stringifyKBSize(ksm[KSM_SHARING]));
+            memInfoBuilder.append(" saved from shared ");
+            memInfoBuilder.append(stringifyKBSize(ksm[KSM_SHARED]));
+            memInfoBuilder.append("\n       ");
+            memInfoBuilder.append(stringifyKBSize(ksm[KSM_UNSHARED]));
+            memInfoBuilder.append(" unshared; ");
+            memInfoBuilder.append(stringifyKBSize(ksm[KSM_VOLATILE]));
+            memInfoBuilder.append(" volatile\n");
+        }
+        memInfoBuilder.append("  Free RAM: ");
+        memInfoBuilder.append(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb()
+                + memInfo.getFreeSizeKb()));
+        memInfoBuilder.append("\n");
+        long kernelUsed = memInfo.getKernelUsedSizeKb();
+        final long ionHeap = Debug.getIonHeapsSizeKb();
+        final long ionPool = Debug.getIonPoolsSizeKb();
+        if (ionHeap >= 0 && ionPool >= 0) {
+            final long ionMapped = Debug.getIonMappedSizeKb();
+            final long ionUnmapped = ionHeap - ionMapped;
+            memInfoBuilder.append("       ION: ");
+            memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
+            memInfoBuilder.append("\n");
+            // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
+            // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
+            kernelUsed += ionHeap;
+        }
+        final long gpuUsage = Debug.getGpuTotalUsageKb();
+        if (gpuUsage >= 0) {
+            memInfoBuilder.append("       GPU: ");
+            memInfoBuilder.append(stringifyKBSize(gpuUsage));
+            memInfoBuilder.append("\n");
+        }
+        memInfoBuilder.append("  Used RAM: ");
+        memInfoBuilder.append(stringifyKBSize(
+                                  totalPss - cachedPss + kernelUsed));
+        memInfoBuilder.append("\n");
+        memInfoBuilder.append("  Lost RAM: ");
+        memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
+                - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
+                - kernelUsed - memInfo.getZramTotalSizeKb()));
+        memInfoBuilder.append("\n");
+        Slog.i(TAG, "Low on memory:");
+        Slog.i(TAG, shortNativeBuilder.toString());
+        Slog.i(TAG, fullJavaBuilder.toString());
+        Slog.i(TAG, memInfoBuilder.toString());
+
+        StringBuilder dropBuilder = new StringBuilder(1024);
+        dropBuilder.append("Low on memory:");
+        dropBuilder.append(stack);
+        dropBuilder.append('\n');
+        dropBuilder.append(fullNativeBuilder);
+        dropBuilder.append(fullJavaBuilder);
+        dropBuilder.append('\n');
+        dropBuilder.append(memInfoBuilder);
+        dropBuilder.append('\n');
+        StringWriter catSw = new StringWriter();
+        synchronized (mService) {
+            PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
+            String[] emptyArgs = new String[] { };
+            catPw.println();
+            synchronized (mProcLock) {
+                mService.mProcessList.dumpProcessesLSP(null, catPw, emptyArgs, 0, false, null, -1);
+            }
+            catPw.println();
+            mService.mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
+                    false, null).dumpLocked();
+            catPw.println();
+            mService.mAtmInternal.dump(
+                    DUMP_ACTIVITIES_CMD, null, catPw, emptyArgs, 0, false, false, null);
+            catPw.flush();
+        }
+        dropBuilder.append(catSw.toString());
+        FrameworkStatsLog.write(FrameworkStatsLog.LOW_MEM_REPORTED);
+        mService.addErrorToDropBox("lowmem", null, "system_server", null,
+                null, null, tag.toString(), dropBuilder.toString(), null, null);
+        synchronized (mService) {
+            long now = SystemClock.uptimeMillis();
+            if (mLastMemUsageReportTime < now) {
+                mLastMemUsageReportTime = now;
+            }
+        }
+    }
+
+    @GuardedBy("mProfilerLock")
+    private void stopProfilerLPf(ProcessRecord proc, int profileType) {
         if (proc == null || proc == mProfileData.getProfileProc()) {
             proc = mProfileData.getProfileProc();
             profileType = mProfileType;
-            clearProfilerLocked();
+            clearProfilerLPf();
         }
         if (proc == null) {
             return;
         }
+        final IApplicationThread thread = proc.mProfile.getThread();
+        if (thread == null) {
+            return;
+        }
         try {
-            proc.thread.profilerControl(false, null, profileType);
+            thread.profilerControl(false, null, profileType);
         } catch (RemoteException e) {
             throw new IllegalStateException("Process disappeared");
         }
     }
 
-    @GuardedBy("mService")
-    void clearProfilerLocked() {
+    @GuardedBy("mProfilerLock")
+    void clearProfilerLPf() {
         if (mProfileData.getProfilerInfo() != null
                 && mProfileData.getProfilerInfo().profileFd != null) {
             try {
@@ -1118,22 +1613,22 @@
         mProfileData.setProfilerInfo(null);
     }
 
-    @GuardedBy("mService")
-    void clearProfilerLocked(ProcessRecord app) {
+    @GuardedBy("mProfilerLock")
+    void clearProfilerLPf(ProcessRecord app) {
         if (mProfileData.getProfileProc() == null
                 || mProfileData.getProfilerInfo() == null
                 || mProfileData.getProfileProc() != app) {
             return;
         }
-        clearProfilerLocked();
+        clearProfilerLPf();
     }
 
-    @GuardedBy("mService")
-    boolean profileControlLocked(ProcessRecord proc, boolean start,
+    @GuardedBy("mProfilerLock")
+    boolean profileControlLPf(ProcessRecord proc, boolean start,
             ProfilerInfo profilerInfo, int profileType) {
         try {
             if (start) {
-                stopProfilerLocked(null, 0);
+                stopProfilerLPf(null, 0);
                 mService.setProfileApp(proc.info, proc.processName, profilerInfo);
                 mProfileData.setProfileProc(proc);
                 mProfileType = profileType;
@@ -1144,7 +1639,7 @@
                     fd = null;
                 }
                 profilerInfo.profileFd = fd;
-                proc.thread.profilerControl(start, profilerInfo, profileType);
+                proc.mProfile.getThread().profilerControl(start, profilerInfo, profileType);
                 fd = null;
                 try {
                     mProfileData.getProfilerInfo().profileFd.close();
@@ -1152,7 +1647,7 @@
                 }
                 mProfileData.getProfilerInfo().profileFd = null;
 
-                if (proc.pid == mService.MY_PID) {
+                if (proc.getPid() == mService.MY_PID) {
                     // When profiling the system server itself, avoid closing the file
                     // descriptor, as profilerControl will not create a copy.
                     // Note: it is also not correct to just set profileFd to null, as the
@@ -1160,7 +1655,7 @@
                     profilerInfo = null;
                 }
             } else {
-                stopProfilerLocked(proc, profileType);
+                stopProfilerLPf(proc, profileType);
                 if (profilerInfo != null && profilerInfo.profileFd != null) {
                     try {
                         profilerInfo.profileFd.close();
@@ -1182,8 +1677,8 @@
         }
     }
 
-    @GuardedBy("mService")
-    void setProfileAppLocked(String processName, ProfilerInfo profilerInfo) {
+    @GuardedBy("mProfilerLock")
+    void setProfileAppLPf(String processName, ProfilerInfo profilerInfo) {
         mProfileData.setProfileApp(processName);
 
         if (mProfileData.getProfilerInfo() != null) {
@@ -1198,13 +1693,13 @@
         mProfileType = 0;
     }
 
-    @GuardedBy("mService")
-    void setProfileProcLocked(ProcessRecord proc) {
+    @GuardedBy("mProfilerLock")
+    void setProfileProcLPf(ProcessRecord proc) {
         mProfileData.setProfileProc(proc);
     }
 
-    @GuardedBy("mService")
-    void setAgentAppLocked(@NonNull String packageName, @Nullable String agent) {
+    @GuardedBy("mProfilerLock")
+    void setAgentAppLPf(@NonNull String packageName, @Nullable String agent) {
         if (agent == null) {
             if (mAppAgentMap != null) {
                 mAppAgentMap.remove(packageName);
@@ -1226,8 +1721,7 @@
         }
     }
 
-    @GuardedBy("mService")
-    void updateCpuStatsLocked() {
+    void updateCpuStats() {
         final long now = SystemClock.uptimeMillis();
         if (mLastCpuTime.get() >= now - MONITOR_CPU_MIN_TIME) {
             return;
@@ -1301,17 +1795,18 @@
                                 totalUTime += st.rel_utime;
                                 totalSTime += st.rel_stime;
                                 if (pr != null) {
-                                    BatteryStatsImpl.Uid.Proc ps = pr.curProcBatteryStats;
+                                    final ProcessProfileRecord profile = pr.mProfile;
+                                    BatteryStatsImpl.Uid.Proc ps = profile.getCurProcBatteryStats();
                                     if (ps == null || !ps.isActive()) {
-                                        pr.curProcBatteryStats = ps = bstats.getProcessStatsLocked(
+                                        profile.setCurProcBatteryStats(
+                                                ps = bstats.getProcessStatsLocked(
                                                 pr.info.uid, pr.processName,
-                                                elapsedRealtime, uptime);
+                                                elapsedRealtime, uptime));
                                     }
                                     ps.addCpuTimeLocked(st.rel_utime, st.rel_stime);
-                                    pr.curCpuTime += st.rel_utime + st.rel_stime;
-                                    if (pr.lastCpuTime == 0) {
-                                        pr.lastCpuTime = pr.curCpuTime;
-                                    }
+                                    final long curCpuTime = profile.mCurCpuTime.addAndGet(
+                                            st.rel_utime + st.rel_stime);
+                                    profile.mLastCpuTime.compareAndSet(0, curCpuTime);
                                 } else {
                                     BatteryStatsImpl.Uid.Proc ps = st.batteryStats;
                                     if (ps == null || !ps.isActive()) {
@@ -1438,6 +1933,7 @@
 
     AppProfiler(ActivityManagerService service, Looper bgLooper, LowMemDetector detector) {
         mService = service;
+        mProcLock = service.mProcLock;
         mBgHandler = new BgHandler(bgLooper);
         mLowMemDetector = detector;
         mProcessCpuThread = new ProcessCpuThread("CpuTracker");
@@ -1483,44 +1979,46 @@
         ProfilerInfo profilerInfo = null;
         String preBindAgent = null;
         final String processName = app.processName;
-        if (mProfileData.getProfileApp() != null
-                && mProfileData.getProfileApp().equals(processName)) {
-            mProfileData.setProfileProc(app);
-            if (mProfileData.getProfilerInfo() != null) {
-                // Send a profiler info object to the app if either a file is given, or
-                // an agent should be loaded at bind-time.
-                boolean needsInfo = mProfileData.getProfilerInfo().profileFile != null
-                        || mProfileData.getProfilerInfo().attachAgentDuringBind;
-                profilerInfo = needsInfo
-                        ? new ProfilerInfo(mProfileData.getProfilerInfo()) : null;
-                if (mProfileData.getProfilerInfo().agent != null) {
-                    preBindAgent = mProfileData.getProfilerInfo().agent;
+        synchronized (mProfilerLock) {
+            if (mProfileData.getProfileApp() != null
+                    && mProfileData.getProfileApp().equals(processName)) {
+                mProfileData.setProfileProc(app);
+                if (mProfileData.getProfilerInfo() != null) {
+                    // Send a profiler info object to the app if either a file is given, or
+                    // an agent should be loaded at bind-time.
+                    boolean needsInfo = mProfileData.getProfilerInfo().profileFile != null
+                            || mProfileData.getProfilerInfo().attachAgentDuringBind;
+                    profilerInfo = needsInfo
+                            ? new ProfilerInfo(mProfileData.getProfilerInfo()) : null;
+                    if (mProfileData.getProfilerInfo().agent != null) {
+                        preBindAgent = mProfileData.getProfilerInfo().agent;
+                    }
+                }
+            } else if (instr != null && instr.mProfileFile != null) {
+                profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false,
+                        null, false);
+            }
+            if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) {
+                // We need to do a debuggable check here. See setAgentApp for why the check is
+                // postponed to here.
+                if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+                    String agent = mAppAgentMap.get(processName);
+                    // Do not overwrite already requested agent.
+                    if (profilerInfo == null) {
+                        profilerInfo = new ProfilerInfo(null, null, 0, false, false,
+                                mAppAgentMap.get(processName), true);
+                    } else if (profilerInfo.agent == null) {
+                        profilerInfo = profilerInfo.setAgent(mAppAgentMap.get(processName), true);
+                    }
                 }
             }
-        } else if (instr != null && instr.mProfileFile != null) {
-            profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false,
-                    null, false);
-        }
-        if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) {
-            // We need to do a debuggable check here. See setAgentApp for why the check is
-            // postponed to here.
-            if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
-                String agent = mAppAgentMap.get(processName);
-                // Do not overwrite already requested agent.
-                if (profilerInfo == null) {
-                    profilerInfo = new ProfilerInfo(null, null, 0, false, false,
-                            mAppAgentMap.get(processName), true);
-                } else if (profilerInfo.agent == null) {
-                    profilerInfo = profilerInfo.setAgent(mAppAgentMap.get(processName), true);
-                }
-            }
-        }
 
-        if (profilerInfo != null && profilerInfo.profileFd != null) {
-            profilerInfo.profileFd = profilerInfo.profileFd.dup();
-            if (TextUtils.equals(mProfileData.getProfileApp(), processName)
-                    && mProfileData.getProfilerInfo() != null) {
-                clearProfilerLocked();
+            if (profilerInfo != null && profilerInfo.profileFd != null) {
+                profilerInfo.profileFd = profilerInfo.profileFd.dup();
+                if (TextUtils.equals(mProfileData.getProfileApp(), processName)
+                        && mProfileData.getProfilerInfo() != null) {
+                    clearProfilerLPf();
+                }
             }
         }
 
@@ -1533,19 +2031,21 @@
                     i >= 0 && app.getActiveInstrumentation() == null; i--) {
                 ActiveInstrumentation aInstr = mService.mActiveInstrumentation.get(i);
                 if (!aInstr.mFinished && aInstr.mTargetInfo.uid == app.uid) {
-                    if (aInstr.mTargetProcesses.length == 0) {
-                        // This is the wildcard mode, where every process brought up for
-                        // the target instrumentation should be included.
-                        if (aInstr.mTargetInfo.packageName.equals(app.info.packageName)) {
-                            app.setActiveInstrumentation(aInstr);
-                            aInstr.mRunningProcesses.add(app);
-                        }
-                    } else {
-                        for (String proc : aInstr.mTargetProcesses) {
-                            if (proc.equals(app.processName)) {
+                    synchronized (mProcLock) {
+                        if (aInstr.mTargetProcesses.length == 0) {
+                            // This is the wildcard mode, where every process brought up for
+                            // the target instrumentation should be included.
+                            if (aInstr.mTargetInfo.packageName.equals(app.info.packageName)) {
                                 app.setActiveInstrumentation(aInstr);
                                 aInstr.mRunningProcesses.add(app);
-                                break;
+                            }
+                        } else {
+                            for (String proc : aInstr.mTargetProcesses) {
+                                if (proc.equals(app.processName)) {
+                                    app.setActiveInstrumentation(aInstr);
+                                    aInstr.mRunningProcesses.add(app);
+                                    break;
+                                }
                             }
                         }
                     }
@@ -1566,19 +2066,25 @@
 
     @GuardedBy("mService")
     void onCleanupApplicationRecordLocked(ProcessRecord app) {
-        mPendingPssProcesses.remove(app);
-        abortNextPssTime(app.procStateMemTracker);
-    }
-
-    @GuardedBy("mService")
-    void onAppDiedLocked(ProcessRecord app) {
-        if (mProfileData.getProfileProc() == app) {
-            clearProfilerLocked();
+        synchronized (mProfilerLock) {
+            final ProcessProfileRecord profile = app.mProfile;
+            mProcessesToGc.remove(app);
+            mPendingPssProfiles.remove(profile);
+            profile.abortNextPssTime();
         }
     }
 
     @GuardedBy("mService")
-    boolean dumpMemWatchProcessesLocked(PrintWriter pw, boolean needSep) {
+    void onAppDiedLocked(ProcessRecord app) {
+        synchronized (mProfilerLock) {
+            if (mProfileData.getProfileProc() == app) {
+                clearProfilerLPf();
+            }
+        }
+    }
+
+    @GuardedBy("mProfilerLock")
+    boolean dumpMemWatchProcessesLPf(PrintWriter pw, boolean needSep) {
         if (mMemWatchProcesses.getMap().size() > 0) {
             pw.println("  Mem watch processes:");
             final ArrayMap<String, SparseArray<Pair<Long, String>>> procs =
@@ -1669,8 +2175,8 @@
                 + " mLastNumProcesses=" + mLastNumProcesses);
     }
 
-    @GuardedBy("mService")
-    void writeMemWatchProcessToProtoLocked(ProtoOutputStream proto) {
+    @GuardedBy("mProfilerLock")
+    void writeMemWatchProcessToProtoLPf(ProtoOutputStream proto) {
         if (mMemWatchProcesses.getMap().size() > 0) {
             final long token = proto.start(
                     ActivityManagerServiceDumpProcessesProto.MEM_WATCH_PROCESSES);
@@ -1753,4 +2259,55 @@
             report.append(mProcessCpuTracker.printCurrentState(time));
         }
     }
+
+    @GuardedBy("mProfilerLock")
+    void writeProcessesToGcToProto(ProtoOutputStream proto, long fieldId, String dumpPackage) {
+        if (mProcessesToGc.size() > 0) {
+            long now = SystemClock.uptimeMillis();
+            for (int i = 0, size = mProcessesToGc.size(); i < size; i++) {
+                ProcessRecord r = mProcessesToGc.get(i);
+                if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
+                    continue;
+                }
+                final long token = proto.start(fieldId);
+                final ProcessProfileRecord profile = r.mProfile;
+                r.dumpDebug(proto, ProcessToGcProto.PROC);
+                proto.write(ProcessToGcProto.REPORT_LOW_MEMORY, profile.getReportLowMemory());
+                proto.write(ProcessToGcProto.NOW_UPTIME_MS, now);
+                proto.write(ProcessToGcProto.LAST_GCED_MS, profile.getLastRequestedGc());
+                proto.write(ProcessToGcProto.LAST_LOW_MEMORY_MS, profile.getLastLowMemory());
+                proto.end(token);
+            }
+        }
+    }
+
+    @GuardedBy("mProfilerLock")
+    boolean dumpProcessesToGc(PrintWriter pw, boolean needSep, String dumpPackage) {
+        if (mProcessesToGc.size() > 0) {
+            boolean printed = false;
+            long now = SystemClock.uptimeMillis();
+            for (int i = 0, size = mProcessesToGc.size(); i < size; i++) {
+                ProcessRecord proc = mProcessesToGc.get(i);
+                if (dumpPackage != null && !dumpPackage.equals(proc.info.packageName)) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) pw.println();
+                    needSep = true;
+                    pw.println("  Processes that are waiting to GC:");
+                    printed = true;
+                }
+                pw.print("    Process "); pw.println(proc);
+                final ProcessProfileRecord profile = proc.mProfile;
+                pw.print("      lowMem="); pw.print(profile.getReportLowMemory());
+                pw.print(", last gced=");
+                pw.print(now - profile.getLastRequestedGc());
+                pw.print(" ms ago, last lowMem=");
+                pw.print(now - profile.getLastLowMemory());
+                pw.println(" ms ago");
+
+            }
+        }
+        return needSep;
+    }
 }
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b153cfd..c6947c2d 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -43,6 +43,7 @@
 import android.util.SparseLongArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.power.MeasuredEnergyArray;
 import com.android.internal.power.MeasuredEnergyStats;
@@ -96,8 +97,6 @@
                         return t;
                     });
 
-    private final Context mContext;
-
     @GuardedBy("mStats")
     private final BatteryStatsImpl mStats;
 
@@ -168,15 +167,39 @@
     @GuardedBy("this")
     private long mLastCollectionTimeStamp;
 
+    final Injector mInjector;
+
+    @VisibleForTesting
+    public static class Injector {
+        private final Context mContext;
+
+        Injector(Context context) {
+            mContext = context;
+        }
+
+        public <T> T getSystemService(Class<T> serviceClass) {
+            return mContext.getSystemService(serviceClass);
+        }
+
+        public <T> T getLocalService(Class<T> serviceClass) {
+            return LocalServices.getService(serviceClass);
+        }
+    }
+
     BatteryExternalStatsWorker(Context context, BatteryStatsImpl stats) {
-        mContext = context;
+        this(new Injector(context), stats);
+    }
+
+    @VisibleForTesting
+    BatteryExternalStatsWorker(Injector injector, BatteryStatsImpl stats) {
+        mInjector = injector;
         mStats = stats;
     }
 
     public void systemServicesReady() {
-        final WifiManager wm = mContext.getSystemService(WifiManager.class);
-        final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-        final PowerStatsInternal psi = LocalServices.getService(PowerStatsInternal.class);
+        final WifiManager wm = mInjector.getSystemService(WifiManager.class);
+        final TelephonyManager tm = mInjector.getSystemService(TelephonyManager.class);
+        final PowerStatsInternal psi = mInjector.getLocalService(PowerStatsInternal.class);
         synchronized (mWorkerLock) {
             mWifiManager = wm;
             mTelephony = tm;
@@ -184,12 +207,13 @@
             if (mPowerStatsInternal != null) {
                 populateEnergyConsumerSubsystemMapsLocked();
                 final MeasuredEnergyArray initialMeasuredEnergies = getEnergyConsumptionData();
-                final boolean[] supportedBuckets = getSupportedEnergyBuckets(
-                        initialMeasuredEnergies);
                 mMeasuredEnergySnapshot = initialMeasuredEnergies == null
                         ? null : new MeasuredEnergySnapshot(initialMeasuredEnergies);
+                final boolean[] supportedStdBuckets
+                        = getSupportedEnergyBuckets(initialMeasuredEnergies);
+                final int numCustomBuckets = 0; // TODO: Get this from initialMeasuredEnergies
                 synchronized (mStats) {
-                    mStats.initMeasuredEnergyStatsLocked(supportedBuckets);
+                    mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, numCustomBuckets);
                 }
             }
         }
@@ -717,15 +741,16 @@
 
     /**
      * Map the {@link MeasuredEnergyArray.MeasuredEnergySubsystem}s in the given energyArray to
-     * their corresponding {@link MeasuredEnergyStats.EnergyBucket}s.
+     * their corresponding {@link MeasuredEnergyStats.StandardEnergyBucket}s.
+     * Does not include custom energy buckets (which are always, by definition, supported).
      *
-     * @return array with true for index i if energy bucket i is supported.
+     * @return array with true for index i if standard energy bucket i is supported.
      */
     private static @Nullable boolean[] getSupportedEnergyBuckets(MeasuredEnergyArray energyArray) {
         if (energyArray == null) {
             return null;
         }
-        final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_ENERGY_BUCKETS];
+        final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_ENERGY_BUCKETS];
         final int size = energyArray.size();
         for (int energyIdx = 0; energyIdx < size; energyIdx++) {
             switch (energyArray.getSubsystem(energyIdx)) {
@@ -747,7 +772,8 @@
      * EnergyConsumerResult}[]
      */
     @GuardedBy("mWorkerLock")
-    private @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
+    @VisibleForTesting
+    public @Nullable MeasuredEnergyArray getEnergyConsumptionData() {
         final EnergyConsumerResult[] results;
         try {
             results = mPowerStatsInternal.getEnergyConsumedAsync(new int[0])
@@ -761,28 +787,41 @@
         final int[] subsystems = new int[size];
         final long[] energyUJ = new long[size];
 
+        int count = 0;
         for (int i = 0; i < size; i++) {
             final EnergyConsumerResult consumer = results[i];
             final int subsystem = mEnergyConsumerToSubsystemMap.get(consumer.id,
                     MeasuredEnergyArray.SUBSYSTEM_UNKNOWN);
             if (subsystem == MeasuredEnergyArray.SUBSYSTEM_UNKNOWN) continue;
-            subsystems[i] = subsystem;
-            energyUJ[i] = consumer.energyUWs;
+            subsystems[count] = subsystem;
+            energyUJ[count] = consumer.energyUWs;
+            count++;
         }
+        final int arraySize = count;
         return new MeasuredEnergyArray() {
             @Override
             public int getSubsystem(int index) {
+                if (index >= size()) {
+                    throw new IllegalArgumentException(
+                            "Out of bounds subsystem index! index : " + index + ", size : "
+                                    + size());
+                }
                 return subsystems[index];
             }
 
             @Override
             public long getEnergy(int index) {
+                if (index >= size()) {
+                    throw new IllegalArgumentException(
+                            "Out of bounds subsystem index! index : " + index + ", size : "
+                                    + size());
+                }
                 return energyUJ[index];
             }
 
             @Override
             public int size() {
-                return size;
+                return arraySize;
             }
         };
     }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index b1cbb4a..773e313 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -21,6 +21,10 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.State;
+import android.hardware.power.stats.StateResidency;
+import android.hardware.power.stats.StateResidencyResult;
 import android.net.INetworkManagementEventObserver;
 import android.net.NetworkCapabilities;
 import android.os.BatteryStats;
@@ -51,6 +55,7 @@
 import android.os.health.HealthStatsParceler;
 import android.os.health.HealthStatsWriter;
 import android.os.health.UidHealthStats;
+import android.power.PowerStatsInternal;
 import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
@@ -87,10 +92,13 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 /**
  * All information we are collecting about things that can happen that impact
@@ -112,23 +120,23 @@
     private final BatteryExternalStatsWorker mWorker;
     private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
 
-    private native void getLowPowerStats(RpmStats rpmStats);
-    private native int getPlatformLowPowerStats(ByteBuffer outBuffer);
-    private native int getSubsystemLowPowerStats(ByteBuffer outBuffer);
     private native void getRailEnergyPowerStats(RailStats railStats);
     private CharsetDecoder mDecoderStat = StandardCharsets.UTF_8
                     .newDecoder()
                     .onMalformedInput(CodingErrorAction.REPLACE)
                     .onUnmappableCharacter(CodingErrorAction.REPLACE)
                     .replaceWith("?");
-    private ByteBuffer mUtf8BufferStat = ByteBuffer.allocateDirect(MAX_LOW_POWER_STATS_SIZE);
-    private CharBuffer mUtf16BufferStat = CharBuffer.allocate(MAX_LOW_POWER_STATS_SIZE);
     private static final int MAX_LOW_POWER_STATS_SIZE = 4096;
+    private static final int POWER_STATS_QUERY_TIMEOUT_MILLIS = 2000;
 
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final Object mLock = new Object();
 
+    private PowerStatsInternal mPowerStatsInternal = null;
+    private Map<Integer, String> mEntityNames = new HashMap();
+    private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();
+
     @GuardedBy("mStats")
     private int mLastPowerStateFromRadio = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
     @GuardedBy("mStats")
@@ -163,16 +171,57 @@
                 }
             };
 
+    private void populatePowerEntityMaps() {
+        if (mPowerStatsInternal == null) {
+            // PowerStatsInternal unavailable, don't bother populating maps.
+            mEntityNames = null;
+            mStateNames = null;
+            return;
+        }
+
+        PowerEntity[] entities = mPowerStatsInternal.getPowerEntityInfo();
+        if (entities == null) {
+            return;
+        }
+
+        for (int i = 0; i < entities.length; i++) {
+            final PowerEntity entity = entities[i];
+            Map<Integer, String> states = new HashMap();
+            for (int j = 0; j < entity.states.length; j++) {
+                final State state = entity.states[j];
+                states.put(state.id, state.name);
+            }
+
+            mEntityNames.put(entity.id, entity.name);
+            mStateNames.put(entity.id, states);
+        }
+    }
+
     /**
      * Replaces the information in the given rpmStats with up-to-date information.
      */
     @Override
     public void fillLowPowerStats(RpmStats rpmStats) {
-        if (DBG) Slog.d(TAG, "begin getLowPowerStats");
+        final StateResidencyResult[] results;
         try {
-            getLowPowerStats(rpmStats);
-        } finally {
-            if (DBG) Slog.d(TAG, "end getLowPowerStats");
+            results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
+                    .get(POWER_STATS_QUERY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to getStateResidencyAsync", e);
+            return;
+        }
+
+        for (int i = 0; i < results.length; i++) {
+            final StateResidencyResult result = results[i];
+            RpmStats.PowerStateSubsystem subsystem =
+                    rpmStats.getSubsystem(mEntityNames.get(result.id));
+
+            for (int j = 0; j < result.stateResidencyData.length; j++) {
+                final StateResidency stateResidency = result.stateResidencyData[j];
+                subsystem.putState(mStateNames.get(result.id).get(stateResidency.id),
+                        stateResidency.totalTimeInStateMs,
+                        (int) stateResidency.totalStateEntryCount);
+            }
         }
     }
 
@@ -187,47 +236,46 @@
     }
 
     @Override
-    public String getPlatformLowPowerStats() {
-        if (DBG) Slog.d(TAG, "begin getPlatformLowPowerStats");
-        try {
-            mUtf8BufferStat.clear();
-            mUtf16BufferStat.clear();
-            mDecoderStat.reset();
-            int bytesWritten = getPlatformLowPowerStats(mUtf8BufferStat);
-            if (bytesWritten < 0) {
-                return null;
-            } else if (bytesWritten == 0) {
-                return "Empty";
-            }
-            mUtf8BufferStat.limit(bytesWritten);
-            mDecoderStat.decode(mUtf8BufferStat, mUtf16BufferStat, true);
-            mUtf16BufferStat.flip();
-            return mUtf16BufferStat.toString();
-        } finally {
-            if (DBG) Slog.d(TAG, "end getPlatformLowPowerStats");
-        }
-    }
-
-    @Override
     public String getSubsystemLowPowerStats() {
-        if (DBG) Slog.d(TAG, "begin getSubsystemLowPowerStats");
+        final StateResidencyResult[] results;
         try {
-            mUtf8BufferStat.clear();
-            mUtf16BufferStat.clear();
-            mDecoderStat.reset();
-            int bytesWritten = getSubsystemLowPowerStats(mUtf8BufferStat);
-            if (bytesWritten < 0) {
-                return null;
-            } else if (bytesWritten == 0) {
-                return "Empty";
-            }
-            mUtf8BufferStat.limit(bytesWritten);
-            mDecoderStat.decode(mUtf8BufferStat, mUtf16BufferStat, true);
-            mUtf16BufferStat.flip();
-            return mUtf16BufferStat.toString();
-        } finally {
-            if (DBG) Slog.d(TAG, "end getSubsystemLowPowerStats");
+            results = mPowerStatsInternal.getStateResidencyAsync(new int[0])
+                    .get(POWER_STATS_QUERY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to getStateResidencyAsync", e);
+            return "Empty";
         }
+
+        if (results.length == 0) return "Empty";
+
+        int charsLeft = MAX_LOW_POWER_STATS_SIZE;
+        StringBuilder builder = new StringBuilder("SubsystemPowerState");
+        for (int i = 0; i < results.length; i++) {
+            final StateResidencyResult result = results[i];
+            StringBuilder subsystemBuilder = new StringBuilder();
+            subsystemBuilder.append(" subsystem_" + i);
+            subsystemBuilder.append(" name=" + mEntityNames.get(result.id));
+
+            for (int j = 0; j < result.stateResidencyData.length; j++) {
+                final StateResidency stateResidency = result.stateResidencyData[j];
+                subsystemBuilder.append(" state_" + j);
+                subsystemBuilder.append(" name=" + mStateNames.get(result.id).get(
+                        stateResidency.id));
+                subsystemBuilder.append(" time=" + stateResidency.totalTimeInStateMs);
+                subsystemBuilder.append(" count=" + stateResidency.totalStateEntryCount);
+                subsystemBuilder.append(" last entry=" + stateResidency.lastEntryTimestampMs);
+            }
+
+            if (subsystemBuilder.length() <= charsLeft) {
+                charsLeft -= subsystemBuilder.length();
+                builder.append(subsystemBuilder);
+            } else {
+                Slog.e(TAG, "getSubsystemLowPowerStats: buffer not enough");
+                break;
+            }
+        }
+
+        return builder.toString();
     }
 
     BatteryStatsService(Context context, File systemDir, Handler handler) {
@@ -254,6 +302,8 @@
         mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);
         mStats.setPowerProfileLocked(new PowerProfile(context));
+        mStats.startTrackingSystemServerCpuTime();
+
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats);
     }
 
@@ -272,6 +322,11 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
         }
+        mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class);
+        if (mPowerStatsInternal != null) {
+            populatePowerEntityMaps();
+        }
+
         Watchdog.getInstance().addMonitor(this);
     }
 
@@ -559,10 +614,10 @@
      * Returns BatteryUsageStats, which contains power attribution data on a per-subsystem
      * and per-UID basis.
      */
-    public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) {
+    public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
         mContext.enforceCallingPermission(
                 android.Manifest.permission.BATTERY_STATS, null);
-        return mBatteryUsageStatsProvider.getBatteryUsageStats(query);
+        return mBatteryUsageStatsProvider.getBatteryUsageStats(queries);
     }
 
     public byte[] getStatistics() {
@@ -1674,11 +1729,11 @@
     }
 
     @Override
-    public void noteNetworkInterfaceType(final String iface, final int networkType) {
+    public void noteNetworkInterfaceForTransports(final String iface, int[] transportTypes) {
         enforceCallingPermission();
         synchronized (mLock) {
             mHandler.post(() -> {
-                mStats.noteNetworkInterfaceType(iface, networkType);
+                mStats.noteNetworkInterfaceForTransports(iface, transportTypes);
             });
         }
     }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index d2ee69e..7e65434 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -26,6 +26,7 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
+import android.app.IApplicationThread;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -42,6 +43,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerWhitelistManager.TempAllowListType;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -221,7 +223,7 @@
     }
 
     public boolean isPendingBroadcastProcessLocked(int pid) {
-        return mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid;
+        return mPendingBroadcast != null && mPendingBroadcast.curApp.getPid() == pid;
     }
 
     public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
@@ -285,19 +287,21 @@
             ProcessRecord app) throws RemoteException {
         if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                 "Process cur broadcast " + r + " for app " + app);
-        if (app.thread == null) {
+        final IApplicationThread thread = app.getThread();
+        if (thread == null) {
             throw new RemoteException();
         }
-        if (app.inFullBackup) {
+        if (app.isInFullBackup()) {
             skipReceiverLocked(r);
             return;
         }
 
-        r.receiver = app.thread.asBinder();
+        r.receiver = thread.asBinder();
         r.curApp = app;
-        app.curReceivers.add(r);
-        app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-        mService.mProcessList.updateLruProcessLocked(app, false, null);
+        final ProcessReceiverRecord prr = app.mReceivers;
+        prr.addCurReceiver(r);
+        app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+        mService.updateLruProcessLocked(app, false, null);
         // Make sure the oom adj score is updated before delivering the broadcast.
         // Force an update, even if there are other pending requests, overall it still saves time,
         // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
@@ -314,10 +318,10 @@
                     + ": " + r);
             mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
                                       PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
-            app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
+            thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
                     mService.compatibilityInfoForPackage(r.curReceiver.applicationInfo),
                     r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
-                    app.getReportedProcState());
+                    app.mState.getReportedProcState());
             if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                     "Process cur broadcast " + r + " DELIVERED for app " + app);
             started = true;
@@ -327,7 +331,7 @@
                         "Process cur broadcast " + r + ": NOT STARTED!");
                 r.receiver = null;
                 r.curApp = null;
-                app.curReceivers.remove(r);
+                prr.removeCurReceiver(r);
             }
         }
     }
@@ -335,7 +339,7 @@
     public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
         boolean didSomething = false;
         final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.pid > 0 && br.curApp.pid == app.pid) {
+        if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
             if (br.curApp != app) {
                 Slog.e(TAG, "App mismatch when sending pending broadcast to "
                         + app.processName + ", intended target is " + br.curApp.processName);
@@ -362,7 +366,7 @@
 
     public void skipPendingBroadcastLocked(int pid) {
         final BroadcastRecord br = mPendingBroadcast;
-        if (br != null && br.curApp.pid == pid) {
+        if (br != null && br.curApp.getPid() == pid) {
             br.state = BroadcastRecord.IDLE;
             br.nextReceiver = mPendingBroadcastRecvIndex;
             mPendingBroadcast = null;
@@ -502,8 +506,8 @@
 
         r.receiver = null;
         r.intent.setComponent(null);
-        if (r.curApp != null && r.curApp.curReceivers.contains(r)) {
-            r.curApp.curReceivers.remove(r);
+        if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
+            r.curApp.mReceivers.removeCurReceiver(r);
             mService.enqueueOomAdjTargetLocked(r.curApp);
         }
         if (r.curFilter != null) {
@@ -577,12 +581,14 @@
             throws RemoteException {
         // Send the intent to the receiver asynchronously using one-way binder calls.
         if (app != null) {
-            if (app.thread != null) {
+            final IApplicationThread thread = app.getThread();
+            if (thread != null) {
                 // If we have an app thread, do the call through that so it is
                 // correctly ordered with other one-way calls.
                 try {
-                    app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
-                            data, extras, ordered, sticky, sendingUser, app.getReportedProcState());
+                    thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
+                            data, extras, ordered, sticky, sendingUser,
+                            app.mState.getReportedProcState());
                 // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
                 // DeadObjectException when the process isn't actually dead.
                 //} catch (DeadObjectException ex) {
@@ -592,8 +598,8 @@
                     // Failed to call into the process. It's either dying or wedged. Kill it gently.
                     synchronized (mService) {
                         Slog.w(TAG, "Can't deliver broadcast to " + app.processName
-                                + " (pid " + app.pid + "). Crashing it.");
-                        app.scheduleCrash("can't deliver broadcast");
+                                + " (pid " + app.getPid() + "). Crashing it.");
+                        app.scheduleCrashLocked("can't deliver broadcast");
                     }
                     throw ex;
                 }
@@ -723,8 +729,8 @@
             skip = true;
         }
 
-        if (!skip && (filter.receiverList.app == null || filter.receiverList.app.killed
-                || filter.receiverList.app.isCrashing())) {
+        if (!skip && (filter.receiverList.app == null || filter.receiverList.app.isKilled()
+                || filter.receiverList.app.mErrorState.isCrashing())) {
             Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
                     + " to " + filter.receiverList + ": process gone or crashing");
             skip = true;
@@ -793,7 +799,7 @@
                 // things that directly call the IActivityManager API, which
                 // are already core system stuff so don't matter for this.
                 r.curApp = filter.receiverList.app;
-                filter.receiverList.app.curReceivers.add(r);
+                filter.receiverList.app.mReceivers.addCurReceiver(r);
                 mService.enqueueOomAdjTargetLocked(r.curApp);
                 mService.updateOomAdjPendingTargetsLocked(
                         OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
@@ -805,7 +811,7 @@
         try {
             if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
                     "Delivering to " + filter + " : " + r);
-            if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) {
+            if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
                 // Skip delivery if full backup in progress
                 // If it's an ordered broadcast, we need to continue to the next receiver.
                 if (ordered) {
@@ -832,7 +838,7 @@
             if (filter.receiverList.app != null) {
                 filter.receiverList.app.removeAllowBackgroundActivityStartsToken(r);
                 if (ordered) {
-                    filter.receiverList.app.curReceivers.remove(r);
+                    filter.receiverList.app.mReceivers.removeCurReceiver(r);
                     // Something wrong, its oom adj could be downgraded, but not in a hurry.
                     mService.enqueueOomAdjTargetLocked(r.curApp);
                 }
@@ -849,14 +855,14 @@
     private boolean requestStartTargetPermissionsReviewIfNeededLocked(
             BroadcastRecord receiverRecord, String receivingPackageName,
             final int receivingUserId) {
-        if (!mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+        if (!mService.getPackageManagerInternal().isPermissionsReviewRequired(
                 receivingPackageName, receivingUserId)) {
             return true;
         }
 
         final boolean callerForeground = receiverRecord.callerApp != null
-                ? receiverRecord.callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND
-                : true;
+                ? receiverRecord.callerApp.mState.getSetSchedGroup()
+                != ProcessList.SCHED_GROUP_BACKGROUND : true;
 
         // Show a permission review UI only for explicit broadcast from a foreground app
         if (callerForeground && receiverRecord.intent.getComponent() != null) {
@@ -897,7 +903,7 @@
     }
 
     final void scheduleTempWhitelistLocked(int uid, long duration, BroadcastRecord r,
-            @BroadcastOptions.TempAllowListType int type) {
+            @TempAllowListType int type) {
         if (duration > Integer.MAX_VALUE) {
             duration = Integer.MAX_VALUE;
         }
@@ -922,7 +928,7 @@
             Slog.v(TAG, "Broadcast temp whitelist uid=" + uid + " duration=" + duration
                     + " type=" + type + " : " + b.toString());
         }
-        mService.tempWhitelistUidLocked(uid, duration, b.toString(), type);
+        mService.tempAllowlistUidLocked(uid, duration, b.toString(), type);
     }
 
     /**
@@ -969,7 +975,7 @@
                 + mParallelBroadcasts.size() + " parallel broadcasts; "
                 + mDispatcher.describeStateLocked());
 
-        mService.updateCpuStatsLocked();
+        mService.updateCpuStats();
 
         if (fromMsg) {
             mBroadcastsScheduled = false;
@@ -1017,16 +1023,16 @@
                     + mPendingBroadcast.curApp);
 
             boolean isDead;
-            if (mPendingBroadcast.curApp.pid > 0) {
+            if (mPendingBroadcast.curApp.getPid() > 0) {
                 synchronized (mService.mPidsSelfLocked) {
                     ProcessRecord proc = mService.mPidsSelfLocked.get(
-                            mPendingBroadcast.curApp.pid);
-                    isDead = proc == null || proc.isCrashing();
+                            mPendingBroadcast.curApp.getPid());
+                    isDead = proc == null || proc.mErrorState.isCrashing();
                 }
             } else {
-                final ProcessRecord proc = mService.mProcessList.mProcessNames.get(
+                final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
                         mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
-                isDead = proc == null || !proc.pendingStart;
+                isDead = proc == null || !proc.isPendingStart();
             }
             if (!isDead) {
                 // It's still alive, so keep waiting
@@ -1050,7 +1056,9 @@
             if (r == null) {
                 // No more broadcasts are deliverable right now, so all done!
                 mDispatcher.scheduleDeferralCheckLocked(false);
-                mService.scheduleAppGcsLocked();
+                synchronized (mService.mAppProfiler.mProfilerLock) {
+                    mService.mAppProfiler.scheduleAppGcsLPf();
+                }
                 if (looped && !skipOomAdj) {
                     // If we had finished the last ordered broadcast, then
                     // make sure all processes have correct oom and sched
@@ -1494,7 +1502,7 @@
                     + " (uid " + r.callingUid + ")");
             skip = true;
         }
-        if (r.curApp != null && r.curApp.isCrashing()) {
+        if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
             // If the target process is crashing, just skip it.
             Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
                     + " to " + r.curApp + ": process crashing");
@@ -1545,14 +1553,14 @@
                 info.activityInfo.applicationInfo.uid, false);
 
         if (!skip) {
-            final int allowed = mService.getAppStartModeLocked(
+            final int allowed = mService.getAppStartModeLOSP(
                     info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
                     info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
             if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
                 // We won't allow this receiver to be launched if the app has been
                 // completely disabled from launches, or it was not explicitly sent
                 // to it and the app is in a state that should not receive it
-                // (depending on how getAppStartModeLocked has determined that).
+                // (depending on how getAppStartModeLOSP has determined that).
                 if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
                     Slog.w(TAG, "Background execution disabled: receiving "
                             + r.intent + " to "
@@ -1627,7 +1635,7 @@
         }
 
         // Is this receiver's application already running?
-        if (app != null && app.thread != null && !app.killed) {
+        if (app != null && app.getThread() != null && !app.isKilled()) {
             try {
                 app.addPackage(info.activityInfo.packageName,
                         info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
@@ -1662,13 +1670,13 @@
         if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                 "Need to start app ["
                 + mQueueName + "] " + targetProcess + " for broadcast " + r);
-        if ((r.curApp=mService.startProcessLocked(targetProcess,
+        r.curApp = mService.startProcessLocked(targetProcess,
                 info.activityInfo.applicationInfo, true,
                 r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
-                new HostingRecord("broadcast", r.curComponent),
-                isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
-                (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
-                        == null) {
+                new HostingRecord("broadcast", r.curComponent), isActivityCapable
+                ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
+                (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false);
+        if (r.curApp == null) {
             // Ah, this recipient is unavailable.  Finish it if necessary,
             // and mark the broadcast record as ready for the next.
             Slog.w(TAG, "Unable to launch app "
diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java
index 26cfd62..50278fd 100644
--- a/services/core/java/com/android/server/am/CacheOomRanker.java
+++ b/services/core/java/com/android/server/am/CacheOomRanker.java
@@ -60,6 +60,10 @@
 
     private final Object mPhenotypeFlagLock = new Object();
 
+    private final ActivityManagerService mService;
+    private final ActivityManagerGlobalLock mProcLock;
+    private final Object mProfilerLock;
+
     @GuardedBy("mPhenotypeFlagLock")
     private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING;
     // Weight to apply to the LRU ordering.
@@ -101,6 +105,12 @@
                 }
             };
 
+    CacheOomRanker(final ActivityManagerService service) {
+        mService = service;
+        mProcLock = service.mProcLock;
+        mProfilerLock = service.mAppProfiler.mProfilerLock;
+    }
+
     /** Load settings from device config and register a listener for changes. */
     public void init(Executor executor) {
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -171,15 +181,14 @@
      * Re-rank the cached processes in the lru list with a weighted ordering
      * of lru, rss size and number of times the process has been put in the cache.
      */
-    public void reRankLruCachedApps(ProcessList processList) {
+    @GuardedBy({"mService", "mProcLock"})
+    void reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart) {
         float lruWeight;
         float usesWeight;
         float rssWeight;
         int[] lruPositions;
         RankedProcessRecord[] scoredProcessRecords;
 
-        ArrayList<ProcessRecord> lruList = processList.mLruProcesses;
-
         synchronized (mPhenotypeFlagLock) {
             lruWeight = mLruWeight;
             usesWeight = mUsesWeight;
@@ -196,11 +205,11 @@
         // Collect the least recently used processes to re-rank, only rank cached
         // processes further down the list than mLruProcessServiceStart.
         int cachedProcessPos = 0;
-        for (int i = 0; i < processList.mLruProcessServiceStart
+        for (int i = 0; i < lruProcessServiceStart
                 && cachedProcessPos < scoredProcessRecords.length; ++i) {
             ProcessRecord app = lruList.get(i);
             // Processes that will be assigned a cached oom adj score.
-            if (!app.killedByAm && app.thread != null && app.curAdj
+            if (!app.isKilledByAm() && app.getThread() != null && app.mState.getCurAdj()
                     >= ProcessList.UNKNOWN_ADJ) {
                 scoredProcessRecords[cachedProcessPos].proc = app;
                 scoredProcessRecords[cachedProcessPos].score = 0.0f;
@@ -223,7 +232,9 @@
             addToScore(scoredProcessRecords, lruWeight);
         }
         if (rssWeight > 0.0f) {
-            Arrays.sort(scoredProcessRecords, LAST_RSS_COMPARATOR);
+            synchronized (mService.mAppProfiler.mProfilerLock) {
+                Arrays.sort(scoredProcessRecords, LAST_RSS_COMPARATOR);
+            }
             addToScore(scoredProcessRecords, rssWeight);
         }
         if (usesWeight > 0.0f) {
@@ -237,7 +248,8 @@
         if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
             boolean printedHeader = false;
             for (int i = 0; i < scoredProcessRecords.length; ++i) {
-                if (scoredProcessRecords[i].proc.pid != lruList.get(lruPositions[i]).pid) {
+                if (scoredProcessRecords[i].proc.getPid()
+                        != lruList.get(lruPositions[i]).getPid()) {
                     if (!printedHeader) {
                         Slog.i(OomAdjuster.TAG, "reRankLruCachedApps");
                         printedHeader = true;
@@ -281,15 +293,15 @@
     private static class LastActivityTimeComparator implements Comparator<RankedProcessRecord> {
         @Override
         public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
-            return Long.compare(o1.proc.lastActivityTime, o2.proc.lastActivityTime);
+            return Long.compare(o1.proc.getLastActivityTime(), o2.proc.getLastActivityTime());
         }
     }
 
     private static class CacheUseComparator implements Comparator<RankedProcessRecord> {
         @Override
         public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
-            return Long.compare(o1.proc.getCacheOomRankerUseCount(),
-                    o2.proc.getCacheOomRankerUseCount());
+            return Long.compare(o1.proc.mState.getCacheOomRankerUseCount(),
+                    o2.proc.mState.getCacheOomRankerUseCount());
         }
     }
 
@@ -297,7 +309,7 @@
         @Override
         public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
             // High RSS first to match least recently used.
-            return Long.compare(o2.proc.mLastRss, o1.proc.mLastRss);
+            return Long.compare(o2.proc.mProfile.getLastRss(), o1.proc.mProfile.getLastRss());
         }
     }
 
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index dd09a1c..c18031f 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -42,7 +42,6 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.ServiceThread;
 
-import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -80,20 +79,22 @@
 
     // Phenotype sends int configurations and we map them to the strings we'll use on device,
     // preventing a weird string value entering the kernel.
+    private static final int COMPACT_ACTION_NONE = 0;
+    private static final int COMPACT_ACTION_FILE = 1;
+    private static final int COMPACT_ACTION_ANON = 2;
+    private static final int COMPACT_ACTION_FULL = 3;
+
+    private static final String COMPACT_ACTION_STRING[] = {"", "file", "anon", "all"};
+
+    // Keeps these flags in sync with services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
     private static final int COMPACT_ACTION_FILE_FLAG = 1;
     private static final int COMPACT_ACTION_ANON_FLAG = 2;
-    private static final int COMPACT_ACTION_FULL_FLAG = 3;
-    private static final int COMPACT_ACTION_NONE_FLAG = 4;
-    private static final String COMPACT_ACTION_NONE = "";
-    private static final String COMPACT_ACTION_FILE = "file";
-    private static final String COMPACT_ACTION_ANON = "anon";
-    private static final String COMPACT_ACTION_FULL = "all";
 
     // Defaults for phenotype flags.
     @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
     @VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = false;
-    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG;
-    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG;
+    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE;
+    @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000;
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500;
@@ -151,9 +152,14 @@
      */
     final ServiceThread mCachedAppOptimizerThread;
 
+    @GuardedBy("mProcLock")
     private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
             new ArrayList<ProcessRecord>();
+
     private final ActivityManagerService mAm;
+
+    private final ActivityManagerGlobalLock mProcLock;
+
     private final OnPropertiesChangedListener mOnFlagsChangedListener =
             new OnPropertiesChangedListener() {
                 @Override
@@ -232,12 +238,15 @@
     @VisibleForTesting
     Handler mCompactionHandler;
     private Handler mFreezeHandler;
+    @GuardedBy("mProcLock")
+    private boolean mFreezerOverride = false;
 
     // Maps process ID to last compaction statistics for processes that we've fully compacted. Used
     // when evaluating throttles that we only consider for "full" compaction, so we don't store
     // data for "some" compactions. Uses LinkedHashMap to ensure insertion order is kept and
     // facilitate removal of the oldest entry.
     @VisibleForTesting
+    @GuardedBy("mProcLock")
     LinkedHashMap<Integer, LastCompactionStats> mLastCompactionStats =
             new LinkedHashMap<Integer, LastCompactionStats>() {
                 @Override
@@ -260,6 +269,7 @@
     CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback,
             ProcessDependencies processDependencies) {
         mAm = am;
+        mProcLock = am.mProcLock;
         mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
             Process.THREAD_GROUP_SYSTEM, true);
         mProcStateThrottle = new HashSet<>();
@@ -305,7 +315,7 @@
         }
     }
 
-    @GuardedBy("mAm")
+    @GuardedBy("mProcLock")
     void dump(PrintWriter pw) {
         pw.println("CachedAppOptimizer settings");
         synchronized (mPhenotypeFlagLock) {
@@ -346,58 +356,57 @@
         }
     }
 
-    @GuardedBy("mAm")
+    @GuardedBy("mProcLock")
     void compactAppSome(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_SOME;
+        app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
         mPendingCompactionProcesses.add(app);
         mCompactionHandler.sendMessage(
                 mCompactionHandler.obtainMessage(
-                COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+                COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
     }
 
-    @GuardedBy("mAm")
+    @GuardedBy("mProcLock")
     void compactAppFull(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_FULL;
+        app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
         mPendingCompactionProcesses.add(app);
         mCompactionHandler.sendMessage(
                 mCompactionHandler.obtainMessage(
-                COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
+                COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
 
     }
 
-    @GuardedBy("mAm")
+    @GuardedBy("mProcLock")
     void compactAppPersistent(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_PERSISTENT;
+        app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
         mPendingCompactionProcesses.add(app);
         mCompactionHandler.sendMessage(
                 mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+                    COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
     }
 
-    @GuardedBy("mAm")
+    @GuardedBy("mProcLock")
     boolean shouldCompactPersistent(ProcessRecord app, long now) {
-        return (app.lastCompactTime == 0
-                || (now - app.lastCompactTime) > mCompactThrottlePersistent);
+        return (app.mOptRecord.getLastCompactTime() == 0
+                || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottlePersistent);
     }
 
-    @GuardedBy("mAm")
+    @GuardedBy("mProcLock")
     void compactAppBfgs(ProcessRecord app) {
-        app.reqCompactAction = COMPACT_PROCESS_BFGS;
+        app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
         mPendingCompactionProcesses.add(app);
         mCompactionHandler.sendMessage(
                 mCompactionHandler.obtainMessage(
-                    COMPACT_PROCESS_MSG, app.curAdj, app.setProcState));
+                    COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
     }
 
-    @GuardedBy("mAm")
+    @GuardedBy("mProcLock")
     boolean shouldCompactBFGS(ProcessRecord app, long now) {
-        return (app.lastCompactTime == 0
-                || (now - app.lastCompactTime) > mCompactThrottleBFGS);
+        return (app.mOptRecord.getLastCompactTime() == 0
+                || (now - app.mOptRecord.getLastCompactTime()) > mCompactThrottleBFGS);
     }
 
-    @GuardedBy("mAm")
     void compactAllSystem() {
-        if (mUseCompaction) {
+        if (useCompaction()) {
             mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage(
                                               COMPACT_SYSTEM_MSG));
         }
@@ -406,6 +415,14 @@
     private native void compactSystem();
 
     /**
+     * Compacts a process or app
+     * @param pid pid of process to compact
+     * @param compactionFlags selects the compaction type as defined by COMPACT_ACTION_{TYPE}_FLAG
+     *         constants
+     */
+    static private native void compactProcess(int pid, int compactionFlags);
+
+    /**
      * Reads the flag value from DeviceConfig to determine whether app compaction
      * should be enabled, and starts the freeze/compaction thread if needed.
      */
@@ -454,21 +471,34 @@
             }
         }
 
-        try {
-            enableFreezerInternal(enable);
-            return true;
-        } catch (java.lang.RuntimeException e) {
-            if (enable) {
-                mFreezerDisableCount = 0;
-            } else {
-                mFreezerDisableCount = 1;
-            }
+        // Override is applied immediately, restore is delayed
+        synchronized (mAm) {
+            synchronized (mProcLock) {
+                mFreezerOverride = !enable;
+                Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride);
 
-            Slog.e(TAG_AM, "Exception handling freezer state (enable: " + enable + "): "
-                    + e.toString());
+                mAm.mProcessList.forEachLruProcessesLOSP(true, process -> {
+                    if (process == null) {
+                        return;
+                    }
+
+                    final ProcessCachedOptimizerRecord opt = process.mOptRecord;
+                    if (enable && opt.hasFreezerOverride()) {
+                        freezeAppAsyncLSP(process);
+                        opt.setFreezerOverride(false);
+                    }
+
+                    if (!enable && opt.isFrozen()) {
+                        unfreezeAppLSP(process);
+
+                        // Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
+                        opt.setFreezerOverride(true);
+                    }
+                });
+            }
         }
 
-        return false;
+        return true;
     }
 
     /**
@@ -515,7 +545,7 @@
         FileReader fr = null;
 
         try {
-            fr = new FileReader("/sys/fs/cgroup/freezer/cgroup.freeze");
+            fr = new FileReader("/sys/fs/cgroup/uid_0/cgroup.freeze");
             char state = (char) fr.read();
 
             if (state == '1' || state == '0') {
@@ -706,32 +736,28 @@
 
     @VisibleForTesting
     static String compactActionIntToString(int action) {
-        switch(action) {
-            case COMPACT_ACTION_NONE_FLAG:
-                return COMPACT_ACTION_NONE;
-            case COMPACT_ACTION_FILE_FLAG:
-                return COMPACT_ACTION_FILE;
-            case COMPACT_ACTION_ANON_FLAG:
-                return COMPACT_ACTION_ANON;
-            case COMPACT_ACTION_FULL_FLAG:
-                return COMPACT_ACTION_FULL;
-            default:
-                return COMPACT_ACTION_NONE;
+        if (action < 0 || action >= COMPACT_ACTION_STRING.length) {
+            return "";
         }
+
+        return COMPACT_ACTION_STRING[action];
     }
 
     // This will ensure app will be out of the freezer for at least FREEZE_TIMEOUT_MS
+    @GuardedBy("mAm")
     void unfreezeTemporarily(ProcessRecord app) {
-        synchronized (mAm) {
-            if (app.frozen) {
-                unfreezeAppLocked(app);
-                freezeAppAsync(app);
+        if (mUseFreezer) {
+            synchronized (mProcLock) {
+                if (app.mOptRecord.isFrozen()) {
+                    unfreezeAppLSP(app);
+                    freezeAppAsyncLSP(app);
+                }
             }
         }
     }
 
-    @GuardedBy("mAm")
-    void freezeAppAsync(ProcessRecord app) {
+    @GuardedBy({"mAm", "mProcLock"})
+    void freezeAppAsyncLSP(ProcessRecord app) {
         mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
 
         mFreezeHandler.sendMessageDelayed(
@@ -740,41 +766,46 @@
                 FREEZE_TIMEOUT_MS);
     }
 
-    @GuardedBy("mAm")
-    void unfreezeAppLocked(ProcessRecord app) {
+    @GuardedBy({"mAm", "mProcLock"})
+    void unfreezeAppLSP(ProcessRecord app) {
         mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
 
-        if (!app.frozen) {
+        final int pid = app.getPid();
+        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+        opt.setFreezerOverride(false);
+        if (!opt.isFrozen()) {
             if (DEBUG_FREEZER) {
                 Slog.d(TAG_AM,
-                        "Skipping unfreeze for process " + app.pid + " "
+                        "Skipping unfreeze for process " + pid + " "
                         + app.processName + " (not frozen)");
             }
             return;
         }
 
+        // Unfreeze the binder interface first, to avoid transactions triggered by timers fired
+        // right after unfreezing the process to fail
         boolean processKilled = false;
 
         try {
-            int freezeInfo = getBinderFreezeInfo(app.pid);
+            int freezeInfo = getBinderFreezeInfo(pid);
 
             if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
-                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+                Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
                         + " received sync transactions while frozen, killing");
-                app.kill("Sync transaction while in frozen state",
+                app.killLocked("Sync transaction while in frozen state",
                         ApplicationExitInfo.REASON_OTHER,
                         ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
                 processKilled = true;
             }
 
             if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
-                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
+                Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
                         + " received async transactions while frozen");
             }
         } catch (Exception e) {
-            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "
+            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + pid + " "
                     + app.processName + ". Killing it. Exception: " + e);
-            app.kill("Unable to query binder frozen stats",
+            app.killLocked("Unable to query binder frozen stats",
                     ApplicationExitInfo.REASON_OTHER,
                     ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
             processKilled = true;
@@ -784,38 +815,38 @@
             return;
         }
 
-        long freezeTime = app.freezeUnfreezeTime;
+        long freezeTime = opt.getFreezeUnfreezeTime();
 
         try {
-            freezeBinder(app.pid, false);
+            freezeBinder(pid, false);
         } catch (RuntimeException e) {
-            Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName
+            Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
                     + ". Killing it");
-            app.kill("Unable to unfreeze",
+            app.killLocked("Unable to unfreeze",
                     ApplicationExitInfo.REASON_OTHER,
                     ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
             return;
         }
 
         try {
-            Process.setProcessFrozen(app.pid, app.uid, false);
+            Process.setProcessFrozen(pid, app.uid, false);
 
-            app.freezeUnfreezeTime = SystemClock.uptimeMillis();
-            app.frozen = false;
+            opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
+            opt.setFrozen(false);
         } catch (Exception e) {
-            Slog.e(TAG_AM, "Unable to unfreeze " + app.pid + " " + app.processName
+            Slog.e(TAG_AM, "Unable to unfreeze " + pid + " " + app.processName
                     + ". This might cause inconsistency or UI hangs.");
         }
 
-        if (!app.frozen) {
+        if (!opt.isFrozen()) {
             if (DEBUG_FREEZER) {
-                Slog.d(TAG_AM, "sync unfroze " + app.pid + " " + app.processName);
+                Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
             }
 
             mFreezeHandler.sendMessage(
                     mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
-                        app.pid,
-                        (int) Math.min(app.freezeUnfreezeTime - freezeTime, Integer.MAX_VALUE),
+                        pid,
+                        (int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
                         app.processName));
         }
     }
@@ -844,6 +875,7 @@
                 case COMPACT_PROCESS_MSG: {
                     long start = SystemClock.uptimeMillis();
                     ProcessRecord proc;
+                    final ProcessCachedOptimizerRecord opt;
                     int pid;
                     String action;
                     final String name;
@@ -852,18 +884,19 @@
                     LastCompactionStats lastCompactionStats;
                     int lastOomAdj = msg.arg1;
                     int procState = msg.arg2;
-                    synchronized (mAm) {
+                    synchronized (mProcLock) {
                         proc = mPendingCompactionProcesses.remove(0);
+                        opt = proc.mOptRecord;
 
-                        pendingAction = proc.reqCompactAction;
-                        pid = proc.pid;
+                        pendingAction = opt.getReqCompactAction();
+                        pid = proc.getPid();
                         name = proc.processName;
 
                         // don't compact if the process has returned to perceptible
                         // and this is only a cached/home/prev compaction
                         if ((pendingAction == COMPACT_PROCESS_SOME
                                 || pendingAction == COMPACT_PROCESS_FULL)
-                                && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
+                                && (proc.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
                             if (DEBUG_COMPACTION) {
                                 Slog.d(TAG_AM,
                                         "Skipping compaction as process " + name + " is "
@@ -872,8 +905,8 @@
                             return;
                         }
 
-                        lastCompactAction = proc.lastCompactAction;
-                        lastCompactTime = proc.lastCompactTime;
+                        lastCompactAction = opt.getLastCompactAction();
+                        lastCompactTime = opt.getLastCompactTime();
                         lastCompactionStats = mLastCompactionStats.get(pid);
                     }
 
@@ -950,11 +983,11 @@
                             action = mCompactActionFull;
                             break;
                         default:
-                            action = COMPACT_ACTION_NONE;
+                            action = COMPACT_ACTION_STRING[COMPACT_ACTION_NONE];
                             break;
                     }
 
-                    if (COMPACT_ACTION_NONE.equals(action)) {
+                    if (COMPACT_ACTION_STRING[COMPACT_ACTION_NONE].equals(action)) {
                         return;
                     }
 
@@ -978,7 +1011,8 @@
                         return;
                     }
 
-                    if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
+                    if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
+                            || action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
                         if (mFullAnonRssThrottleKb > 0L
                                 && anonRssBefore < mFullAnonRssThrottleKb) {
                             if (DEBUG_COMPACTION) {
@@ -1050,12 +1084,12 @@
                                     lastOomAdj, ActivityManager.processStateAmToProto(procState),
                                     zramFreeKbBefore, zramFreeKbAfter);
                         }
-                        synchronized (mAm) {
-                            proc.lastCompactTime = end;
-                            proc.lastCompactAction = pendingAction;
+                        synchronized (mProcLock) {
+                            opt.setLastCompactTime(end);
+                            opt.setLastCompactAction(pendingAction);
                         }
-                        if (action.equals(COMPACT_ACTION_FULL)
-                                || action.equals(COMPACT_ACTION_ANON)) {
+                        if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])
+                                || action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
                             // Remove entry and insert again to update insertion order.
                             mLastCompactionStats.remove(pid);
                             mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
@@ -1100,11 +1134,12 @@
             }
         }
 
-        private void freezeProcess(ProcessRecord proc) {
-            final int pid = proc.pid;
+        private void freezeProcess(final ProcessRecord proc) {
+            int pid = proc.getPid(); // Unlocked intentionally
             final String name = proc.processName;
             final long unfrozenDuration;
             final boolean frozen;
+            final ProcessCachedOptimizerRecord opt = proc.mOptRecord;
 
             try {
                 // pre-check for locks to avoid unnecessary freeze/unfreeze operations
@@ -1120,36 +1155,60 @@
                 return;
             }
 
-            synchronized (mAm) {
-                if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ
-                        || proc.shouldNotFreeze) {
+            synchronized (mProcLock) {
+                pid = proc.getPid();
+                if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
+                        || opt.shouldNotFreeze()) {
                     if (DEBUG_FREEZER) {
                         Slog.d(TAG_AM, "Skipping freeze for process " + pid
-                                + " " + name + " curAdj = " + proc.curAdj
-                                + ", shouldNotFreeze = " + proc.shouldNotFreeze);
+                                + " " + name + " curAdj = " + proc.mState.getCurAdj()
+                                + ", shouldNotFreeze = " + opt.shouldNotFreeze());
                     }
                     return;
                 }
 
-                if (pid == 0 || proc.frozen) {
+                if (mFreezerOverride) {
+                    opt.setFreezerOverride(true);
+                    Slog.d(TAG_AM, "Skipping freeze for process " + pid
+                            + " " + name + " curAdj = " + proc.mState.getCurAdj()
+                            + "(override)");
+                    return;
+                }
+
+                if (pid == 0 || opt.isFrozen()) {
                     // Already frozen or not a real process, either one being
                     // launched or one being killed
                     return;
                 }
 
-                long unfreezeTime = proc.freezeUnfreezeTime;
+                // Freeze binder interface before the process, to flush any
+                // transactions that might be pending.
+                try {
+                    freezeBinder(pid, true);
+                } catch (RuntimeException e) {
+                    Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
+                    mFreezeHandler.post(() -> {
+                        synchronized (mAm) {
+                            proc.killLocked("Unable to freeze binder interface",
+                                    ApplicationExitInfo.REASON_OTHER,
+                                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
+                        }
+                    });
+                }
+
+                long unfreezeTime = opt.getFreezeUnfreezeTime();
 
                 try {
                     Process.setProcessFrozen(pid, proc.uid, true);
 
-                    proc.freezeUnfreezeTime = SystemClock.uptimeMillis();
-                    proc.frozen = true;
+                    opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
+                    opt.setFrozen(true);
                 } catch (Exception e) {
                     Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
                 }
 
-                unfrozenDuration = proc.freezeUnfreezeTime - unfreezeTime;
-                frozen = proc.frozen;
+                unfrozenDuration = opt.getFreezeUnfreezeTime() - unfreezeTime;
+                frozen = opt.isFrozen();
             }
 
             if (!frozen) {
@@ -1163,15 +1222,6 @@
 
             EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);
 
-            try {
-                freezeBinder(pid, true);
-            } catch (RuntimeException e) {
-                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
-                proc.kill("Unable to freeze binder interface",
-                        ApplicationExitInfo.REASON_OTHER,
-                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
-            }
-
             // See above for why we're not taking mPhenotypeFlagLock here
             if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
                 FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
@@ -1189,13 +1239,17 @@
                     }
 
                     synchronized (mAm) {
-                        unfreezeAppLocked(proc);
+                        synchronized (mProcLock) {
+                            unfreezeAppLSP(proc);
+                        }
                     }
                 }
             } catch (IOException e) {
                 Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
                 synchronized (mAm) {
-                    unfreezeAppLocked(proc);
+                    synchronized (mProcLock) {
+                        unfreezeAppLSP(proc);
+                    }
                 }
             }
         }
@@ -1229,8 +1283,12 @@
         // Compact process.
         @Override
         public void performCompaction(String action, int pid) throws IOException {
-            try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) {
-                fos.write(action.getBytes());
+            if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])) {
+                compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
+            } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FILE])) {
+                compactProcess(pid, COMPACT_ACTION_FILE_FLAG);
+            } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
+                compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 6d9d3fb..e6cd509 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -128,7 +128,7 @@
                 && association == null && binding.service.app != null
                 && (binding.service.appInfo.uid != clientUid
                         || !binding.service.processName.equals(clientProcessName))) {
-            ProcessStats.ProcessStateHolder holder = binding.service.app.pkgList.get(
+            ProcessStats.ProcessStateHolder holder = binding.service.app.getPkgList().get(
                     binding.service.instanceName.getPackageName());
             if (holder == null) {
                 Slog.wtf(TAG_AM, "No package in referenced service "
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index be49ce4..efee432 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+
 import android.os.Binder;
 import android.os.SystemClock;
 import android.util.Slog;
@@ -25,9 +28,6 @@
 import com.android.internal.app.procstats.AssociationState;
 import com.android.internal.app.procstats.ProcessStats;
 
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
-import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
-
 /**
  * Represents a link between a content provider and client.
  */
@@ -71,7 +71,7 @@
                 && association == null && provider.proc != null
                 && (provider.appInfo.uid != client.uid
                         || !provider.info.processName.equals(client.processName))) {
-            ProcessStats.ProcessStateHolder holder = provider.proc.pkgList.get(
+            ProcessStats.ProcessStateHolder holder = provider.proc.getPkgList().get(
                     provider.name.getPackageName());
             if (holder == null) {
                 Slog.wtf(TAG_AM, "No package in referenced provider "
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 89ed423..f43c7f6 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -57,6 +57,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -148,7 +149,7 @@
 
             ProcessRecord r = null;
             if (caller != null) {
-                r = mService.getRecordForAppLocked(caller);
+                r = mService.getRecordForAppLOSP(caller);
                 if (r == null) {
                     throw new SecurityException("Unable to find app for caller " + caller
                             + " (pid=" + Binder.getCallingPid() + ") when getting content provider "
@@ -183,14 +184,14 @@
 
             ProcessRecord dyingProc = null;
             if (cpr != null && cpr.proc != null) {
-                providerRunning = !cpr.proc.killed;
+                providerRunning = !cpr.proc.isKilled();
 
                 // Note if killedByAm is also set, this means the provider process has just been
                 // killed by AM (in ProcessRecord.kill()), but appDiedLocked() hasn't been called
                 // yet. So we need to call appDiedLocked() here and let it clean up.
                 // (See the commit message on I2c4ba1e87c2d47f2013befff10c49b3dc337a9a7 to see
                 // how to test this case.)
-                if (cpr.proc.killed && cpr.proc.killedByAm) {
+                if (cpr.proc.isKilled() && cpr.proc.isKilledByAm()) {
                     Slog.wtf(TAG, cpr.proc.toString() + " was killed by AM but isn't really dead");
                     // Now we are going to wait for the death before starting the new process.
                     dyingProc = cpr.proc;
@@ -235,7 +236,7 @@
                             callingTag, stable, true, startTime, mService.mProcessList);
 
                     checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
-                    final int verifiedAdj = cpr.proc.verifiedAdj;
+                    final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
                     boolean success = mService.updateOomAdjLocked(cpr.proc, true,
                             OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
                     // XXX things have changed so updateOomAdjLocked doesn't actually tell us
@@ -243,7 +244,7 @@
                     // it, we will check whether the process still exists.  Note that this doesn't
                     // completely get rid of races with LMK killing the process, but should make
                     // them much smaller.
-                    if (success && verifiedAdj != cpr.proc.setAdj
+                    if (success && verifiedAdj != cpr.proc.mState.getSetAdj()
                             && !isProcessAliveLocked(cpr.proc)) {
                         success = false;
                     }
@@ -275,7 +276,7 @@
                         conn = null;
                         dyingProc = cpr.proc;
                     } else {
-                        cpr.proc.verifiedAdj = cpr.proc.setAdj;
+                        cpr.proc.mState.setVerifiedAdj(cpr.proc.mState.getSetAdj());
                     }
                 } finally {
                     Binder.restoreCallingIdentity(origId);
@@ -428,15 +429,18 @@
                         checkTime(startTime, "getContentProviderImpl: looking for process record");
                         ProcessRecord proc = mService.getProcessRecordLocked(
                                 cpi.processName, cpr.appInfo.uid, false);
-                        if (proc != null && proc.thread != null && !proc.killed) {
+                        IApplicationThread thread;
+                        if (proc != null && (thread = proc.getThread()) != null
+                                && !proc.isKilled()) {
                             if (ActivityManagerDebugConfig.DEBUG_PROVIDER) {
                                 Slog.d(TAG, "Installing in existing process " + proc);
                             }
-                            if (!proc.pubProviders.containsKey(cpi.name)) {
+                            final ProcessProviderRecord pr = proc.mProviders;
+                            if (!pr.hasProvider(cpi.name)) {
                                 checkTime(startTime, "getContentProviderImpl: scheduling install");
-                                proc.pubProviders.put(cpi.name, cpr);
+                                pr.installProvider(cpi.name, cpr);
                                 try {
-                                    proc.thread.scheduleInstallProvider(cpi);
+                                    thread.scheduleInstallProvider(cpi);
                                 } catch (RemoteException e) {
                                 }
                             }
@@ -445,8 +449,8 @@
                             proc = mService.startProcessLocked(
                                     cpi.processName, cpr.appInfo, false, 0,
                                     new HostingRecord("content provider",
-                                            new ComponentName(
-                                                    cpi.applicationInfo.packageName, cpi.name)),
+                                        new ComponentName(
+                                                cpi.applicationInfo.packageName, cpi.name)),
                                     Process.ZYGOTE_POLICY_FLAG_EMPTY, false, false, false);
                             checkTime(startTime, "getContentProviderImpl: after start process");
                             if (proc == null) {
@@ -558,9 +562,9 @@
             // Note we do it after releasing the lock.
             String callerName = "unknown";
             if (caller != null) {
-                synchronized (mService) {
+                synchronized (mService.mProcLock) {
                     final ProcessRecord record =
-                            mService.mProcessList.getLRURecordForAppLocked(caller);
+                            mService.mProcessList.getLRURecordForAppLOSP(caller);
                     if (record != null) {
                         callerName = record.processName;
                     }
@@ -600,7 +604,7 @@
 
         mService.enforceNotIsolatedCaller("publishContentProviders");
         synchronized (mService) {
-            final ProcessRecord r = mService.getRecordForAppLocked(caller);
+            final ProcessRecord r = mService.getRecordForAppLOSP(caller);
             if (DEBUG_MU) {
                 Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
             }
@@ -617,7 +621,7 @@
                 if (src == null || src.info == null || src.provider == null) {
                     continue;
                 }
-                ContentProviderRecord dst = r.pubProviders.get(src.info.name);
+                ContentProviderRecord dst = r.mProviders.getProvider(src.info.name);
                 if (dst == null) {
                     continue;
                 }
@@ -808,7 +812,7 @@
             }
 
             ProcessRecord proc = conn.provider.proc;
-            if (proc == null || proc.thread == null) {
+            if (proc == null || proc.getThread() == null) {
                 // Seems like the process is already cleaned up.
                 return;
             }
@@ -816,7 +820,7 @@
             // As far as we're concerned, this is just like receiving a
             // death notification...  just a bit prematurely.
             mService.reportUidInfoMessageLocked(TAG, "Process " + proc.processName
-                            + " (pid " + proc.pid + ") early provider death", proc.info.uid);
+                            + " (pid " + proc.getPid() + ") early provider death", proc.info.uid);
             final long token = Binder.clearCallingIdentity();
             try {
                 mService.appDiedLocked(proc, "unstable content provider");
@@ -1074,7 +1078,8 @@
         }
 
         int numProviders = providers.size();
-        app.pubProviders.ensureCapacity(numProviders + app.pubProviders.size());
+        final ProcessProviderRecord pr = app.mProviders;
+        pr.ensureProviderCapacity(numProviders + pr.numberOfProviders());
         for (int i = 0; i < numProviders; i++) {
             // NOTE: keep logic in sync with installEncryptionUnawareProviders
             ProviderInfo cpi = providers.get(i);
@@ -1111,7 +1116,7 @@
             if (DEBUG_MU) {
                 Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid);
             }
-            app.pubProviders.put(cpi.name, cpr);
+            pr.installProvider(cpi.name, cpr);
             if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
                 // Don't add this if it is a platform component that is marked
                 // to run in multiple processes, because this is actually
@@ -1162,7 +1167,8 @@
     public final void installSystemProviders() {
         List<ProviderInfo> providers;
         synchronized (mService) {
-            ProcessRecord app = mService.mProcessList.mProcessNames.get("system", SYSTEM_UID);
+            ProcessRecord app = mService.mProcessList
+                    .getProcessNamesLOSP().get("system", SYSTEM_UID);
             providers = generateApplicationProvidersLocked(app);
             if (providers != null) {
                 for (int i = providers.size() - 1; i >= 0; i--) {
@@ -1205,26 +1211,29 @@
         final int matchFlags =
                 PackageManager.GET_PROVIDERS | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
-        synchronized (mService) {
-            final int numProc = mService.mProcessList.mProcessNames.getMap().size();
+        synchronized (mService.mProcLock) {
+            final ArrayMap<String, SparseArray<ProcessRecord>> pmap =
+                    mService.mProcessList.getProcessNamesLOSP().getMap();
+            final int numProc = pmap.size();
             for (int iProc = 0; iProc < numProc; iProc++) {
-                final SparseArray<ProcessRecord> apps =
-                        mService.mProcessList.mProcessNames.getMap().valueAt(iProc);
+                final SparseArray<ProcessRecord> apps = pmap.valueAt(iProc);
                 for (int iApp = 0, numApps = apps.size(); iApp < numApps; iApp++) {
                     final ProcessRecord app = apps.valueAt(iApp);
-                    if (app.userId != userId || app.thread == null || app.unlocked) continue;
+                    if (app.userId != userId || app.getThread() == null || app.isUnlocked()) {
+                        continue;
+                    }
 
-                    for (int iPkg = 0, numPkgs = app.pkgList.size(); iPkg < numPkgs; iPkg++) {
+                    app.getPkgList().forEachPackage(pkgName -> {
                         try {
-                            final String pkgName = app.pkgList.keyAt(iPkg);
                             final PackageInfo pkgInfo = AppGlobals.getPackageManager()
-                                    .getPackageInfo(pkgName, matchFlags, userId);
+                                    .getPackageInfo(pkgName, matchFlags, app.userId);
+                            final IApplicationThread thread = app.getThread();
                             if (pkgInfo != null && !ArrayUtils.isEmpty(pkgInfo.providers)) {
                                 for (ProviderInfo pi : pkgInfo.providers) {
                                     // NOTE: keep in sync with generateApplicationProvidersLocked
                                     final boolean processMatch =
                                             Objects.equals(pi.processName, app.processName)
-                                                    || pi.multiprocess;
+                                            || pi.multiprocess;
                                     final boolean userMatch = !mService.isSingleton(
                                             pi.processName, pi.applicationInfo, pi.name, pi.flags)
                                             || app.userId == UserHandle.USER_SYSTEM;
@@ -1235,7 +1244,7 @@
                                     if (processMatch && userMatch
                                             && (!isInstantApp || splitInstalled)) {
                                         Log.v(TAG, "Installing " + pi);
-                                        app.thread.scheduleInstallProvider(pi);
+                                        thread.scheduleInstallProvider(pi);
                                     } else {
                                         Log.v(TAG, "Skipping " + pi);
                                     }
@@ -1243,7 +1252,7 @@
                             }
                         } catch (RemoteException ignored) {
                         }
-                    }
+                    });
                 }
             }
         }
@@ -1260,8 +1269,9 @@
         }
 
 
-        for (int i = 0, size = r.conProviders.size(); i < size; i++) {
-            ContentProviderConnection conn = r.conProviders.get(i);
+        final ProcessProviderRecord pr = r.mProviders;
+        for (int i = 0, size = pr.numberOfProviderConnections(); i < size; i++) {
+            ContentProviderConnection conn = pr.getProviderConnectionAt(i);
             if (conn.provider == cpr) {
                 conn.incrementCount(stable);
                 return conn;
@@ -1273,10 +1283,11 @@
         conn.startAssociationIfNeeded();
         conn.initializeCount(stable);
         cpr.connections.add(conn);
-        r.conProviders.add(conn);
-        mService.startAssociationLocked(r.uid, r.processName, r.getCurProcState(),
+        pr.addProviderConnection(conn);
+        mService.startAssociationLocked(r.uid, r.processName, r.mState.getCurProcState(),
                 cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
-        if (updateLru && cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+        if (updateLru && cpr.proc != null
+                && r.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
             // If this is a perceptible app accessing the provider, make
             // sure to count it as being accessed and thus back up on
             // the LRU list.  This is good because content providers are
@@ -1326,13 +1337,14 @@
             final ContentProviderRecord cpr = conn.provider;
             conn.stopAssociation();
             cpr.connections.remove(conn);
-            conn.client.conProviders.remove(conn);
-            if (conn.client.setProcState < ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+            conn.client.mProviders.removeProviderConnection(conn);
+            if (conn.client.mState.getSetProcState()
+                    < ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
                 // The client is more important than last activity -- note the time this
                 // is happening, so we keep the old provider process around a bit as last
                 // activity to avoid thrashing it.
                 if (cpr.proc != null) {
-                    cpr.proc.lastProviderTime = SystemClock.uptimeMillis();
+                    cpr.proc.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
                 }
             }
             mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
@@ -1430,13 +1442,14 @@
             return mService.validateAssociationAllowedLocked(cpi.packageName,
                     cpi.applicationInfo.uid, null, callingUid) ? null : "<null>";
         }
-        for (int i = callingApp.pkgList.size() - 1; i >= 0; i--) {
-            if (!mService.validateAssociationAllowedLocked(callingApp.pkgList.keyAt(i),
-                    callingApp.uid, cpi.packageName, cpi.applicationInfo.uid)) {
+        final String r = callingApp.getPkgList().searchEachPackage(pkgName -> {
+            if (!mService.validateAssociationAllowedLocked(pkgName,
+                        callingApp.uid, cpi.packageName, cpi.applicationInfo.uid)) {
                 return cpi.packageName;
             }
-        }
-        return null;
+            return null;
+        });
+        return r;
     }
 
     ProviderInfo getProviderInfoLocked(String authority, @UserIdInt int userId, int pmFlags) {
@@ -1455,8 +1468,8 @@
 
     private void maybeUpdateProviderUsageStatsLocked(ProcessRecord app, String providerPkgName,
             String authority) {
-        if (app == null
-                || app.getCurProcState() > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+        if (app == null || app.mState.getCurProcState()
+                > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
             return;
         }
 
@@ -1483,31 +1496,30 @@
     private final long[] mProcessStateStatsLongs = new long[1];
 
     private boolean isProcessAliveLocked(ProcessRecord proc) {
-        if (proc.pid <= 0) {
+        final int pid = proc.getPid();
+        if (pid <= 0) {
             if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
                 Slog.d(ActivityManagerService.TAG, "Process hasn't started yet: " + proc);
             }
             return false;
         }
-        if (proc.procStatFile == null) {
-            proc.procStatFile = "/proc/" + proc.pid + "/stat";
-        }
+        final String procStatFile = "/proc/" + pid + "/stat";
         mProcessStateStatsLongs[0] = 0;
-        if (!Process.readProcFile(proc.procStatFile, PROCESS_STATE_STATS_FORMAT, null,
+        if (!Process.readProcFile(procStatFile, PROCESS_STATE_STATS_FORMAT, null,
                 mProcessStateStatsLongs, null)) {
             if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
                 Slog.d(ActivityManagerService.TAG,
-                        "UNABLE TO RETRIEVE STATE FOR " + proc.procStatFile);
+                        "UNABLE TO RETRIEVE STATE FOR " + procStatFile);
             }
             return false;
         }
         final long state = mProcessStateStatsLongs[0];
         if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
             Slog.d(ActivityManagerService.TAG,
-                    "RETRIEVED STATE FOR " + proc.procStatFile + ": " + (char) state);
+                    "RETRIEVED STATE FOR " + procStatFile + ": " + (char) state);
         }
         if (state != 'Z' && state != 'X' && state != 'x' && state != 'K') {
-            return Process.getUidForPid(proc.pid) == proc.uid;
+            return Process.getUidForPid(pid) == proc.uid;
         }
         return false;
     }
@@ -1531,13 +1543,13 @@
 
     private boolean requestTargetProviderPermissionsReviewIfNeededLocked(ProviderInfo cpi,
             ProcessRecord r, final int userId, Context context) {
-        if (!mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
+        if (!mService.getPackageManagerInternal().isPermissionsReviewRequired(
                 cpi.packageName, userId)) {
             return true;
         }
 
         final boolean callerForeground = r == null
-                || r.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
+                || r.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_BACKGROUND;
 
         // Show a permission review UI only for starting from a foreground app
         if (!callerForeground) {
@@ -1611,26 +1623,29 @@
                 }
             }
             ProcessRecord capp = conn.client;
+            final IApplicationThread thread = capp.getThread();
             conn.dead = true;
             if (conn.stableCount() > 0) {
-                if (!capp.isPersistent() && capp.thread != null
-                        && capp.pid != 0 && capp.pid != ActivityManagerService.MY_PID) {
-                    capp.kill("depends on provider " + cpr.name.flattenToShortString()
-                                    + " in dying proc " + (proc != null ? proc.processName : "??")
-                                    + " (adj " + (proc != null ? proc.setAdj : "??") + ")",
+                final int pid = capp.getPid();
+                if (!capp.isPersistent() && thread != null
+                        && pid != 0 && pid != ActivityManagerService.MY_PID) {
+                    capp.killLocked(
+                            "depends on provider " + cpr.name.flattenToShortString()
+                            + " in dying proc " + (proc != null ? proc.processName : "??")
+                            + " (adj " + (proc != null ? proc.mState.getSetAdj() : "??") + ")",
                             ApplicationExitInfo.REASON_DEPENDENCY_DIED,
                             ApplicationExitInfo.SUBREASON_UNKNOWN,
                             true);
                 }
-            } else if (capp.thread != null && conn.provider.provider != null) {
+            } else if (thread != null && conn.provider.provider != null) {
                 try {
-                    capp.thread.unstableProviderDied(conn.provider.provider.asBinder());
+                    thread.unstableProviderDied(conn.provider.provider.asBinder());
                 } catch (RemoteException e) {
                 }
                 // In the protocol here, we don't expect the client to correctly
                 // clean up this connection, we'll just remove it.
                 cpr.connections.remove(i);
-                if (conn.client.conProviders.remove(conn)) {
+                if (conn.client.mProviders.removeProviderConnection(conn)) {
                     mService.stopAssociationLocked(capp.uid, capp.processName,
                             cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
                 }
@@ -1671,7 +1686,7 @@
                 // It's being launched but we've reached maximum attempts, mark it as bad
                 alwaysBad = true;
             }
-            if (!alwaysBad && !app.bad && cpr.hasConnectionOrHandle()) {
+            if (!alwaysBad && !app.mErrorState.isBad() && cpr.hasConnectionOrHandle()) {
                 restart = true;
             } else {
                 removeDyingProviderLocked(app, cpr, true);
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index fb8b5d4..b217cae 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -19,6 +19,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 
 import android.app.ContentProviderHolder;
+import android.app.IApplicationThread;
 import android.content.ComponentName;
 import android.content.IContentProvider;
 import android.content.pm.ApplicationInfo;
@@ -211,9 +212,10 @@
                                 + " caller=" + client);
                     }
                 }
-                if (client.thread != null) {
+                final IApplicationThread thread = client.getThread();
+                if (thread != null) {
                     try {
-                        client.thread.notifyContentProviderPublishStatus(
+                        thread.notifyContentProviderPublishStatus(
                                 newHolder(status ? conn : null, false),
                                 info.authority, userId, status);
                     } catch (RemoteException e) {
@@ -343,8 +345,8 @@
                     && mAssociation == null && provider.proc != null
                     && (provider.appInfo.uid != mOwningUid
                             || !provider.info.processName.equals(mOwningProcessName))) {
-                ProcessStats.ProcessStateHolder holder = provider.proc.pkgList.get(
-                        provider.name.getPackageName());
+                ProcessStats.ProcessStateHolder holder =
+                        provider.proc.getPkgList().get(provider.name.getPackageName());
                 if (holder == null) {
                     Slog.wtf(TAG_AM, "No package in referenced provider "
                             + provider.name.toShortString() + ": proc=" + provider.proc);
@@ -373,7 +375,7 @@
                 if (hasExternalProcessHandles() &&
                         externalProcessTokenToHandle.get(mToken) != null) {
                     removeExternalProcessHandleInternalLocked(mToken);
-                }                        
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ErrorDialogController.java b/services/core/java/com/android/server/am/ErrorDialogController.java
new file mode 100644
index 0000000..ef135d5
--- /dev/null
+++ b/services/core/java/com/android/server/am/ErrorDialogController.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import android.app.Dialog;
+import android.content.Context;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A controller to generate error dialogs in {@link ProcessRecord}.
+ */
+final class ErrorDialogController {
+    private final ProcessRecord mApp;
+    private final ActivityManagerService mService;
+    private final ActivityManagerGlobalLock mProcLock;
+
+    /**
+     * Dialogs being displayed due to crash.
+     */
+    @GuardedBy("mProcLock")
+    private List<AppErrorDialog> mCrashDialogs;
+
+    /**
+     * Dialogs being displayed due to app not responding.
+     */
+    @GuardedBy("mProcLock")
+    private List<AppNotRespondingDialog> mAnrDialogs;
+
+    /**
+     * Dialogs displayed due to strict mode violation.
+     */
+    @GuardedBy("mProcLock")
+    private List<StrictModeViolationDialog> mViolationDialogs;
+
+    /**
+     * Current wait for debugger dialog.
+     */
+    @GuardedBy("mProcLock")
+    private AppWaitingForDebuggerDialog mWaitDialog;
+
+    @GuardedBy("mProcLock")
+    boolean hasCrashDialogs() {
+        return mCrashDialogs != null;
+    }
+
+    @GuardedBy("mProcLock")
+    List<AppErrorDialog> getCrashDialogs() {
+        return mCrashDialogs;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean hasAnrDialogs() {
+        return mAnrDialogs != null;
+    }
+
+    @GuardedBy("mProcLock")
+    List<AppNotRespondingDialog> getAnrDialogs() {
+        return mAnrDialogs;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean hasViolationDialogs() {
+        return mViolationDialogs != null;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean hasDebugWaitingDialog() {
+        return mWaitDialog != null;
+    }
+
+    @GuardedBy("mProcLock")
+    void clearAllErrorDialogs() {
+        clearCrashDialogs();
+        clearAnrDialogs();
+        clearViolationDialogs();
+        clearWaitingDialog();
+    }
+
+    @GuardedBy("mProcLock")
+    void clearCrashDialogs() {
+        clearCrashDialogs(true /* needDismiss */);
+    }
+
+    @GuardedBy("mProcLock")
+    void clearCrashDialogs(boolean needDismiss) {
+        if (mCrashDialogs == null) {
+            return;
+        }
+        if (needDismiss) {
+            forAllDialogs(mCrashDialogs, Dialog::dismiss);
+        }
+        mCrashDialogs = null;
+    }
+
+    @GuardedBy("mProcLock")
+    void clearAnrDialogs() {
+        if (mAnrDialogs == null) {
+            return;
+        }
+        forAllDialogs(mAnrDialogs, Dialog::dismiss);
+        mAnrDialogs = null;
+    }
+
+    @GuardedBy("mProcLock")
+    void clearViolationDialogs() {
+        if (mViolationDialogs == null) {
+            return;
+        }
+        forAllDialogs(mViolationDialogs, Dialog::dismiss);
+        mViolationDialogs = null;
+    }
+
+    @GuardedBy("mProcLock")
+    void clearWaitingDialog() {
+        if (mWaitDialog == null) {
+            return;
+        }
+        mWaitDialog.dismiss();
+        mWaitDialog = null;
+    }
+
+    void forAllDialogs(List<? extends BaseErrorDialog> dialogs, Consumer<BaseErrorDialog> c) {
+        for (int i = dialogs.size() - 1; i >= 0; i--) {
+            c.accept(dialogs.get(i));
+        }
+    }
+
+    @GuardedBy("mProcLock")
+    void showCrashDialogs(AppErrorDialog.Data data) {
+        List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
+        mCrashDialogs = new ArrayList<>();
+        for (int i = contexts.size() - 1; i >= 0; i--) {
+            final Context c = contexts.get(i);
+            mCrashDialogs.add(new AppErrorDialog(c, mService, data));
+        }
+        mService.mUiHandler.post(() -> {
+            List<AppErrorDialog> dialogs;
+            synchronized (mProcLock) {
+                dialogs = mCrashDialogs;
+            }
+            if (dialogs != null) {
+                forAllDialogs(dialogs, Dialog::show);
+            }
+        });
+    }
+
+    @GuardedBy("mProcLock")
+    void showAnrDialogs(AppNotRespondingDialog.Data data) {
+        List<Context> contexts = getDisplayContexts(
+                mApp.mErrorState.isSilentAnr() /* lastUsedOnly */);
+        mAnrDialogs = new ArrayList<>();
+        for (int i = contexts.size() - 1; i >= 0; i--) {
+            final Context c = contexts.get(i);
+            mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data));
+        }
+        mService.mUiHandler.post(() -> {
+            List<AppNotRespondingDialog> dialogs;
+            synchronized (mProcLock) {
+                dialogs = mAnrDialogs;
+            }
+            if (dialogs != null) {
+                forAllDialogs(dialogs, Dialog::show);
+            }
+        });
+    }
+
+    @GuardedBy("mProcLock")
+    void showViolationDialogs(AppErrorResult res) {
+        List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
+        mViolationDialogs = new ArrayList<>();
+        for (int i = contexts.size() - 1; i >= 0; i--) {
+            final Context c = contexts.get(i);
+            mViolationDialogs.add(
+                    new StrictModeViolationDialog(c, mService, res, mApp));
+        }
+        mService.mUiHandler.post(() -> {
+            List<StrictModeViolationDialog> dialogs;
+            synchronized (mProcLock) {
+                dialogs = mViolationDialogs;
+            }
+            if (dialogs != null) {
+                forAllDialogs(dialogs, Dialog::show);
+            }
+        });
+    }
+
+    @GuardedBy("mProcLock")
+    void showDebugWaitingDialogs() {
+        List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */);
+        final Context c = contexts.get(0);
+        mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, mApp);
+
+        mService.mUiHandler.post(() -> {
+            Dialog dialog;
+            synchronized (mProcLock) {
+                dialog = mWaitDialog;
+            }
+            if (dialog != null) {
+                dialog.show();
+            }
+        });
+    }
+
+    /**
+     * Helper function to collect contexts from crashed app located displays.
+     *
+     * @param lastUsedOnly Sets to {@code true} to indicate to only get last used context.
+     *                     Sets to {@code false} to collect contexts from crashed app located
+     *                     displays.
+     *
+     * @return display context list.
+     */
+    private List<Context> getDisplayContexts(boolean lastUsedOnly) {
+        List<Context> displayContexts = new ArrayList<>();
+        if (!lastUsedOnly) {
+            mApp.getWindowProcessController().getDisplayContextsWithErrorDialogs(displayContexts);
+        }
+        // If there is no foreground window display, fallback to last used display.
+        if (displayContexts.isEmpty() || lastUsedOnly) {
+            displayContexts.add(mService.mWmInternal != null
+                    ? mService.mWmInternal.getTopFocusedDisplayUiContext()
+                    : mService.mUiContext);
+        }
+        return displayContexts;
+    }
+
+    ErrorDialogController(ProcessRecord app) {
+        mApp = app;
+        mService = app.mService;
+        mProcLock = mService.mProcLock;
+    }
+}
diff --git a/services/core/java/com/android/server/am/FgsStartTempAllowList.java b/services/core/java/com/android/server/am/FgsStartTempAllowList.java
index 3aca4cf..4d8749c 100644
--- a/services/core/java/com/android/server/am/FgsStartTempAllowList.java
+++ b/services/core/java/com/android/server/am/FgsStartTempAllowList.java
@@ -70,4 +70,8 @@
             return true;
         }
     }
+
+    void remove(int uid) {
+        mTempAllowListFgs.delete(uid);
+    }
 }
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
index 6e42aee..94eb076 100644
--- a/services/core/java/com/android/server/am/NativeCrashListener.java
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -16,6 +16,12 @@
 
 package com.android.server.am;
 
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVTIMEO;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
 import android.app.ApplicationErrorReport.CrashInfo;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -23,8 +29,6 @@
 import android.system.UnixSocketAddress;
 import android.util.Slog;
 
-import static android.system.OsConstants.*;
-
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -259,8 +263,10 @@
                     // even though the process will vanish as soon as we let
                     // debuggerd proceed.
                     synchronized (mAm) {
-                        pr.setCrashing(true);
-                        pr.forceCrashReport = true;
+                        synchronized (mAm.mProcLock) {
+                            pr.mErrorState.setCrashing(true);
+                            pr.mErrorState.setForceCrashReport(true);
+                        }
                     }
 
                     // Crash reporting is synchronous but we want to let debuggerd
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index de79315..d79fb8a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -51,6 +51,7 @@
 import static android.os.Process.setThreadPriority;
 import static android.os.Process.setThreadScheduler;
 
+import static com.android.server.am.ActiveServices.FGS_FEATURE_DENIED;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
@@ -65,10 +66,10 @@
 import static com.android.server.am.ActivityManagerService.TAG_BACKUP;
 import static com.android.server.am.ActivityManagerService.TAG_LRU;
 import static com.android.server.am.ActivityManagerService.TAG_OOM_ADJ;
-import static com.android.server.am.ActivityManagerService.TAG_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
 import static com.android.server.am.AppProfiler.TAG_PSS;
+import static com.android.server.am.ProcessList.TAG_PROCESS_OBSERVERS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
 import android.app.ActivityManager;
@@ -99,6 +100,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.compat.IPlatformCompat;
@@ -131,7 +133,7 @@
     static final String OOM_ADJ_REASON_GET_PROVIDER = OOM_ADJ_REASON_METHOD + "_getProvider";
     static final String OOM_ADJ_REASON_REMOVE_PROVIDER = OOM_ADJ_REASON_METHOD + "_removeProvider";
     static final String OOM_ADJ_REASON_UI_VISIBILITY = OOM_ADJ_REASON_METHOD + "_uiVisibility";
-    static final String OOM_ADJ_REASON_WHITELIST = OOM_ADJ_REASON_METHOD + "_whitelistChange";
+    static final String OOM_ADJ_REASON_ALLOWLIST = OOM_ADJ_REASON_METHOD + "_allowlistChange";
     static final String OOM_ADJ_REASON_PROCESS_BEGIN = OOM_ADJ_REASON_METHOD + "_processBegin";
     static final String OOM_ADJ_REASON_PROCESS_END = OOM_ADJ_REASON_METHOD + "_processEnd";
 
@@ -205,11 +207,12 @@
     int mNumCachedHiddenProcs = 0;
 
     /** Track all uids that have actively running processes. */
+    @CompositeRWLock({"mService", "mProcLock"})
     ActiveUids mActiveUids;
 
     /**
      * The handler to execute {@link #setProcessGroup} (it may be heavy if the process has many
-     * threads) for reducing the time spent in {@link #applyOomAdjLocked}.
+     * threads) for reducing the time spent in {@link #applyOomAdjLSP}.
      */
     private final Handler mProcessGroupHandler;
 
@@ -217,6 +220,7 @@
 
     private final ActivityManagerService mService;
     private final ProcessList mProcessList;
+    private final ActivityManagerGlobalLock mProcLock;
 
     private final int mNumSlots;
     private final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>();
@@ -332,12 +336,13 @@
             ServiceThread adjusterThread) {
         mService = service;
         mProcessList = processList;
+        mProcLock = service.mProcLock;
         mActiveUids = activeUids;
 
         mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
         mConstants = mService.mConstants;
         mCachedAppOptimizer = new CachedAppOptimizer(mService);
-        mCacheOomRanker = new CacheOomRanker();
+        mCacheOomRanker = new CacheOomRanker(service);
 
         mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
             final int pid = msg.arg1;
@@ -388,23 +393,27 @@
     @VisibleForTesting
     @GuardedBy("mService")
     void handleUserSwitchedLocked() {
+        mProcessList.forEachLruProcessesLOSP(false,
+                this::updateKeepWarmIfNecessaryForProcessLocked);
+    }
+
+    @GuardedBy("mService")
+    private void updateKeepWarmIfNecessaryForProcessLocked(final ProcessRecord app) {
         final ArraySet<ComponentName> warmServices = mService.mConstants.KEEP_WARMING_SERVICES;
-        final ArrayList<ProcessRecord> processes = mProcessList.mLruProcesses;
-        for (int i = processes.size() - 1; i >= 0; i--) {
-            final ProcessRecord app = processes.get(i);
-            boolean includeWarmPkg = false;
-            for (int j = warmServices.size() - 1; j >= 0; j--) {
-                if (app.pkgList.containsKey(warmServices.valueAt(j).getPackageName())) {
-                    includeWarmPkg = true;
-                    break;
-                }
+        boolean includeWarmPkg = false;
+        final PackageList pkgList = app.getPkgList();
+        for (int j = warmServices.size() - 1; j >= 0; j--) {
+            if (pkgList.containsKey(warmServices.valueAt(j).getPackageName())) {
+                includeWarmPkg = true;
+                break;
             }
-            if (!includeWarmPkg) {
-                continue;
-            }
-            for (int j = app.numberOfRunningServices() - 1; j >= 0; j--) {
-                app.getRunningServiceAt(j).updateKeepWarmLocked();
-            }
+        }
+        if (!includeWarmPkg) {
+            return;
+        }
+        final ProcessServiceRecord psr = app.mServices;
+        for (int j = psr.numberOfRunningServices() - 1; j >= 0; j--) {
+            psr.getRunningServiceAt(j).updateKeepWarmLocked();
         }
     }
 
@@ -419,11 +428,20 @@
     @GuardedBy("mService")
     boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll,
             String oomAdjReason) {
-        if (oomAdjAll && mConstants.OOMADJ_UPDATE_QUICK) {
-            return updateOomAdjLocked(app, oomAdjReason);
+        synchronized (mProcLock) {
+            return updateOomAdjLSP(app, oomAdjAll, oomAdjReason);
         }
-        final ProcessRecord TOP_APP = mService.getTopAppLocked();
-        final boolean wasCached = app.isCached();
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private boolean updateOomAdjLSP(ProcessRecord app, boolean oomAdjAll,
+            String oomAdjReason) {
+        if (oomAdjAll && mConstants.OOMADJ_UPDATE_QUICK) {
+            return updateOomAdjLSP(app, oomAdjReason);
+        }
+        final ProcessRecord topApp = mService.getTopApp();
+        final ProcessStateRecord state = app.mState;
+        final boolean wasCached = state.isCached();
 
         mAdjSeq++;
 
@@ -431,30 +449,31 @@
         // If our app is currently cached, we know it, and that is it.  Otherwise,
         // we don't know it yet, and it needs to now be cached we will then
         // need to do a complete oom adj.
-        final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
-                ? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
+        final int cachedAdj = state.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
+                ? state.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
         // Check if this process is in the pending list too, remove from pending list if so.
         mPendingProcessSet.remove(app);
-        boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
+        boolean success = updateOomAdjLSP(app, cachedAdj, topApp, false,
                 SystemClock.uptimeMillis());
         if (oomAdjAll
-                && (wasCached != app.isCached() || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
+                && (wasCached != state.isCached()
+                    || state.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
             // Changed to/from cached state, so apps after it in the LRU
             // list may also be changed.
-            updateOomAdjLocked(oomAdjReason);
+            updateOomAdjLSP(oomAdjReason);
         }
         return success;
     }
 
-    @GuardedBy("mService")
-    private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
+    @GuardedBy({"mService", "mProcLock"})
+    private boolean updateOomAdjLSP(ProcessRecord app, int cachedAdj,
             ProcessRecord TOP_APP, boolean doingAll, long now) {
-        if (app.thread == null) {
+        if (app.getThread() == null) {
             return false;
         }
 
-        app.resetCachedInfo();
-        UidRecord uidRec = app.uidRecord;
+        app.mState.resetCachedInfo();
+        UidRecord uidRec = app.getUidRecord();
         if (uidRec != null) {
             if (DEBUG_UID_OBSERVERS) {
                 Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
@@ -465,35 +484,23 @@
         // Check if this process is in the pending list too, remove from pending list if so.
         mPendingProcessSet.remove(app);
 
-        computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false, true);
+        computeOomAdjLSP(app, cachedAdj, TOP_APP, doingAll, now, false, true);
 
-        boolean success = applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
+        boolean success = applyOomAdjLSP(app, doingAll, now, SystemClock.elapsedRealtime());
 
         if (uidRec != null) {
-            // After uidRec.reset() above, for UidRecord that has multiple processes (ProcessRecord)
-            // , We need to apply all ProcessRecord into UidRecord.
-            final ArraySet<ProcessRecord> procRecords = app.uidRecord.procRecords;
-            for (int i = procRecords.size() - 1; i >= 0; i--) {
-                final ProcessRecord pr = procRecords.valueAt(i);
-                if (!pr.killedByAm && pr.thread != null) {
-                    if (pr.isolated && pr.numberOfRunningServices() <= 0
-                            && pr.isolatedEntryPoint == null) {
-                        // No op.
-                    } else {
-                        // Keeping this process, update its uid.
-                        updateAppUidRecLocked(pr);
-                    }
-                }
-            }
+            // After uidRec.reset() above, for UidRecord with multiple processes (ProcessRecord),
+            // we need to apply all ProcessRecord into UidRecord.
+            uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
             if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
-                    && (uidRec.setProcState != uidRec.getCurProcState()
-                    || uidRec.setCapability != uidRec.curCapability
-                    || uidRec.setWhitelist != uidRec.curWhitelist)) {
+                    && (uidRec.getSetProcState() != uidRec.getCurProcState()
+                    || uidRec.getSetCapability() != uidRec.getCurCapability()
+                    || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) {
                 ActiveUids uids = mTmpUidRecords;
                 uids.clear();
-                uids.put(uidRec.uid, uidRec);
-                updateUidsLocked(uids, SystemClock.elapsedRealtime());
-                mProcessList.incrementProcStateSeqAndNotifyAppsLocked(uids);
+                uids.put(uidRec.getUid(), uidRec);
+                updateUidsLSP(uids, SystemClock.elapsedRealtime());
+                mProcessList.incrementProcStateSeqAndNotifyAppsLOSP(uids);
             }
         }
 
@@ -505,11 +512,18 @@
      */
     @GuardedBy("mService")
     void updateOomAdjLocked(String oomAdjReason) {
-        final ProcessRecord topApp = mService.getTopAppLocked();
+        synchronized (mProcLock) {
+            updateOomAdjLSP(oomAdjReason);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateOomAdjLSP(String oomAdjReason) {
+        final ProcessRecord topApp = mService.getTopApp();
         // Clear any pending ones because we are doing a full update now.
         mPendingProcessSet.clear();
         mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
-        updateOomAdjLockedInner(oomAdjReason, topApp , null, null, true, true);
+        updateOomAdjInnerLSP(oomAdjReason, topApp , null, null, true, true);
     }
 
     /**
@@ -522,32 +536,42 @@
      */
     @GuardedBy("mService")
     boolean updateOomAdjLocked(ProcessRecord app, String oomAdjReason) {
+        synchronized (mProcLock) {
+            return updateOomAdjLSP(app, oomAdjReason);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private boolean updateOomAdjLSP(ProcessRecord app, String oomAdjReason) {
         if (app == null || !mConstants.OOMADJ_UPDATE_QUICK) {
-            updateOomAdjLocked(oomAdjReason);
+            updateOomAdjLSP(oomAdjReason);
             return true;
         }
 
-        final ProcessRecord topApp = mService.getTopAppLocked();
+        final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReason);
         mService.mOomAdjProfiler.oomAdjStarted();
         mAdjSeq++;
 
         // Firstly, try to see if the importance of itself gets changed
-        final boolean wasCached = app.isCached();
-        final int oldAdj = app.getCurRawAdj();
+        final ProcessStateRecord state = app.mState;
+        final boolean wasCached = state.isCached();
+        final int oldAdj = state.getCurRawAdj();
         final int cachedAdj = oldAdj >= ProcessList.CACHED_APP_MIN_ADJ
                 ? oldAdj : ProcessList.UNKNOWN_ADJ;
-        final boolean wasBackground = ActivityManager.isProcStateBackground(app.setProcState);
-        app.containsCycle = false;
-        app.procStateChanged = false;
-        app.resetCachedInfo();
+        final boolean wasBackground = ActivityManager.isProcStateBackground(
+                state.getSetProcState());
+        state.setContainsCycle(false);
+        state.setProcStateChanged(false);
+        state.resetCachedInfo();
         // Check if this process is in the pending list too, remove from pending list if so.
         mPendingProcessSet.remove(app);
-        boolean success = updateOomAdjLocked(app, cachedAdj, topApp, false,
+        boolean success = updateOomAdjLSP(app, cachedAdj, topApp, false,
                 SystemClock.uptimeMillis());
-        if (!success || (wasCached == app.isCached() && oldAdj != ProcessList.INVALID_ADJ
-                && wasBackground == ActivityManager.isProcStateBackground(app.setProcState))) {
+        if (!success || (wasCached == state.isCached() && oldAdj != ProcessList.INVALID_ADJ
+                && wasBackground == ActivityManager.isProcStateBackground(
+                        state.getSetProcState()))) {
             // Okay, it's unchanged, it won't impact any service it binds to, we're done here.
             if (DEBUG_OOM_ADJ) {
                 Slog.i(TAG_OOM_ADJ, "No oomadj changes for " + app);
@@ -569,23 +593,25 @@
         // Track if any of them reachables could include a cycle
         boolean containsCycle = false;
         // Scan downstreams of the process record
-        app.mReachable = true;
+        state.setReachable(true);
         for (ProcessRecord pr = app; pr != null; pr = queue.poll()) {
             if (pr != app) {
                 processes.add(pr);
             }
-            if (pr.uidRecord != null) {
-                uids.put(pr.uidRecord.uid, pr.uidRecord);
+            final UidRecord uidRec = pr.getUidRecord();
+            if (uidRec != null) {
+                uids.put(uidRec.getUid(), uidRec);
             }
-            for (int i = pr.connections.size() - 1; i >= 0; i--) {
-                ConnectionRecord cr = pr.connections.valueAt(i);
+            final ProcessServiceRecord psr = pr.mServices;
+            for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
+                ConnectionRecord cr = psr.getConnectionAt(i);
                 ProcessRecord service = (cr.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
                         ? cr.binding.service.isolatedProc : cr.binding.service.app;
                 if (service == null || service == pr) {
                     continue;
                 }
-                containsCycle |= service.mReachable;
-                if (service.mReachable) {
+                containsCycle |= service.mState.isReachable();
+                if (service.mState.isReachable()) {
                     continue;
                 }
                 if ((cr.flags & (Context.BIND_WAIVE_PRIORITY
@@ -595,24 +621,26 @@
                     continue;
                 }
                 queue.offer(service);
-                service.mReachable = true;
+                service.mState.setReachable(true);
                 // During scanning the reachable dependants, remove them from the pending oomadj
                 // targets list if it's possible, as they've been added into the immediate
                 // oomadj targets list 'processes' above.
                 mPendingProcessSet.remove(service);
             }
-            for (int i = pr.conProviders.size() - 1; i >= 0; i--) {
-                ContentProviderConnection cpc = pr.conProviders.get(i);
+            final ProcessProviderRecord ppr = pr.mProviders;
+            for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) {
+                ContentProviderConnection cpc = ppr.getProviderConnectionAt(i);
                 ProcessRecord provider = cpc.provider.proc;
-                if (provider == null || provider == pr || (containsCycle |= provider.mReachable)) {
+                if (provider == null || provider == pr
+                        || (containsCycle |= provider.mState.isReachable())) {
                     continue;
                 }
-                containsCycle |= provider.mReachable;
-                if (provider.mReachable) {
+                containsCycle |= provider.mState.isReachable();
+                if (provider.mState.isReachable()) {
                     continue;
                 }
                 queue.offer(provider);
-                provider.mReachable = true;
+                provider.mState.setReachable(true);
                 // During scanning the reachable dependants, remove them from the pending oomadj
                 // targets list if it's possible, as they've been added into the immediate
                 // oomadj targets list 'processes' above.
@@ -621,10 +649,10 @@
         }
 
         // Reset the flag
-        app.mReachable = false;
+        state.setReachable(false);
         int size = processes.size();
         if (size > 0) {
-            // Reverse the process list, since the updateOomAdjLockedInner scans from the end of it.
+            // Reverse the process list, since the updateOomAdjInnerLSP scans from the end of it.
             for (int l = 0, r = size - 1; l < r; l++, r--) {
                 ProcessRecord t = processes.get(l);
                 processes.set(l, processes.get(r));
@@ -632,13 +660,13 @@
             }
             mAdjSeq--;
             // Update these reachable processes
-            updateOomAdjLockedInner(oomAdjReason, topApp, processes, uids, containsCycle, false);
-        } else if (app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ) {
+            updateOomAdjInnerLSP(oomAdjReason, topApp, processes, uids, containsCycle, false);
+        } else if (state.getCurRawAdj() == ProcessList.UNKNOWN_ADJ) {
             // In case the app goes from non-cached to cached but it doesn't have other reachable
             // processes, its adj could be still unknown as of now, assign one.
             processes.add(app);
             assignCachedAdjIfNecessary(processes);
-            applyOomAdjLocked(app, false, SystemClock.uptimeMillis(),
+            applyOomAdjLSP(app, false, SystemClock.uptimeMillis(),
                     SystemClock.elapsedRealtime());
         }
         mTmpProcessList.clear();
@@ -676,24 +704,27 @@
         if (mPendingProcessSet.isEmpty()) {
             return;
         }
-        final ProcessRecord topApp = mService.getTopAppLocked();
+        final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReason);
         mService.mOomAdjProfiler.oomAdjStarted();
 
         final ArrayList<ProcessRecord> processes = mTmpProcessList;
         final ActiveUids uids = mTmpUidRecords;
-        uids.clear();
-        processes.clear();
-        for (int i = mPendingProcessSet.size() - 1; i >= 0; i--) {
-            final ProcessRecord app = mPendingProcessSet.valueAt(i);
-            if (app.uidRecord != null) {
-                uids.put(app.uidRecord.uid, app.uidRecord);
+        synchronized (mProcLock) {
+            uids.clear();
+            processes.clear();
+            for (int i = mPendingProcessSet.size() - 1; i >= 0; i--) {
+                final ProcessRecord app = mPendingProcessSet.valueAt(i);
+                final UidRecord uidRec = app.getUidRecord();
+                if (uidRec != null) {
+                    uids.put(uidRec.getUid(), uidRec);
+                }
+                processes.add(app);
             }
-            processes.add(app);
-        }
 
-        updateOomAdjLockedInner(oomAdjReason, topApp, processes, uids, true, false);
+            updateOomAdjInnerLSP(oomAdjReason, topApp, processes, uids, true, false);
+        }
         processes.clear();
         mPendingProcessSet.clear();
 
@@ -706,8 +737,8 @@
      * list if the given list is null; when it's partial update, each process's client proc won't
      * get evaluated recursively here.
      */
-    @GuardedBy("mService")
-    private void updateOomAdjLockedInner(String oomAdjReason, final ProcessRecord topApp,
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateOomAdjInnerLSP(String oomAdjReason, final ProcessRecord topApp,
             ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
             boolean startProfiling) {
         if (startProfiling) {
@@ -719,7 +750,7 @@
         final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
         final boolean fullUpdate = processes == null;
         ActiveUids activeUids = uids;
-        ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.mLruProcesses
+        ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.getLruProcessesLOSP()
                 : processes;
         final int numProc = activeProcesses.size();
 
@@ -728,8 +759,8 @@
             activeUids = mTmpUidRecords;
             activeUids.clear();
             for (int i = 0; i < numUids; i++) {
-                UidRecord r = mActiveUids.valueAt(i);
-                activeUids.put(r.uid, r);
+                UidRecord uidRec = mActiveUids.valueAt(i);
+                activeUids.put(uidRec.getUid(), uidRec);
             }
         }
 
@@ -751,36 +782,39 @@
         boolean retryCycles = false;
         boolean computeClients = fullUpdate || potentialCycles;
 
-        // need to reset cycle state before calling computeOomAdjLocked because of service conns
+        // need to reset cycle state before calling computeOomAdjLSP because of service conns
         for (int i = numProc - 1; i >= 0; i--) {
             ProcessRecord app = activeProcesses.get(i);
-            app.mReachable = false;
+            final ProcessStateRecord state = app.mState;
+            state.setReachable(false);
             // No need to compute again it has been evaluated in previous iteration
-            if (app.adjSeq != mAdjSeq) {
-                app.containsCycle = false;
-                app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
-                app.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
-                app.setCapability = PROCESS_CAPABILITY_NONE;
-                app.resetCachedInfo();
+            if (state.getAdjSeq() != mAdjSeq) {
+                state.setContainsCycle(false);
+                state.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
+                state.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
+                state.setSetCapability(PROCESS_CAPABILITY_NONE);
+                state.resetCachedInfo();
             }
         }
         for (int i = numProc - 1; i >= 0; i--) {
             ProcessRecord app = activeProcesses.get(i);
-            if (!app.killedByAm && app.thread != null) {
-                app.procStateChanged = false;
-                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, topApp, fullUpdate, now, false,
+            final ProcessStateRecord state = app.mState;
+            if (!app.isKilledByAm() && app.getThread() != null) {
+                state.setProcStateChanged(false);
+                computeOomAdjLSP(app, ProcessList.UNKNOWN_ADJ, topApp, fullUpdate, now, false,
                         computeClients); // It won't enter cycle if not computing clients.
                 // if any app encountered a cycle, we need to perform an additional loop later
-                retryCycles |= app.containsCycle;
+                retryCycles |= state.containsCycle();
                 // Keep the completedAdjSeq to up to date.
-                app.completedAdjSeq = mAdjSeq;
+                state.setCompletedAdjSeq(mAdjSeq);
             }
         }
 
         if (mCacheOomRanker.useOomReranking()) {
-            mCacheOomRanker.reRankLruCachedApps(mProcessList);
+            mCacheOomRanker.reRankLruCachedAppsLSP(mProcessList.getLruProcessesLSP(),
+                    mProcessList.getLruProcessServiceStartLOSP());
         }
-        assignCachedAdjIfNecessary(mProcessList.mLruProcesses);
+        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
 
         if (computeClients) { // There won't be cycles if we didn't compute clients above.
             // Cycle strategy:
@@ -794,16 +828,18 @@
 
                 for (int i = 0; i < numProc; i++) {
                     ProcessRecord app = activeProcesses.get(i);
-                    if (!app.killedByAm && app.thread != null && app.containsCycle) {
-                        app.adjSeq--;
-                        app.completedAdjSeq--;
+                    final ProcessStateRecord state = app.mState;
+                    if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) {
+                        state.decAdjSeq();
+                        state.decCompletedAdjSeq();
                     }
                 }
 
                 for (int i = 0; i < numProc; i++) {
                     ProcessRecord app = activeProcesses.get(i);
-                    if (!app.killedByAm && app.thread != null && app.containsCycle) {
-                        if (computeOomAdjLocked(app, app.getCurRawAdj(), topApp, true, now,
+                    final ProcessStateRecord state = app.mState;
+                    if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) {
+                        if (computeOomAdjLSP(app, state.getCurRawAdj(), topApp, true, now,
                                 true, true)) {
                             retryCycles = true;
                         }
@@ -815,7 +851,7 @@
         mNumNonCachedProcs = 0;
         mNumCachedHiddenProcs = 0;
 
-        boolean allChanged = updateAndTrimProcessLocked(now, nowElapsed, oldTime, activeUids);
+        boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids);
         mNumServiceProcs = mNewNumServiceProcs;
 
         if (mService.mAlwaysFinishActivities) {
@@ -825,11 +861,11 @@
         }
 
         if (allChanged) {
-            mService.mAppProfiler.requestPssAllProcsLocked(now, false,
+            mService.mAppProfiler.requestPssAllProcsLPr(now, false,
                     mService.mProcessStats.isMemFactorLowered());
         }
 
-        updateUidsLocked(activeUids, nowElapsed);
+        updateUidsLSP(activeUids, nowElapsed);
 
         synchronized (mService.mProcessStats.mLock) {
             if (mService.mProcessStats.shouldWriteNowLocked(now)) {
@@ -856,6 +892,7 @@
         }
     }
 
+    @GuardedBy({"mService", "mProcLock"})
     private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
         final int numLru = lruList.size();
 
@@ -899,23 +936,27 @@
 
         for (int i = numLru - 1; i >= 0; i--) {
             ProcessRecord app = lruList.get(i);
+            final ProcessStateRecord state = app.mState;
             // If we haven't yet assigned the final cached adj
             // to the process, do that now.
-            if (!app.killedByAm && app.thread != null && app.curAdj
+            if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
                     >= ProcessList.UNKNOWN_ADJ) {
-                switch (app.getCurProcState()) {
+                final ProcessServiceRecord psr = app.mServices;
+                switch (state.getCurProcState()) {
                     case PROCESS_STATE_CACHED_ACTIVITY:
                     case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                     case ActivityManager.PROCESS_STATE_CACHED_RECENT:
                         // Figure out the next cached level, taking into account groups.
                         boolean inGroup = false;
-                        if (app.connectionGroup != 0) {
+                        final int connectionGroup = psr.getConnectionGroup();
+                        if (connectionGroup != 0) {
+                            final int connectionImportance = psr.getConnectionImportance();
                             if (lastCachedGroupUid == app.uid
-                                    && lastCachedGroup == app.connectionGroup) {
+                                    && lastCachedGroup == connectionGroup) {
                                 // This is in the same group as the last process, just tweak
                                 // adjustment by importance.
-                                if (app.connectionImportance > lastCachedGroupImportance) {
-                                    lastCachedGroupImportance = app.connectionImportance;
+                                if (connectionImportance > lastCachedGroupImportance) {
+                                    lastCachedGroupImportance = connectionImportance;
                                     if (curCachedAdj < nextCachedAdj
                                             && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
                                         curCachedImpAdj++;
@@ -924,8 +965,8 @@
                                 inGroup = true;
                             } else {
                                 lastCachedGroupUid = app.uid;
-                                lastCachedGroup = app.connectionGroup;
-                                lastCachedGroupImportance = app.connectionImportance;
+                                lastCachedGroup = connectionGroup;
+                                lastCachedGroupImportance = connectionImportance;
                             }
                         }
                         if (!inGroup && curCachedAdj != nextCachedAdj) {
@@ -943,11 +984,12 @@
                         // This process is a cached process holding activities...
                         // assign it the next cached value for that type, and then
                         // step that cached level.
-                        app.setCurRawAdj(curCachedAdj + curCachedImpAdj);
-                        app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj);
+                        state.setCurRawAdj(curCachedAdj + curCachedImpAdj);
+                        state.setCurAdj(psr.modifyRawOomAdj(curCachedAdj + curCachedImpAdj));
                         if (DEBUG_LRU) {
                             Slog.d(TAG_LRU, "Assigning activity LRU #" + i
-                                    + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+                                    + " adj: " + state.getCurAdj()
+                                    + " (curCachedAdj=" + curCachedAdj
                                     + " curCachedImpAdj=" + curCachedImpAdj + ")");
                         }
                         break;
@@ -969,11 +1011,11 @@
                         // long-running services that have dropped down to the
                         // cached level will be treated as empty (since their process
                         // state is still as a service), which is what we want.
-                        app.setCurRawAdj(curEmptyAdj);
-                        app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
+                        state.setCurRawAdj(curEmptyAdj);
+                        state.setCurAdj(psr.modifyRawOomAdj(curEmptyAdj));
                         if (DEBUG_LRU) {
                             Slog.d(TAG_LRU, "Assigning empty LRU #" + i
-                                    + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
+                                    + " adj: " + state.getCurAdj() + " (curEmptyAdj=" + curEmptyAdj
                                     + ")");
                         }
                         break;
@@ -982,9 +1024,10 @@
         }
     }
 
-    private boolean updateAndTrimProcessLocked(final long now, final long nowElapsed,
+    @GuardedBy({"mService", "mProcLock"})
+    private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
             final long oldTime, final ActiveUids activeUids) {
-        ArrayList<ProcessRecord> lruList = mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
         final int numLru = lruList.size();
 
         final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
@@ -999,34 +1042,37 @@
 
         for (int i = numLru - 1; i >= 0; i--) {
             ProcessRecord app = lruList.get(i);
-            if (!app.killedByAm && app.thread != null) {
+            final ProcessStateRecord state = app.mState;
+            if (!app.isKilledByAm() && app.getThread() != null) {
                 // We don't need to apply the update for the process which didn't get computed
-                if (app.completedAdjSeq == mAdjSeq) {
-                    applyOomAdjLocked(app, true, now, nowElapsed);
+                if (state.getCompletedAdjSeq() == mAdjSeq) {
+                    applyOomAdjLSP(app, true, now, nowElapsed);
                 }
 
+                final ProcessServiceRecord psr = app.mServices;
                 // Count the number of process types.
-                switch (app.getCurProcState()) {
+                switch (state.getCurProcState()) {
                     case PROCESS_STATE_CACHED_ACTIVITY:
                     case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                         mNumCachedHiddenProcs++;
                         numCached++;
-                        if (app.connectionGroup != 0) {
+                        final int connectionGroup = psr.getConnectionGroup();
+                        if (connectionGroup != 0) {
                             if (lastCachedGroupUid == app.info.uid
-                                    && lastCachedGroup == app.connectionGroup) {
+                                    && lastCachedGroup == connectionGroup) {
                                 // If this process is the next in the same group, we don't
                                 // want it to count against our limit of the number of cached
                                 // processes, so bump up the group count to account for it.
                                 numCachedExtraGroup++;
                             } else {
                                 lastCachedGroupUid = app.info.uid;
-                                lastCachedGroup = app.connectionGroup;
+                                lastCachedGroup = connectionGroup;
                             }
                         } else {
                             lastCachedGroupUid = lastCachedGroup = 0;
                         }
                         if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
-                            app.kill("cached #" + numCached,
+                            app.killLocked("cached #" + numCached,
                                     ApplicationExitInfo.REASON_OTHER,
                                     ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
                                     true);
@@ -1034,17 +1080,16 @@
                         break;
                     case PROCESS_STATE_CACHED_EMPTY:
                         if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
-                                && app.lastActivityTime < oldTime) {
-                            app.kill("empty for "
-                                    + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
-                                    / 1000) + "s",
+                                && app.getLastActivityTime() < oldTime) {
+                            app.killLocked("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME
+                                    - app.getLastActivityTime()) / 1000) + "s",
                                     ApplicationExitInfo.REASON_OTHER,
                                     ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
                                     true);
                         } else {
                             numEmpty++;
                             if (numEmpty > emptyProcessLimit) {
-                                app.kill("empty #" + numEmpty,
+                                app.killLocked("empty #" + numEmpty,
                                         ApplicationExitInfo.REASON_OTHER,
                                         ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
                                         true);
@@ -1056,8 +1101,8 @@
                         break;
                 }
 
-                if (app.isolated && app.numberOfRunningServices() <= 0
-                        && app.isolatedEntryPoint == null) {
+                if (app.isolated && psr.numberOfRunningServices() <= 0
+                        && app.getIsolatedEntryPoint() == null) {
                     // If this is an isolated process, there are no services
                     // running in it, and it's not a special process with a
                     // custom entry point, then the process is no longer
@@ -1065,40 +1110,56 @@
                     // definition not re-use the same process again, and it is
                     // good to avoid having whatever code was running in them
                     // left sitting around after no longer needed.
-                    app.kill("isolated not needed", ApplicationExitInfo.REASON_OTHER,
+                    app.killLocked("isolated not needed", ApplicationExitInfo.REASON_OTHER,
                             ApplicationExitInfo.SUBREASON_ISOLATED_NOT_NEEDED, true);
                 } else {
                     // Keeping this process, update its uid.
-                    updateAppUidRecLocked(app);
+                    updateAppUidRecLSP(app);
                 }
 
-                if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
-                        && !app.killedByAm) {
+                if (state.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
+                        && !app.isKilledByAm()) {
                     numTrimming++;
                 }
             }
         }
 
-        mProcessList.incrementProcStateSeqAndNotifyAppsLocked(activeUids);
+        mProcessList.incrementProcStateSeqAndNotifyAppsLOSP(activeUids);
 
-        return mService.mAppProfiler.updateLowMemStateLocked(numCached, numEmpty, numTrimming);
+        return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming);
     }
 
-    private void updateAppUidRecLocked(ProcessRecord app) {
-        final UidRecord uidRec = app.uidRecord;
-        if (uidRec != null) {
-            uidRec.ephemeral = app.info.isInstantApp();
-            if (uidRec.getCurProcState() > app.getCurProcState()) {
-                uidRec.setCurProcState(app.getCurProcState());
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) {
+        if (!app.isKilledByAm() && app.getThread() != null) {
+            if (app.isolated && app.mServices.numberOfRunningServices() <= 0
+                    && app.getIsolatedEntryPoint() == null) {
+                // No op.
+            } else {
+                // Keeping this process, update its uid.
+                updateAppUidRecLSP(app);
             }
-            if (app.hasForegroundServices()) {
-                uidRec.foregroundServices = true;
-            }
-            uidRec.curCapability |= app.curCapability;
         }
     }
 
-    private void updateUidsLocked(ActiveUids activeUids, final long nowElapsed) {
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateAppUidRecLSP(ProcessRecord app) {
+        final UidRecord uidRec = app.getUidRecord();
+        if (uidRec != null) {
+            final ProcessStateRecord state = app.mState;
+            uidRec.setEphemeral(app.info.isInstantApp());
+            if (uidRec.getCurProcState() > state.getCurProcState()) {
+                uidRec.setCurProcState(state.getCurProcState());
+            }
+            if (app.mServices.hasForegroundServices()) {
+                uidRec.setForegroundServices(true);
+            }
+            uidRec.setCurCapability(uidRec.getCurCapability() | state.getCurCapability());
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) {
         ArrayList<UidRecord> becameIdle = mTmpBecameIdle;
         becameIdle.clear();
 
@@ -1110,22 +1171,22 @@
             final UidRecord uidRec = activeUids.valueAt(i);
             int uidChange = UidRecord.CHANGE_PROCSTATE;
             if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
-                    && (uidRec.setProcState != uidRec.getCurProcState()
-                    || uidRec.setCapability != uidRec.curCapability
-                    || uidRec.setWhitelist != uidRec.curWhitelist)) {
+                    && (uidRec.getSetProcState() != uidRec.getCurProcState()
+                    || uidRec.getSetCapability() != uidRec.getCurCapability()
+                    || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) {
                 if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
-                        + ": proc state from " + uidRec.setProcState + " to "
+                        + ": proc state from " + uidRec.getSetProcState() + " to "
                         + uidRec.getCurProcState() + ", capability from "
-                        + uidRec.setCapability + " to " + uidRec.curCapability
-                        + ", whitelist from " + uidRec.setWhitelist
-                        + " to " + uidRec.curWhitelist);
+                        + uidRec.getSetCapability() + " to " + uidRec.getCurCapability()
+                        + ", allowlist from " + uidRec.isSetAllowListed()
+                        + " to " + uidRec.isCurAllowListed());
                 if (ActivityManager.isProcStateBackground(uidRec.getCurProcState())
-                        && !uidRec.curWhitelist) {
+                        && !uidRec.isCurAllowListed()) {
                     // UID is now in the background (and not on the temp allowlist).  Was it
                     // previously in the foreground (or on the temp allowlist)?
-                    if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
-                            || uidRec.setWhitelist) {
-                        uidRec.lastBackgroundTime = nowElapsed;
+                    if (!ActivityManager.isProcStateBackground(uidRec.getSetProcState())
+                            || uidRec.isSetAllowListed()) {
+                        uidRec.setLastBackgroundTime(nowElapsed);
                         if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
                             // Note: the background settle time is in elapsed realtime, while
                             // the handler time base is uptime.  All this means is that we may
@@ -1135,38 +1196,40 @@
                                     mConstants.BACKGROUND_SETTLE_TIME);
                         }
                     }
-                    if (uidRec.idle && !uidRec.setIdle) {
+                    if (uidRec.isIdle() && !uidRec.isSetIdle()) {
                         uidChange = UidRecord.CHANGE_IDLE;
                         becameIdle.add(uidRec);
                     }
                 } else {
-                    if (uidRec.idle) {
+                    if (uidRec.isIdle()) {
                         uidChange = UidRecord.CHANGE_ACTIVE;
-                        EventLogTags.writeAmUidActive(uidRec.uid);
-                        uidRec.idle = false;
+                        EventLogTags.writeAmUidActive(uidRec.getUid());
+                        uidRec.setIdle(false);
                     }
-                    uidRec.lastBackgroundTime = 0;
+                    uidRec.setLastBackgroundTime(0);
                 }
-                final boolean wasCached = uidRec.setProcState
+                final boolean wasCached = uidRec.getSetProcState()
                         > ActivityManager.PROCESS_STATE_RECEIVER;
                 final boolean isCached = uidRec.getCurProcState()
                         > ActivityManager.PROCESS_STATE_RECEIVER;
-                if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) {
+                if (wasCached != isCached
+                        || uidRec.getSetProcState() == PROCESS_STATE_NONEXISTENT) {
                     uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED;
                 }
-                uidRec.setProcState = uidRec.getCurProcState();
-                uidRec.setCapability = uidRec.curCapability;
-                uidRec.setWhitelist = uidRec.curWhitelist;
-                uidRec.setIdle = uidRec.idle;
-                mService.mAtmInternal.onUidProcStateChanged(uidRec.uid, uidRec.setProcState);
+                uidRec.setSetProcState(uidRec.getCurProcState());
+                uidRec.setSetCapability(uidRec.getCurCapability());
+                uidRec.setSetAllowListed(uidRec.isCurAllowListed());
+                uidRec.setSetIdle(uidRec.isIdle());
+                mService.mAtmInternal.onUidProcStateChanged(
+                        uidRec.getUid(), uidRec.getSetProcState());
                 mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
-                mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState(),
-                        uidRec.curCapability);
-                if (uidRec.foregroundServices) {
+                mService.noteUidProcessState(uidRec.getUid(), uidRec.getCurProcState(),
+                        uidRec.getCurCapability());
+                if (uidRec.hasForegroundServices()) {
                     mService.mServices.foregroundServiceProcStateChangedLocked(uidRec);
                 }
             }
-            mService.mInternal.deletePendingTopUid(uidRec.uid);
+            mService.mInternal.deletePendingTopUid(uidRec.getUid());
         }
         if (mLocalPowerManager != null) {
             mLocalPowerManager.finishUidChanges();
@@ -1177,7 +1240,7 @@
             // If we have any new uids that became idle this time, we need to make sure
             // they aren't left with running services.
             for (int i = size - 1; i >= 0; i--) {
-                mService.mServices.stopInBackgroundLocked(becameIdle.get(i).uid);
+                mService.mServices.stopInBackgroundLocked(becameIdle.get(i).getUid());
             }
         }
     }
@@ -1185,7 +1248,7 @@
     private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
             new ComputeOomAdjWindowCallback();
 
-    /** These methods are called inline during computeOomAdjLocked(), on the same thread */
+    /** These methods are called inline during computeOomAdjLSP(), on the same thread */
     final class ComputeOomAdjWindowCallback
             implements WindowProcessController.ComputeOomAdjCallback {
 
@@ -1197,6 +1260,7 @@
         int appUid;
         int logUid;
         int processStateCurTop;
+        ProcessStateRecord mState;
 
         void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
                 int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) {
@@ -1208,6 +1272,7 @@
             this.appUid = appUid;
             this.logUid = logUid;
             this.processStateCurTop = processStateCurTop;
+            this.mState = app.mState;
         }
 
         @Override
@@ -1215,14 +1280,14 @@
             // App has a visible activity; only upgrade adjustment.
             if (adj > ProcessList.VISIBLE_APP_ADJ) {
                 adj = ProcessList.VISIBLE_APP_ADJ;
-                app.adjType = "vis-activity";
+                mState.setAdjType("vis-activity");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
                 }
             }
             if (procState > processStateCurTop) {
                 procState = processStateCurTop;
-                app.adjType = "vis-activity";
+                mState.setAdjType("vis-activity");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ,
                             "Raise procstate to vis-activity (top): " + app);
@@ -1231,8 +1296,8 @@
             if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
             }
-            app.setCached(false);
-            app.empty = false;
+            mState.setCached(false);
+            mState.setEmpty(false);
             foregroundActivities = true;
         }
 
@@ -1240,14 +1305,14 @@
         public void onPausedActivity() {
             if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                app.adjType = "pause-activity";
+                mState.setAdjType("pause-activity");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: "  + app);
                 }
             }
             if (procState > processStateCurTop) {
                 procState = processStateCurTop;
-                app.adjType = "pause-activity";
+                mState.setAdjType("pause-activity");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ,
                             "Raise procstate to pause-activity (top): "  + app);
@@ -1256,8 +1321,8 @@
             if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
             }
-            app.setCached(false);
-            app.empty = false;
+            mState.setCached(false);
+            mState.setEmpty(false);
             foregroundActivities = true;
         }
 
@@ -1265,7 +1330,7 @@
         public void onStoppingActivity(boolean finishing) {
             if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                app.adjType = "stop-activity";
+                mState.setAdjType("stop-activity");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ,
                             "Raise adj to stop-activity: "  + app);
@@ -1281,15 +1346,15 @@
             if (!finishing) {
                 if (procState > PROCESS_STATE_LAST_ACTIVITY) {
                     procState = PROCESS_STATE_LAST_ACTIVITY;
-                    app.adjType = "stop-activity";
+                    mState.setAdjType("stop-activity");
                     if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                         reportOomAdjMessageLocked(TAG_OOM_ADJ,
                                 "Raise procstate to stop-activity: " + app);
                     }
                 }
             }
-            app.setCached(false);
-            app.empty = false;
+            mState.setCached(false);
+            mState.setEmpty(false);
             foregroundActivities = true;
         }
 
@@ -1297,7 +1362,7 @@
         public void onOtherActivity() {
             if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
                 procState = PROCESS_STATE_CACHED_ACTIVITY;
-                app.adjType = "cch-act";
+                mState.setAdjType("cch-act");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ,
                             "Raise procstate to cached activity: " + app);
@@ -1306,99 +1371,102 @@
         }
     }
 
-    private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj,
+    @GuardedBy({"mService", "mProcLock"})
+    private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
             ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,
             boolean computeClients) {
-        if (mAdjSeq == app.adjSeq) {
-            if (app.adjSeq == app.completedAdjSeq) {
+        final ProcessStateRecord state = app.mState;
+        if (mAdjSeq == state.getAdjSeq()) {
+            if (state.getAdjSeq() == state.getCompletedAdjSeq()) {
                 // This adjustment has already been computed successfully.
                 return false;
             } else {
                 // The process is being computed, so there is a cycle. We cannot
                 // rely on this process's state.
-                app.containsCycle = true;
+                state.setContainsCycle(true);
 
                 return false;
             }
         }
 
-        if (app.thread == null) {
-            app.adjSeq = mAdjSeq;
-            app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
-            app.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
-            app.curAdj = ProcessList.CACHED_APP_MAX_ADJ;
-            app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
-            app.completedAdjSeq = app.adjSeq;
-            app.curCapability = PROCESS_CAPABILITY_NONE;
+        if (app.getThread() == null) {
+            state.setAdjSeq(mAdjSeq);
+            state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
+            state.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
+            state.setCurAdj(ProcessList.CACHED_APP_MAX_ADJ);
+            state.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
+            state.setCompletedAdjSeq(state.getAdjSeq());
+            state.setCurCapability(PROCESS_CAPABILITY_NONE);
             return false;
         }
 
-        app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
-        app.adjSource = null;
-        app.adjTarget = null;
-        app.empty = false;
-        app.setCached(false);
-        app.shouldNotFreeze = false;
-
-        app.resetAllowStartFgs();
+        state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN);
+        state.setAdjSource(null);
+        state.setAdjTarget(null);
+        state.setEmpty(false);
+        state.setCached(false);
+        state.setAllowStartFgsState(PROCESS_STATE_NONEXISTENT);
+        state.resetAllowStartFgs();
+        app.mOptRecord.setShouldNotFreeze(false);
 
         final int appUid = app.info.uid;
         final int logUid = mService.mCurOomAdjUid;
 
-        int prevAppAdj = app.curAdj;
-        int prevProcState = app.getCurProcState();
-        int prevCapability = app.curCapability;
+        int prevAppAdj = state.getCurAdj();
+        int prevProcState = state.getCurProcState();
+        int prevCapability = state.getCurCapability();
+        final ProcessServiceRecord psr = app.mServices;
 
-        if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+        if (state.getMaxAdj() <= ProcessList.FOREGROUND_APP_ADJ) {
             // The max adjustment doesn't allow this app to be anything
             // below foreground, so it is not worth doing work for it.
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                mService.reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
             }
-            app.adjType = "fixed";
-            app.adjSeq = mAdjSeq;
-            app.setCurRawAdj(app.maxAdj);
-            app.setHasForegroundActivities(false);
-            app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
-            app.curCapability = PROCESS_CAPABILITY_ALL;
-            app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
+            state.setAdjType("fixed");
+            state.setAdjSeq(mAdjSeq);
+            state.setCurRawAdj(state.getMaxAdj());
+            state.setHasForegroundActivities(false);
+            state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
+            state.setCurCapability(PROCESS_CAPABILITY_ALL);
+            state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
             // System processes can do UI, and when they do we want to have
             // them trim their memory after the user leaves the UI.  To
             // facilitate this, here we need to determine whether or not it
             // is currently showing UI.
-            app.systemNoUi = true;
+            state.setSystemNoUi(true);
             if (app == topApp) {
-                app.systemNoUi = false;
-                app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
-                app.adjType = "pers-top-activity";
-            } else if (app.hasTopUi()) {
+                state.setSystemNoUi(false);
+                state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+                state.setAdjType("pers-top-activity");
+            } else if (state.hasTopUi()) {
                 // sched group/proc state adjustment is below
-                app.systemNoUi = false;
-                app.adjType = "pers-top-ui";
-            } else if (app.getCachedHasVisibleActivities()) {
-                app.systemNoUi = false;
+                state.setSystemNoUi(false);
+                state.setAdjType("pers-top-ui");
+            } else if (state.getCachedHasVisibleActivities()) {
+                state.setSystemNoUi(false);
             }
-            if (!app.systemNoUi) {
-                if (mService.mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
+            if (!state.isSystemNoUi()) {
+                if (mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE) {
                     // screen on, promote UI
-                    app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
-                    app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+                    state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+                    state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
                 } else {
                     // screen off, restrict UI scheduling
-                    app.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
-                    app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
+                    state.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+                    state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
                 }
             }
-            app.setCurRawProcState(app.getCurProcState());
-            app.curAdj = app.maxAdj;
-            app.completedAdjSeq = app.adjSeq;
-            app.bumpAllowStartFgsState(app.getCurProcState());
-            app.setAllowStartFgs();
+            state.setCurRawProcState(state.getCurProcState());
+            state.setCurAdj(state.getMaxAdj());
+            state.setCompletedAdjSeq(state.getAdjSeq());
+            state.bumpAllowStartFgsState(state.getCurProcState());
+            state.setAllowStartFgs();
             // if curAdj is less than prevAppAdj, then this process was promoted
-            return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
+            return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
         }
 
-        app.systemNoUi = false;
+        state.setSystemNoUi(false);
 
         final int PROCESS_STATE_CUR_TOP = mService.mAtmInternal.getTopProcessState();
 
@@ -1415,17 +1483,17 @@
             // The last app on the list is the foreground app.
             adj = ProcessList.FOREGROUND_APP_ADJ;
             schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
-            app.adjType = "top-activity";
+            state.setAdjType("top-activity");
             foregroundActivities = true;
             procState = PROCESS_STATE_CUR_TOP;
-            app.bumpAllowStartFgsState(PROCESS_STATE_TOP);
+            state.bumpAllowStartFgsState(PROCESS_STATE_TOP);
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
             }
-        } else if (app.runningRemoteAnimation) {
+        } else if (state.isRunningRemoteAnimation()) {
             adj = ProcessList.VISIBLE_APP_ADJ;
             schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
-            app.adjType = "running-remote-anim";
+            state.setAdjType("running-remote-anim");
             procState = PROCESS_STATE_CUR_TOP;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app);
@@ -1434,12 +1502,12 @@
             // Don't want to kill running instrumentation.
             adj = ProcessList.FOREGROUND_APP_ADJ;
             schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-            app.adjType = "instrumentation";
+            state.setAdjType("instrumentation");
             procState = PROCESS_STATE_FOREGROUND_SERVICE;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
             }
-        } else if (app.getCachedIsReceivingBroadcast(mTmpBroadcastQueue)) {
+        } else if (state.getCachedIsReceivingBroadcast(mTmpBroadcastQueue)) {
             // An app that is currently receiving a broadcast also
             // counts as being in the foreground for OOM killer purposes.
             // It's placed in a sched group based on the nature of the
@@ -1447,27 +1515,26 @@
             adj = ProcessList.FOREGROUND_APP_ADJ;
             schedGroup = (mTmpBroadcastQueue.contains(mService.mFgBroadcastQueue))
                     ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
-            app.adjType = "broadcast";
+            state.setAdjType("broadcast");
             procState = ActivityManager.PROCESS_STATE_RECEIVER;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app);
             }
-        } else if (app.executingServices.size() > 0) {
+        } else if (psr.numberOfExecutingServices() > 0) {
             // An app that is currently executing a service callback also
             // counts as being in the foreground.
             adj = ProcessList.FOREGROUND_APP_ADJ;
-            schedGroup = app.execServicesFg ?
-                    ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
-            app.adjType = "exec-service";
+            schedGroup = psr.shouldExecServicesFg()
+                    ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+            state.setAdjType("exec-service");
             procState = PROCESS_STATE_SERVICE;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
             }
-            //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
         } else if (app == topApp) {
             adj = ProcessList.FOREGROUND_APP_ADJ;
             schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-            app.adjType = "top-sleeping";
+            state.setAdjType("top-sleeping");
             foregroundActivities = true;
             procState = PROCESS_STATE_CUR_TOP;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1480,10 +1547,10 @@
             // value that the caller wants us to.
             adj = cachedAdj;
             procState = PROCESS_STATE_CACHED_EMPTY;
-            if (!app.containsCycle) {
-                app.setCached(true);
-                app.empty = true;
-                app.adjType = "cch-empty";
+            if (!state.containsCycle()) {
+                state.setCached(true);
+                state.setEmpty(true);
+                state.setAdjType("cch-empty");
             }
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app);
@@ -1491,20 +1558,20 @@
         }
 
         // Examine all activities if not already foreground.
-        if (!foregroundActivities && app.getCachedHasActivities()) {
-            app.computeOomAdjFromActivitiesIfNecessary(mTmpComputeOomAdjWindowCallback,
+        if (!foregroundActivities && state.getCachedHasActivities()) {
+            state.computeOomAdjFromActivitiesIfNecessary(mTmpComputeOomAdjWindowCallback,
                     adj, foregroundActivities, procState, schedGroup, appUid, logUid,
                     PROCESS_STATE_CUR_TOP);
 
-            adj = app.mCachedAdj;
-            foregroundActivities = app.mCachedForegroundActivities;
-            procState = app.mCachedProcState;
-            schedGroup = app.mCachedSchedGroup;
+            adj = state.getCachedAdj();
+            foregroundActivities = state.getCachedForegroundActivities();
+            procState = state.getCachedProcState();
+            schedGroup = state.getCachedSchedGroup();
         }
 
-        if (procState > PROCESS_STATE_CACHED_RECENT && app.getCachedHasRecentTasks()) {
+        if (procState > PROCESS_STATE_CACHED_RECENT && state.getCachedHasRecentTasks()) {
             procState = PROCESS_STATE_CACHED_RECENT;
-            app.adjType = "cch-rec";
+            state.setAdjType("cch-rec");
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app);
             }
@@ -1512,24 +1579,24 @@
 
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
                 || procState > PROCESS_STATE_FOREGROUND_SERVICE) {
-            if (app.hasForegroundServices()) {
+            if (psr.hasForegroundServices()) {
                 // The user is aware of this app, so make it visible.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
                 procState = PROCESS_STATE_FOREGROUND_SERVICE;
-                app.bumpAllowStartFgsState(PROCESS_STATE_FOREGROUND_SERVICE);
-                app.adjType = "fg-service";
-                app.setCached(false);
+                state.bumpAllowStartFgsState(PROCESS_STATE_FOREGROUND_SERVICE);
+                state.setAdjType("fg-service");
+                state.setCached(false);
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + app.adjType + ": "
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + state.getAdjType() + ": "
                             + app + " ");
                 }
-            } else if (app.hasOverlayUi()) {
+            } else if (state.hasOverlayUi()) {
                 // The process is display an overlay UI.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
                 procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
-                app.setCached(false);
-                app.adjType = "has-overlay-ui";
+                state.setCached(false);
+                state.setAdjType("has-overlay-ui");
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app);
@@ -1540,11 +1607,11 @@
         // If the app was recently in the foreground and moved to a foreground service status,
         // allow it to get a higher rank in memory for some time, compared to other foreground
         // services so that it can finish performing any persistence/processing of in-memory state.
-        if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
-                && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now
-                || app.setProcState <= PROCESS_STATE_TOP)) {
+        if (psr.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
+                && (state.getLastTopTime() + mConstants.TOP_TO_FGS_GRACE_DURATION > now
+                || state.getSetProcState() <= PROCESS_STATE_TOP)) {
             adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
-            app.adjType = "fg-service-act";
+            state.setAdjType("fg-service-act");
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app);
             }
@@ -1552,15 +1619,15 @@
 
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
                 || procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
-            if (app.forcingToImportant != null) {
+            if (state.getForcingToImportant() != null) {
                 // This is currently used for toasts...  they are not interactive, and
                 // we don't want them to cause the app to become fully foreground (and
                 // thus out of background check), so we yes the best background level we can.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
                 procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
-                app.setCached(false);
-                app.adjType = "force-imp";
-                app.adjSource = app.forcingToImportant;
+                state.setCached(false);
+                state.setAdjType("force-imp");
+                state.setAdjSource(state.getForcingToImportant());
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app);
@@ -1568,63 +1635,63 @@
             }
         }
 
-        if (app.getCachedIsHeavyWeight()) {
+        if (state.getCachedIsHeavyWeight()) {
             if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
                 // We don't want to kill the current heavy-weight process.
                 adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
                 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.setCached(false);
-                app.adjType = "heavy";
+                state.setCached(false);
+                state.setAdjType("heavy");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
                 }
             }
             if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
                 procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
-                app.adjType = "heavy";
+                state.setAdjType("heavy");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app);
                 }
             }
         }
 
-        if (app.getCachedIsHomeProcess()) {
+        if (state.getCachedIsHomeProcess()) {
             if (adj > ProcessList.HOME_APP_ADJ) {
                 // This process is hosting what we currently consider to be the
                 // home app, so we don't want to let it go into the background.
                 adj = ProcessList.HOME_APP_ADJ;
                 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.setCached(false);
-                app.adjType = "home";
+                state.setCached(false);
+                state.setAdjType("home");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
                 }
             }
             if (procState > ActivityManager.PROCESS_STATE_HOME) {
                 procState = ActivityManager.PROCESS_STATE_HOME;
-                app.adjType = "home";
+                state.setAdjType("home");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);
                 }
             }
         }
 
-        if (app.getCachedIsPreviousProcess() && app.getCachedHasActivities()) {
+        if (state.getCachedIsPreviousProcess() && state.getCachedHasActivities()) {
             if (adj > ProcessList.PREVIOUS_APP_ADJ) {
                 // This was the previous process that showed UI to the user.
                 // We want to try to keep it around more aggressively, to give
                 // a good experience around switching between two apps.
                 adj = ProcessList.PREVIOUS_APP_ADJ;
                 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.setCached(false);
-                app.adjType = "previous";
+                state.setCached(false);
+                state.setAdjType("previous");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
                 }
             }
             if (procState > PROCESS_STATE_LAST_ACTIVITY) {
                 procState = PROCESS_STATE_LAST_ACTIVITY;
-                app.adjType = "previous";
+                state.setAdjType("previous");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
                 }
@@ -1632,7 +1699,7 @@
         }
 
         if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj
-                + " reason=" + app.adjType);
+                + " reason=" + state.getAdjType());
 
         // By default, we use the computed adjustment.  It may be changed if
         // there are applications dependent on our services or providers, but
@@ -1640,15 +1707,15 @@
         // infinite recursion. If we're re-evaluating due to cycles, use the previously computed
         // values.
         if (cycleReEval) {
-            procState = Math.min(procState, app.getCurRawProcState());
-            adj = Math.min(adj, app.getCurRawAdj());
-            schedGroup = Math.max(schedGroup, app.getCurrentSchedulingGroup());
+            procState = Math.min(procState, state.getCurRawProcState());
+            adj = Math.min(adj, state.getCurRawAdj());
+            schedGroup = Math.max(schedGroup, state.getCurrentSchedulingGroup());
         }
-        app.setCurRawAdj(adj);
-        app.setCurRawProcState(procState);
+        state.setCurRawAdj(adj);
+        state.setCurRawProcState(procState);
 
-        app.hasStartedServices = false;
-        app.adjSeq = mAdjSeq;
+        state.setHasStartedServices(false);
+        state.setAdjSeq(mAdjSeq);
 
         final BackupRecord backupTarget = mService.mBackupTargets.get(app.userId);
         if (backupTarget != null && app == backupTarget.app) {
@@ -1659,15 +1726,15 @@
                 if (procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
                     procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
                 }
-                app.adjType = "backup";
+                state.setAdjType("backup");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
                 }
-                app.setCached(false);
+                state.setCached(false);
             }
             if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
                 procState = ActivityManager.PROCESS_STATE_BACKUP;
-                app.adjType = "backup";
+                state.setAdjType("backup");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app);
                 }
@@ -1675,29 +1742,29 @@
         }
 
         int capabilityFromFGS = 0; // capability from foreground service.
-        for (int is = app.numberOfRunningServices() - 1;
+        for (int is = psr.numberOfRunningServices() - 1;
                 is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                         || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                         || procState > PROCESS_STATE_TOP);
                 is--) {
-            ServiceRecord s = app.getRunningServiceAt(is);
+            ServiceRecord s = psr.getRunningServiceAt(is);
             if (s.startRequested) {
-                app.hasStartedServices = true;
+                state.setHasStartedServices(true);
                 if (procState > PROCESS_STATE_SERVICE) {
                     procState = PROCESS_STATE_SERVICE;
-                    app.adjType = "started-services";
+                    state.setAdjType("started-services");
                     if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                         reportOomAdjMessageLocked(TAG_OOM_ADJ,
                                 "Raise procstate to started service: " + app);
                     }
                 }
-                if (!s.mKeepWarming && app.hasShownUi && !app.getCachedIsHomeProcess()) {
+                if (!s.mKeepWarming && state.hasShownUi() && !state.getCachedIsHomeProcess()) {
                     // If this process has shown some UI, let it immediately
                     // go to the LRU list because it may be pretty heavy with
                     // UI stuff.  We'll tag it with a label just to help
                     // debug and understand what is going on.
                     if (adj > ProcessList.SERVICE_ADJ) {
-                        app.adjType = "cch-started-ui-services";
+                        state.setAdjType("cch-started-ui-services");
                     }
                 } else {
                     if (s.mKeepWarming
@@ -1707,19 +1774,19 @@
                         // of the background processes.
                         if (adj > ProcessList.SERVICE_ADJ) {
                             adj = ProcessList.SERVICE_ADJ;
-                            app.adjType = "started-services";
+                            state.setAdjType("started-services");
                             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                                 reportOomAdjMessageLocked(TAG_OOM_ADJ,
                                         "Raise adj to started service: " + app);
                             }
-                            app.setCached(false);
+                            state.setCached(false);
                         }
                     }
                     // If we have let the service slide into the background
                     // state, still have some text describing what it is doing
                     // even though the service no longer has an impact.
                     if (adj > ProcessList.SERVICE_ADJ) {
-                        app.adjType = "cch-started-services";
+                        state.setAdjType("cch-started-services");
                     }
                 }
             }
@@ -1774,29 +1841,31 @@
                     boolean trackedProcState = false;
 
                     ProcessRecord client = cr.binding.client;
+                    final ProcessStateRecord cstate = client.mState;
                     if (computeClients) {
-                        computeOomAdjLocked(client, cachedAdj, topApp, doingAll, now,
+                        computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now,
                                 cycleReEval, true);
                     } else {
-                        client.setCurRawAdj(client.setAdj);
-                        client.setCurRawProcState(client.setProcState);
+                        cstate.setCurRawAdj(cstate.getSetAdj());
+                        cstate.setCurRawProcState(cstate.getSetProcState());
                     }
 
-                    int clientAdj = client.getCurRawAdj();
-                    int clientProcState = client.getCurRawProcState();
+                    int clientAdj = cstate.getCurRawAdj();
+                    int clientProcState = cstate.getCurRawProcState();
 
                     // pass client's mAllowStartFgs to the app if client is not persistent process.
-                    if (client.mAllowStartFgs && client.maxAdj >= ProcessList.FOREGROUND_APP_ADJ) {
-                        app.mAllowStartFgs = true;
+                    if (cstate.getAllowedStartFgs() != FGS_FEATURE_DENIED
+                            && cstate.getMaxAdj() >= ProcessList.FOREGROUND_APP_ADJ) {
+                        state.setAllowStartFgs(cstate.getAllowedStartFgs());
                     }
 
                     if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) {
-                        if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
+                        if (shouldSkipDueToCycle(state, cstate, procState, adj, cycleReEval)) {
                             continue;
                         }
 
                         if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
-                            capability |= client.curCapability;
+                            capability |= cstate.getCurCapability();
                         }
 
                         if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
@@ -1809,7 +1878,7 @@
                         if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
                             // Not doing bind OOM management, so treat
                             // this guy more like a started service.
-                            if (app.hasShownUi && !app.getCachedIsHomeProcess()) {
+                            if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
                                 // If this process has shown some UI, let it immediately
                                 // go to the LRU list because it may be pretty heavy with
                                 // UI stuff.  We'll tag it with a label just to help
@@ -1817,7 +1886,7 @@
                                 if (adj > clientAdj) {
                                     adjType = "cch-bound-ui-services";
                                 }
-                                app.setCached(false);
+                                state.setCached(false);
                                 clientAdj = adj;
                                 clientProcState = procState;
                             } else {
@@ -1843,7 +1912,7 @@
                             // about letting this process get into the LRU
                             // list to be killed and restarted if needed for
                             // memory.
-                            if (app.hasShownUi && !app.getCachedIsHomeProcess()
+                            if (state.hasShownUi() && !state.getCachedIsHomeProcess()
                                     && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
                                 if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
                                     adjType = "cch-bound-ui-services";
@@ -1880,12 +1949,12 @@
                                         newAdj = adj;
                                     }
                                 }
-                                if (!client.isCached()) {
-                                    app.setCached(false);
+                                if (!cstate.isCached()) {
+                                    state.setCached(false);
                                 }
                                 if (adj >  newAdj) {
                                     adj = newAdj;
-                                    app.setCurRawAdj(adj);
+                                    state.setCurRawAdj(adj);
                                     adjType = "service";
                                 }
                             }
@@ -1895,7 +1964,7 @@
                             // This will treat important bound services identically to
                             // the top app, which may behave differently than generic
                             // foreground work.
-                            final int curSchedGroup = client.getCurrentSchedulingGroup();
+                            final int curSchedGroup = cstate.getCurrentSchedulingGroup();
                             if (curSchedGroup > schedGroup) {
                                 if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
                                     schedGroup = curSchedGroup;
@@ -1910,9 +1979,9 @@
                                 // give them the best bound state after that.
                                 if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
                                     clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                    app.bumpAllowStartFgsState(
+                                    state.bumpAllowStartFgsState(
                                             PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
-                                } else if (mService.mWakefulness
+                                } else if (mService.mWakefulness.get()
                                         == PowerManagerInternal.WAKEFULNESS_AWAKE
                                         && (cr.flags & Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
                                                 != 0) {
@@ -1925,7 +1994,7 @@
                                 // Go at most to BOUND_TOP, unless requested to elevate
                                 // to client's state.
                                 clientProcState = PROCESS_STATE_BOUND_TOP;
-                                app.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
+                                state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
                                 boolean enabled = false;
                                 try {
                                     enabled = mPlatformCompatCache.isChangeEnabled(
@@ -1969,7 +2038,7 @@
 
                         if (procState > clientProcState) {
                             procState = clientProcState;
-                            app.setCurRawProcState(procState);
+                            state.setCurRawProcState(procState);
                             if (adjType == null) {
                                 adjType = "service";
                             }
@@ -1979,12 +2048,12 @@
                             app.setPendingUiClean(true);
                         }
                         if (adjType != null) {
-                            app.adjType = adjType;
-                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
-                                    .REASON_SERVICE_IN_USE;
-                            app.adjSource = cr.binding.client;
-                            app.adjSourceProcState = clientProcState;
-                            app.adjTarget = s.instanceName;
+                            state.setAdjType(adjType);
+                            state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                                    .REASON_SERVICE_IN_USE);
+                            state.setAdjSource(cr.binding.client);
+                            state.setAdjSourceProcState(clientProcState);
+                            state.setAdjTarget(s.instanceName);
                             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
                                         + ": " + app + ", due to " + cr.binding.client
@@ -2003,18 +2072,18 @@
                         // bound by an unfrozen app via a WPRI binding has to remain
                         // unfrozen.
                         if (clientAdj < ProcessList.CACHED_APP_MIN_ADJ) {
-                            app.shouldNotFreeze = true;
+                            app.mOptRecord.setShouldNotFreeze(true);
                         }
                     }
                     if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
-                        app.treatLikeActivity = true;
+                        psr.setTreatLikeActivity(true);
                     }
                     final ActivityServiceConnectionsHolder a = cr.activity;
                     if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
                         if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ
                                 && a.isActivityVisible()) {
                             adj = ProcessList.FOREGROUND_APP_ADJ;
-                            app.setCurRawAdj(adj);
+                            state.setCurRawAdj(adj);
                             if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
                                 if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
                                     schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
@@ -2022,13 +2091,13 @@
                                     schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                                 }
                             }
-                            app.setCached(false);
-                            app.adjType = "service";
-                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
-                                    .REASON_SERVICE_IN_USE;
-                            app.adjSource = a;
-                            app.adjSourceProcState = procState;
-                            app.adjTarget = s.instanceName;
+                            state.setCached(false);
+                            state.setAdjType("service");
+                            state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                                    .REASON_SERVICE_IN_USE);
+                            state.setAdjSource(a);
+                            state.setAdjSourceProcState(procState);
+                            state.setAdjTarget(s.instanceName);
                             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                                 reportOomAdjMessageLocked(TAG_OOM_ADJ,
                                         "Raise to service w/activity: " + app);
@@ -2039,12 +2108,13 @@
             }
         }
 
-        for (int provi = app.pubProviders.size() - 1;
+        final ProcessProviderRecord ppr = app.mProviders;
+        for (int provi = ppr.numberOfProviders() - 1;
                 provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                         || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                         || procState > PROCESS_STATE_TOP);
                 provi--) {
-            ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
+            ContentProviderRecord cpr = ppr.getProviderAt(provi);
             for (int i = cpr.connections.size() - 1;
                     i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                             || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
@@ -2052,24 +2122,24 @@
                     i--) {
                 ContentProviderConnection conn = cpr.connections.get(i);
                 ProcessRecord client = conn.client;
+                final ProcessStateRecord cstate = client.mState;
                 if (client == app) {
                     // Being our own client is not interesting.
                     continue;
                 }
                 if (computeClients) {
-                    computeOomAdjLocked(client, cachedAdj, topApp, doingAll, now, cycleReEval,
-                            true);
+                    computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true);
                 } else {
-                    client.setCurRawAdj(client.setAdj);
-                    client.setCurRawProcState(client.setProcState);
+                    cstate.setCurRawAdj(cstate.getSetAdj());
+                    cstate.setCurRawProcState(cstate.getSetProcState());
                 }
 
-                if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
+                if (shouldSkipDueToCycle(state, cstate, procState, adj, cycleReEval)) {
                     continue;
                 }
 
-                int clientAdj = client.getCurRawAdj();
-                int clientProcState = client.getCurRawProcState();
+                int clientAdj = cstate.getCurRawAdj();
+                int clientProcState = cstate.getCurRawProcState();
 
                 if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
                     // If the other app is cached for any reason, for purposes here
@@ -2078,16 +2148,16 @@
                 }
                 String adjType = null;
                 if (adj > clientAdj) {
-                    if (app.hasShownUi && !app.getCachedIsHomeProcess()
+                    if (state.hasShownUi() && !state.getCachedIsHomeProcess()
                             && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
                         adjType = "cch-ui-provider";
                     } else {
                         adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
                                 ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
-                        app.setCurRawAdj(adj);
+                        state.setCurRawAdj(adj);
                         adjType = "provider";
                     }
-                    app.setCached(app.isCached() & client.isCached());
+                    state.setCached(state.isCached() & cstate.isCached());
                 }
 
                 if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -2104,18 +2174,18 @@
                 conn.trackProcState(clientProcState, mAdjSeq, now);
                 if (procState > clientProcState) {
                     procState = clientProcState;
-                    app.setCurRawProcState(procState);
+                    state.setCurRawProcState(procState);
                 }
-                if (client.getCurrentSchedulingGroup() > schedGroup) {
+                if (cstate.getCurrentSchedulingGroup() > schedGroup) {
                     schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                 }
                 if (adjType != null) {
-                    app.adjType = adjType;
-                    app.adjTypeCode = ActivityManager.RunningAppProcessInfo
-                            .REASON_PROVIDER_IN_USE;
-                    app.adjSource = client;
-                    app.adjSourceProcState = clientProcState;
-                    app.adjTarget = cpr.name;
+                    state.setAdjType(adjType);
+                    state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                            .REASON_PROVIDER_IN_USE);
+                    state.setAdjSource(client);
+                    state.setAdjSourceProcState(clientProcState);
+                    state.setAdjTarget(cpr.name);
                     if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                         reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
                                 + ": " + app + ", due to " + client
@@ -2130,11 +2200,11 @@
             if (cpr.hasExternalProcessHandles()) {
                 if (adj > ProcessList.FOREGROUND_APP_ADJ) {
                     adj = ProcessList.FOREGROUND_APP_ADJ;
-                    app.setCurRawAdj(adj);
+                    state.setCurRawAdj(adj);
                     schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
-                    app.setCached(false);
-                    app.adjType = "ext-provider";
-                    app.adjTarget = cpr.name;
+                    state.setCached(false);
+                    state.setAdjType("ext-provider");
+                    state.setAdjTarget(cpr.name);
                     if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                         reportOomAdjMessageLocked(TAG_OOM_ADJ,
                                 "Raise adj to external provider: " + app);
@@ -2142,7 +2212,7 @@
                 }
                 if (procState > PROCESS_STATE_IMPORTANT_FOREGROUND) {
                     procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
-                    app.setCurRawProcState(procState);
+                    state.setCurRawProcState(procState);
                     if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                         reportOomAdjMessageLocked(TAG_OOM_ADJ,
                                 "Raise procstate to external provider: " + app);
@@ -2151,13 +2221,13 @@
             }
         }
 
-        if (app.lastProviderTime > 0 &&
-                (app.lastProviderTime + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
+        if (ppr.getLastProviderTime() > 0
+                && (ppr.getLastProviderTime() + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
             if (adj > ProcessList.PREVIOUS_APP_ADJ) {
                 adj = ProcessList.PREVIOUS_APP_ADJ;
                 schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-                app.setCached(false);
-                app.adjType = "recent-provider";
+                state.setCached(false);
+                state.setAdjType("recent-provider");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ,
                             "Raise adj to recent provider: " + app);
@@ -2165,7 +2235,7 @@
             }
             if (procState > PROCESS_STATE_LAST_ACTIVITY) {
                 procState = PROCESS_STATE_LAST_ACTIVITY;
-                app.adjType = "recent-provider";
+                state.setAdjType("recent-provider");
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                     reportOomAdjMessageLocked(TAG_OOM_ADJ,
                             "Raise procstate to recent provider: " + app);
@@ -2174,53 +2244,51 @@
         }
 
         if (procState >= PROCESS_STATE_CACHED_EMPTY) {
-            if (app.hasClientActivities()) {
+            if (psr.hasClientActivities()) {
                 // This is a cached process, but with client activities.  Mark it so.
                 procState = PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
-                app.adjType = "cch-client-act";
-            } else if (app.treatLikeActivity) {
+                state.setAdjType("cch-client-act");
+            } else if (psr.isTreatedLikeActivity()) {
                 // This is a cached process, but somebody wants us to treat it like it has
                 // an activity, okay!
                 procState = PROCESS_STATE_CACHED_ACTIVITY;
-                app.adjType = "cch-as-act";
+                state.setAdjType("cch-as-act");
             }
         }
 
         if (adj == ProcessList.SERVICE_ADJ) {
             if (doingAll && !cycleReEval) {
-                app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
+                state.setServiceB(mNewNumAServiceProcs > (mNumServiceProcs / 3));
                 mNewNumServiceProcs++;
-                //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
-                if (!app.serviceb) {
+                if (!state.isServiceB()) {
                     // This service isn't far enough down on the LRU list to
                     // normally be a B service, but if we are low on RAM and it
                     // is large we want to force it down since we would prefer to
                     // keep launcher over it.
                     if (!mService.mAppProfiler.isLastMemoryLevelNormal()
-                            && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {
-                        app.serviceHighRam = true;
-                        app.serviceb = true;
+                            && app.mProfile.getLastPss()
+                            >= mProcessList.getCachedRestoreThresholdKb()) {
+                        state.setServiceHighRam(true);
+                        state.setServiceB(true);
                         //Slog.i(TAG, "ADJ " + app + " high ram!");
                     } else {
                         mNewNumAServiceProcs++;
                         //Slog.i(TAG, "ADJ " + app + " not high ram!");
                     }
                 } else {
-                    app.serviceHighRam = false;
+                    state.setServiceHighRam(false);
                 }
             }
-            if (app.serviceb) {
+            if (state.isServiceB()) {
                 adj = ProcessList.SERVICE_B_ADJ;
             }
         }
 
-        app.setCurRawAdj(adj);
+        state.setCurRawAdj(adj);
 
-        //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
-        //      " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
-        if (adj > app.maxAdj) {
-            adj = app.maxAdj;
-            if (app.maxAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+        if (adj > state.getMaxAdj()) {
+            adj = state.getMaxAdj();
+            if (adj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
             }
         }
@@ -2228,38 +2296,39 @@
         // Put bound foreground services in a special sched group for additional
         // restrictions on screen off
         if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-                && mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+                && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE) {
             if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
                 schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
             }
         }
 
         // apply capability from FGS.
-        if (app.hasForegroundServices()) {
+        if (psr.hasForegroundServices()) {
             capability |= capabilityFromFGS;
         }
 
-        capability |= getDefaultCapability(app, procState);
+        capability |= getDefaultCapability(psr, procState);
 
         // Do final modification to adj.  Everything we do between here and applying
         // the final setAdj must be done in this function, because we will also use
         // it when computing the final cached adj later.  Note that we don't need to
         // worry about this for max adj above, since max adj will always be used to
         // keep it out of the cached vaues.
-        app.curAdj = app.modifyRawOomAdj(adj);
-        app.curCapability = capability;
-        app.setCurrentSchedulingGroup(schedGroup);
-        app.setCurProcState(procState);
-        app.setCurRawProcState(procState);
-        app.setHasForegroundActivities(foregroundActivities);
-        app.completedAdjSeq = mAdjSeq;
-        app.setAllowStartFgs();
+        state.setCurAdj(psr.modifyRawOomAdj(adj));
+        state.setCurCapability(capability);
+        state.setCurrentSchedulingGroup(schedGroup);
+        state.setCurProcState(procState);
+        state.setCurRawProcState(procState);
+        state.setHasForegroundActivities(foregroundActivities);
+        state.setCompletedAdjSeq(mAdjSeq);
+        state.setAllowStartFgs();
+
         // if curAdj or curProcState improved, then this process was promoted
-        return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState
-                || app.curCapability != prevCapability ;
+        return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState
+                || state.getCurCapability() != prevCapability;
     }
 
-    private int getDefaultCapability(ProcessRecord app, int procState) {
+    private int getDefaultCapability(ProcessServiceRecord psr, int procState) {
         switch (procState) {
             case PROCESS_STATE_PERSISTENT:
             case PROCESS_STATE_PERSISTENT_UI:
@@ -2268,7 +2337,7 @@
             case PROCESS_STATE_BOUND_TOP:
                 return PROCESS_CAPABILITY_NONE;
             case PROCESS_STATE_FOREGROUND_SERVICE:
-                if (app.hasForegroundServices()) {
+                if (psr.hasForegroundServices()) {
                     // Capability from FGS are conditional depending on foreground service type in
                     // manifest file and the mAllowWhileInUsePermissionInFgs flag.
                     return PROCESS_CAPABILITY_NONE;
@@ -2296,16 +2365,16 @@
      *                    evaluation.
      * @return whether to skip using the client connection at this time
      */
-    private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client,
+    private boolean shouldSkipDueToCycle(ProcessStateRecord app, ProcessStateRecord client,
             int procState, int adj, boolean cycleReEval) {
-        if (client.containsCycle) {
-            // We've detected a cycle. We should retry computeOomAdjLocked later in
+        if (client.containsCycle()) {
+            // We've detected a cycle. We should retry computeOomAdjLSP later in
             // case a later-checked connection from a client  would raise its
             // priority legitimately.
-            app.containsCycle = true;
+            app.setContainsCycle(true);
             // If the client has not been completely evaluated, check if it's worth
             // using the partial values.
-            if (client.completedAdjSeq < mAdjSeq) {
+            if (client.getCompletedAdjSeq() < mAdjSeq) {
                 if (cycleReEval) {
                     // If the partial values are no better, skip until the next
                     // attempt
@@ -2325,21 +2394,25 @@
 
     /** Inform the oomadj observer of changes to oomadj. Used by tests. */
     @GuardedBy("mService")
-    void reportOomAdjMessageLocked(String tag, String msg) {
+    private void reportOomAdjMessageLocked(String tag, String msg) {
         Slog.d(tag, msg);
-        if (mService.mCurOomAdjObserver != null) {
-            mService.mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget();
+        synchronized (mService.mOomAdjObserverLock) {
+            if (mService.mCurOomAdjObserver != null) {
+                mService.mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg)
+                        .sendToTarget();
+            }
         }
     }
 
     /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
-    @GuardedBy("mService")
-    private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
+    @GuardedBy({"mService", "mProcLock"})
+    private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
             long nowElapsed) {
         boolean success = true;
+        final ProcessStateRecord state = app.mState;
 
-        if (app.getCurRawAdj() != app.setRawAdj) {
-            app.setRawAdj = app.getCurRawAdj();
+        if (state.getCurRawAdj() != state.getSetRawAdj()) {
+            state.setSetRawAdj(state.getCurRawAdj());
         }
 
         int changes = 0;
@@ -2347,59 +2420,59 @@
         // don't compact during bootup
         if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
             // Cached and prev/home compaction
-            if (app.curAdj != app.setAdj) {
+            if (state.getCurAdj() != state.getSetAdj()) {
                 // Perform a minor compaction when a perceptible app becomes the prev/home app
                 // Perform a major compaction when any app enters cached
                 // reminder: here, setAdj is previous state, curAdj is upcoming state
-                if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
-                        (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
-                                app.curAdj == ProcessList.HOME_APP_ADJ)) {
+                if (state.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ
+                        && (state.getCurAdj() == ProcessList.PREVIOUS_APP_ADJ
+                            || state.getCurAdj() == ProcessList.HOME_APP_ADJ)) {
                     mCachedAppOptimizer.compactAppSome(app);
-                } else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
-                                || app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
-                        && app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
-                        && app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
+                } else if ((state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ
+                                || state.getSetAdj() > ProcessList.CACHED_APP_MAX_ADJ)
+                        && state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ
+                        && state.getCurAdj() <= ProcessList.CACHED_APP_MAX_ADJ) {
                     mCachedAppOptimizer.compactAppFull(app);
                 }
-            } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
-                    && app.setAdj < ProcessList.FOREGROUND_APP_ADJ
+            } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
+                    && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
                     // Because these can fire independent of oom_adj/procstate changes, we need
                     // to throttle the actual dispatch of these requests in addition to the
                     // processing of the requests. As a result, there is throttling both here
                     // and in CachedAppOptimizer.
                     && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
                 mCachedAppOptimizer.compactAppPersistent(app);
-            } else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
-                    && app.getCurProcState()
+            } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
+                    && state.getCurProcState()
                         == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
                     && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
                 mCachedAppOptimizer.compactAppBfgs(app);
             }
         }
 
-        if (app.curAdj != app.setAdj) {
-            ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
+        if (state.getCurAdj() != state.getSetAdj()) {
+            ProcessList.setOomAdj(app.getPid(), app.uid, state.getCurAdj());
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
-                String msg = "Set " + app.pid + " " + app.processName + " adj "
-                        + app.curAdj + ": " + app.adjType;
+                String msg = "Set " + app.getPid() + " " + app.processName + " adj "
+                        + state.getCurAdj() + ": " + state.getAdjType();
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
             }
-            app.setAdj = app.curAdj;
-            app.verifiedAdj = ProcessList.INVALID_ADJ;
+            state.setSetAdj(state.getCurAdj());
+            state.setVerifiedAdj(ProcessList.INVALID_ADJ);
         }
 
-        final int curSchedGroup = app.getCurrentSchedulingGroup();
-        if (app.setSchedGroup != curSchedGroup) {
-            int oldSchedGroup = app.setSchedGroup;
-            app.setSchedGroup = curSchedGroup;
+        final int curSchedGroup = state.getCurrentSchedulingGroup();
+        if (state.getSetSchedGroup() != curSchedGroup) {
+            int oldSchedGroup = state.getSetSchedGroup();
+            state.setSetSchedGroup(curSchedGroup);
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
                 String msg = "Setting sched group of " + app.processName
-                        + " to " + curSchedGroup + ": " + app.adjType;
+                        + " to " + curSchedGroup + ": " + state.getAdjType();
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
             }
-            if (app.waitingToKill != null && app.curReceivers.isEmpty()
-                    && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
-                app.kill(app.waitingToKill, ApplicationExitInfo.REASON_USER_REQUESTED,
+            if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0
+                    && state.getSetSchedGroup() == ProcessList.SCHED_GROUP_BACKGROUND) {
+                app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED,
                         ApplicationExitInfo.SUBREASON_UNKNOWN, true);
                 success = false;
             } else {
@@ -2420,22 +2493,23 @@
                         break;
                 }
                 mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
-                        0 /* unused */, app.pid, processGroup, app.processName));
+                        0 /* unused */, app.getPid(), processGroup, app.processName));
                 try {
+                    final int renderThreadTid = app.getRenderThreadTid();
                     if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
                         // do nothing if we already switched to RT
                         if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                             app.getWindowProcessController().onTopProcChanged();
                             if (mService.mUseFifoUiScheduling) {
                                 // Switch UI pipeline for app to SCHED_FIFO
-                                app.savedPriority = Process.getThreadPriority(app.pid);
-                                mService.scheduleAsFifoPriority(app.pid, /* suppressLogs */true);
-                                if (app.renderThreadTid != 0) {
-                                    mService.scheduleAsFifoPriority(app.renderThreadTid,
+                                state.setSavedPriority(Process.getThreadPriority(app.getPid()));
+                                mService.scheduleAsFifoPriority(app.getPid(), true);
+                                if (renderThreadTid != 0) {
+                                    mService.scheduleAsFifoPriority(renderThreadTid,
                                             /* suppressLogs */true);
                                     if (DEBUG_OOM_ADJ) {
                                         Slog.d("UI_FIFO", "Set RenderThread (TID " +
-                                                app.renderThreadTid + ") to FIFO");
+                                                renderThreadTid + ") to FIFO");
                                     }
                                 } else {
                                     if (DEBUG_OOM_ADJ) {
@@ -2444,10 +2518,10 @@
                                 }
                             } else {
                                 // Boost priority for top app UI and render threads
-                                setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
-                                if (app.renderThreadTid != 0) {
+                                setThreadPriority(app.getPid(), TOP_APP_PRIORITY_BOOST);
+                                if (renderThreadTid != 0) {
                                     try {
-                                        setThreadPriority(app.renderThreadTid,
+                                        setThreadPriority(renderThreadTid,
                                                 TOP_APP_PRIORITY_BOOST);
                                     } catch (IllegalArgumentException e) {
                                         // thread died, ignore
@@ -2461,10 +2535,10 @@
                         if (mService.mUseFifoUiScheduling) {
                             try {
                                 // Reset UI pipeline to SCHED_OTHER
-                                setThreadScheduler(app.pid, SCHED_OTHER, 0);
-                                setThreadPriority(app.pid, app.savedPriority);
-                                if (app.renderThreadTid != 0) {
-                                    setThreadScheduler(app.renderThreadTid,
+                                setThreadScheduler(app.getPid(), SCHED_OTHER, 0);
+                                setThreadPriority(app.getPid(), state.getSavedPriority());
+                                if (renderThreadTid != 0) {
+                                    setThreadScheduler(renderThreadTid,
                                             SCHED_OTHER, 0);
                                 }
                             } catch (IllegalArgumentException e) {
@@ -2476,135 +2550,141 @@
                             }
                         } else {
                             // Reset priority for top app UI and render threads
-                            setThreadPriority(app.pid, 0);
+                            setThreadPriority(app.getPid(), 0);
                         }
 
-                        if (app.renderThreadTid != 0) {
-                            setThreadPriority(app.renderThreadTid, THREAD_PRIORITY_DISPLAY);
+                        if (renderThreadTid != 0) {
+                            setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY);
                         }
                     }
                 } catch (Exception e) {
                     if (DEBUG_ALL) {
-                        Slog.w(TAG, "Failed setting thread priority of " + app.pid, e);
+                        Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
                     }
                 }
             }
         }
-        if (app.repForegroundActivities != app.hasForegroundActivities()) {
-            app.repForegroundActivities = app.hasForegroundActivities();
+        if (state.hasRepForegroundActivities() != state.hasForegroundActivities()) {
+            state.setRepForegroundActivities(state.hasForegroundActivities());
             changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
         }
 
-        updateAppFreezeStateLocked(app);
+        updateAppFreezeStateLSP(app);
 
-        if (app.getReportedProcState() != app.getCurProcState()) {
-            app.setReportedProcState(app.getCurProcState());
-            if (app.thread != null) {
+        if (state.getReportedProcState() != state.getCurProcState()) {
+            state.setReportedProcState(state.getCurProcState());
+            if (app.getThread() != null) {
                 try {
                     if (false) {
                         //RuntimeException h = new RuntimeException("here");
-                        Slog.i(TAG, "Sending new process state " + app.getReportedProcState()
+                        Slog.i(TAG, "Sending new process state " + state.getReportedProcState()
                                 + " to " + app /*, h*/);
                     }
-                    app.thread.setProcessState(app.getReportedProcState());
+                    app.getThread().setProcessState(state.getReportedProcState());
                 } catch (RemoteException e) {
                 }
             }
         }
-        if (app.setProcState == PROCESS_STATE_NONEXISTENT
-                || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) {
-            app.lastStateTime = now;
-            mService.mAppProfiler.updateNextPssTimeLocked(app.getCurProcState(), app, now, true);
+        boolean forceUpdatePssTime = false;
+        if (state.getSetProcState() == PROCESS_STATE_NONEXISTENT
+                || ProcessList.procStatesDifferForMem(
+                        state.getCurProcState(), state.getSetProcState())) {
+            state.setLastStateTime(now);
+            forceUpdatePssTime = true;
             if (DEBUG_PSS) {
                 Slog.d(TAG_PSS, "Process state change from "
-                        + ProcessList.makeProcStateString(app.setProcState) + " to "
-                        + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in "
-                        + (app.nextPssTime - now) + ": " + app);
+                        + ProcessList.makeProcStateString(state.getSetProcState()) + " to "
+                        + ProcessList.makeProcStateString(state.getCurProcState()) + " next pss in "
+                        + (app.mProfile.getNextPssTime() - now) + ": " + app);
             }
-        } else {
-            mService.mAppProfiler.updateNextPssTimeLocked(app.getCurProcState(), app, now, false);
         }
-        if (app.setProcState != app.getCurProcState()) {
+        synchronized (mService.mAppProfiler.mProfilerLock) {
+            app.mProfile.updateProcState(app.mState);
+            mService.mAppProfiler.updateNextPssTimeLPf(
+                    state.getCurProcState(), app.mProfile, now, forceUpdatePssTime);
+        }
+        if (state.getSetProcState() != state.getCurProcState()) {
             if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
                 String msg = "Proc state change of " + app.processName
-                        + " to " + ProcessList.makeProcStateString(app.getCurProcState())
-                        + " (" + app.getCurProcState() + ")" + ": " + app.adjType;
+                        + " to " + ProcessList.makeProcStateString(state.getCurProcState())
+                        + " (" + state.getCurProcState() + ")" + ": " + state.getAdjType();
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
             }
-            boolean setImportant = app.setProcState < PROCESS_STATE_SERVICE;
-            boolean curImportant = app.getCurProcState() < PROCESS_STATE_SERVICE;
+            boolean setImportant = state.getSetProcState() < PROCESS_STATE_SERVICE;
+            boolean curImportant = state.getCurProcState() < PROCESS_STATE_SERVICE;
             if (setImportant && !curImportant) {
                 // This app is no longer something we consider important enough to allow to use
                 // arbitrary amounts of battery power. Note its current CPU time to later know to
                 // kill it if it is not behaving well.
-                app.setWhenUnimportant(now);
-                app.lastCpuTime = 0;
+                state.setWhenUnimportant(now);
+                app.mProfile.mLastCpuTime.set(0);
             }
             // Inform UsageStats of important process state change
             // Must be called before updating setProcState
-            maybeUpdateUsageStatsLocked(app, nowElapsed);
+            maybeUpdateUsageStatsLSP(app, nowElapsed);
 
-            maybeUpdateLastTopTime(app, now);
+            maybeUpdateLastTopTime(state, now);
 
-            app.setProcState = app.getCurProcState();
-            if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
-                app.notCachedSinceIdle = false;
+            state.setSetProcState(state.getCurProcState());
+            if (state.getSetProcState() >= ActivityManager.PROCESS_STATE_HOME) {
+                state.setNotCachedSinceIdle(false);
             }
             if (!doingAll) {
-                mService.setProcessTrackerStateLocked(app,
+                mService.setProcessTrackerStateLOSP(app,
                         mService.mProcessStats.getMemFactorLocked(), now);
             } else {
-                app.procStateChanged = true;
+                state.setProcStateChanged(true);
             }
-        } else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime())
+        } else if (state.hasReportedInteraction() && (nowElapsed - state.getInteractionEventTime())
                 > mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
             // For apps that sit around for a long time in the interactive state, we need
             // to report this at least once a day so they don't go idle.
-            maybeUpdateUsageStatsLocked(app, nowElapsed);
-        } else if (!app.reportedInteraction && (nowElapsed - app.getFgInteractionTime())
+            maybeUpdateUsageStatsLSP(app, nowElapsed);
+        } else if (!state.hasReportedInteraction() && (nowElapsed - state.getFgInteractionTime())
                 > mConstants.SERVICE_USAGE_INTERACTION_TIME) {
             // For foreground services that sit around for a long time but are not interacted with.
-            maybeUpdateUsageStatsLocked(app, nowElapsed);
+            maybeUpdateUsageStatsLSP(app, nowElapsed);
         }
 
-        if (app.curCapability != app.setCapability) {
+        if (state.getCurCapability() != state.getSetCapability()) {
             changes |= ActivityManagerService.ProcessChangeItem.CHANGE_CAPABILITY;
-            app.setCapability = app.curCapability;
+            state.setSetCapability(state.getCurCapability());
         }
 
         if (changes != 0) {
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
                     "Changes in " + app + ": " + changes);
             ActivityManagerService.ProcessChangeItem item =
-                    mService.enqueueProcessChangeItemLocked(app.pid, app.info.uid);
+                    mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid);
             item.changes |= changes;
-            item.foregroundActivities = app.repForegroundActivities;
-            item.capability = app.setCapability;
+            item.foregroundActivities = state.hasRepForegroundActivities();
+            item.capability = state.getSetCapability();
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
                     "Item " + Integer.toHexString(System.identityHashCode(item))
                             + " " + app.toShortString() + ": changes=" + item.changes
                             + " foreground=" + item.foregroundActivities
-                            + " type=" + app.adjType + " source=" + app.adjSource
-                            + " target=" + app.adjTarget + " capability=" + item.capability);
+                            + " type=" + state.getAdjType() + " source=" + state.getAdjSource()
+                            + " target=" + state.getAdjTarget() + " capability=" + item.capability);
         }
 
         return success;
     }
 
-    @GuardedBy("mService")
-    void setAttachingSchedGroupLocked(ProcessRecord app) {
+    @GuardedBy({"mService", "mProcLock"})
+    void setAttachingSchedGroupLSP(ProcessRecord app) {
         int initialSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+        final ProcessStateRecord state = app.mState;
         // If the process has been marked as foreground via Zygote.START_FLAG_USE_TOP_APP_PRIORITY,
         // then verify that the top priority is actually is applied.
-        if (app.hasForegroundActivities()) {
+        if (state.hasForegroundActivities()) {
             String fallbackReason = null;
             try {
-                // The priority must be the same as how does {@link #applyOomAdjLocked} set for
+                // The priority must be the same as how does {@link #applyOomAdjLSP} set for
                 // {@link ProcessList.SCHED_GROUP_TOP_APP}. We don't check render thread because it
                 // is not ready when attaching.
-                if (Process.getProcessGroup(app.pid) == THREAD_GROUP_TOP_APP) {
+                if (Process.getProcessGroup(app.getPid()) == THREAD_GROUP_TOP_APP) {
                     app.getWindowProcessController().onTopProcChanged();
-                    setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
+                    setThreadPriority(app.getPid(), TOP_APP_PRIORITY_BOOST);
                 } else {
                     fallbackReason = "not expected top priority";
                 }
@@ -2620,23 +2700,27 @@
             }
         }
 
-        app.setCurrentSchedulingGroup(app.setSchedGroup = initialSchedGroup);
+        state.setSetSchedGroup(initialSchedGroup);
+        state.setCurrentSchedulingGroup(initialSchedGroup);
     }
 
     // ONLY used for unit testing in OomAdjusterTests.java
     @VisibleForTesting
     void maybeUpdateUsageStats(ProcessRecord app, long nowElapsed) {
         synchronized (mService) {
-            maybeUpdateUsageStatsLocked(app, nowElapsed);
+            synchronized (mProcLock) {
+                maybeUpdateUsageStatsLSP(app, nowElapsed);
+            }
         }
     }
 
-    @GuardedBy("mService")
-    private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
+    @GuardedBy({"mService", "mProcLock"})
+    private void maybeUpdateUsageStatsLSP(ProcessRecord app, long nowElapsed) {
+        final ProcessStateRecord state = app.mState;
         if (DEBUG_USAGE_STATS) {
             Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
-                    + "] state changes: old = " + app.setProcState + ", new = "
-                    + app.getCurProcState());
+                    + "] state changes: old = " + state.getSetProcState() + ", new = "
+                    + state.getCurProcState());
         }
         if (mService.mUsageStatsService == null) {
             return;
@@ -2645,27 +2729,28 @@
         // To avoid some abuse patterns, we are going to be careful about what we consider
         // to be an app interaction.  Being the top activity doesn't count while the display
         // is sleeping, nor do short foreground services.
-        if (app.getCurProcState() <= PROCESS_STATE_TOP
-                || app.getCurProcState() == PROCESS_STATE_BOUND_TOP) {
+        if (state.getCurProcState() <= PROCESS_STATE_TOP
+                || state.getCurProcState() == PROCESS_STATE_BOUND_TOP) {
             isInteraction = true;
-            app.setFgInteractionTime(0);
-        } else if (app.getCurProcState() <= PROCESS_STATE_FOREGROUND_SERVICE) {
-            if (app.getFgInteractionTime() == 0) {
-                app.setFgInteractionTime(nowElapsed);
+            state.setFgInteractionTime(0);
+        } else if (state.getCurProcState() <= PROCESS_STATE_FOREGROUND_SERVICE) {
+            if (state.getFgInteractionTime() == 0) {
+                state.setFgInteractionTime(nowElapsed);
                 isInteraction = false;
             } else {
-                isInteraction = nowElapsed > app.getFgInteractionTime()
+                isInteraction = nowElapsed > state.getFgInteractionTime()
                         + mConstants.SERVICE_USAGE_INTERACTION_TIME;
             }
         } else {
             isInteraction =
-                    app.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
-            app.setFgInteractionTime(0);
+                    state.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
+            state.setFgInteractionTime(0);
         }
         if (isInteraction
-                && (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime())
-                > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
-            app.setInteractionEventTime(nowElapsed);
+                && (!state.hasReportedInteraction()
+                    || (nowElapsed - state.getInteractionEventTime())
+                    > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
+            state.setInteractionEventTime(nowElapsed);
             String[] packages = app.getPackageList();
             if (packages != null) {
                 for (int i = 0; i < packages.length; i++) {
@@ -2674,16 +2759,16 @@
                 }
             }
         }
-        app.reportedInteraction = isInteraction;
+        state.setReportedInteraction(isInteraction);
         if (!isInteraction) {
-            app.setInteractionEventTime(0);
+            state.setInteractionEventTime(0);
         }
     }
 
-    private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) {
-        if (app.setProcState <= PROCESS_STATE_TOP
-                && app.getCurProcState() > PROCESS_STATE_TOP) {
-            app.lastTopTime = nowUptime;
+    private void maybeUpdateLastTopTime(ProcessStateRecord state, long nowUptime) {
+        if (state.getSetProcState() <= PROCESS_STATE_TOP
+                && state.getCurProcState() > PROCESS_STATE_TOP) {
+            state.setLastTopTime(nowUptime);
         }
     }
 
@@ -2705,13 +2790,15 @@
         }
         for (int i = N - 1; i >= 0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
-            final long bgTime = uidRec.lastBackgroundTime;
-            if (bgTime > 0 && !uidRec.idle) {
+            final long bgTime = uidRec.getLastBackgroundTime();
+            if (bgTime > 0 && !uidRec.isIdle()) {
                 if (bgTime <= maxBgTime) {
-                    EventLogTags.writeAmUidIdle(uidRec.uid);
-                    uidRec.idle = true;
-                    uidRec.setIdle = true;
-                    mService.doStopUidLocked(uidRec.uid, uidRec);
+                    EventLogTags.writeAmUidIdle(uidRec.getUid());
+                    synchronized (mProcLock) {
+                        uidRec.setIdle(true);
+                        uidRec.setSetIdle(true);
+                    }
+                    mService.doStopUidLocked(uidRec.getUid(), uidRec);
                 } else {
                     if (nextTime == 0 || nextTime > bgTime) {
                         nextTime = bgTime;
@@ -2729,35 +2816,35 @@
         }
     }
 
-    @GuardedBy("mService")
-    final void setAppIdTempWhitelistStateLocked(int uid, boolean onWhitelist) {
+    @GuardedBy({"mService", "mProcLock"})
+    void setAppIdTempAllowlistStateLSP(int uid, boolean onAllowlist) {
         boolean changed = false;
         for (int i = mActiveUids.size() - 1; i >= 0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
-            if (uidRec.uid == uid && uidRec.curWhitelist != onWhitelist) {
-                uidRec.curWhitelist = onWhitelist;
+            if (uidRec.getUid() == uid && uidRec.isCurAllowListed() != onAllowlist) {
+                uidRec.setCurAllowListed(onAllowlist);
                 changed = true;
             }
         }
         if (changed) {
-            updateOomAdjLocked(OOM_ADJ_REASON_WHITELIST);
+            updateOomAdjLSP(OOM_ADJ_REASON_ALLOWLIST);
         }
     }
 
-    @GuardedBy("mService")
-    final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) {
+    @GuardedBy({"mService", "mProcLock"})
+    void setUidTempAllowlistStateLSP(int uid, boolean onAllowlist) {
         boolean changed = false;
         final UidRecord uidRec = mActiveUids.get(uid);
-        if (uidRec != null && uidRec.curWhitelist != onWhitelist) {
-            uidRec.curWhitelist = onWhitelist;
-            updateOomAdjLocked(OOM_ADJ_REASON_WHITELIST);
+        if (uidRec != null && uidRec.isCurAllowListed() != onAllowlist) {
+            uidRec.setCurAllowListed(onAllowlist);
+            updateOomAdjLSP(OOM_ADJ_REASON_ALLOWLIST);
         }
     }
 
     @GuardedBy("mService")
     void dumpProcessListVariablesLocked(ProtoOutputStream proto) {
         proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq);
-        proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq);
+        proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.getLruSeqLOSP());
         proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS,
                 mNumNonCachedProcs);
         proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs);
@@ -2768,19 +2855,19 @@
 
     @GuardedBy("mService")
     void dumpSequenceNumbersLocked(PrintWriter pw) {
-        pw.println("  mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq);
+        pw.println("  mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.getLruSeqLOSP());
     }
 
     @GuardedBy("mService")
     void dumpProcCountsLocked(PrintWriter pw) {
         pw.println("  mNumNonCachedProcs=" + mNumNonCachedProcs
-                + " (" + mProcessList.getLruSizeLocked() + " total)"
+                + " (" + mProcessList.getLruSizeLOSP() + " total)"
                 + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
                 + " mNumServiceProcs=" + mNumServiceProcs
                 + " mNewNumServiceProcs=" + mNewNumServiceProcs);
     }
 
-    @GuardedBy("mService")
+    @GuardedBy("mProcLock")
     void dumpCachedAppOptimizerSettings(PrintWriter pw) {
         mCachedAppOptimizer.dump(pw);
     }
@@ -2790,22 +2877,25 @@
         mCacheOomRanker.dump(pw);
     }
 
-    @GuardedBy("mService")
-    void updateAppFreezeStateLocked(ProcessRecord app) {
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateAppFreezeStateLSP(ProcessRecord app) {
         if (!mCachedAppOptimizer.useFreezer()) {
             return;
         }
 
+        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
         // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
-        if (app.frozen && app.shouldNotFreeze) {
-            mCachedAppOptimizer.unfreezeAppLocked(app);
+        if (opt.isFrozen() && opt.shouldNotFreeze()) {
+            mCachedAppOptimizer.unfreezeAppLSP(app);
         }
 
+        final ProcessStateRecord state = app.mState;
         // Use current adjustment when freezing, set adjustment when unfreezing.
-        if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) {
-            mCachedAppOptimizer.freezeAppAsync(app);
-        } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) {
-            mCachedAppOptimizer.unfreezeAppLocked(app);
+        if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen()
+                && !opt.shouldNotFreeze()) {
+            mCachedAppOptimizer.freezeAppAsyncLSP(app);
+        } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ && opt.isFrozen()) {
+            mCachedAppOptimizer.unfreezeAppLSP(app);
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/PackageList.java b/services/core/java/com/android/server/am/PackageList.java
new file mode 100644
index 0000000..aa10d52
--- /dev/null
+++ b/services/core/java/com/android/server/am/PackageList.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import android.annotation.NonNull;
+import android.content.pm.VersionedPackage;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.ProcessStats;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * List of packages running in the process, self locked.
+ */
+final class PackageList {
+    private final ProcessRecord mProcess;
+
+    private final ArrayMap<String, ProcessStats.ProcessStateHolder> mPkgList = new ArrayMap<>();
+
+    PackageList(final ProcessRecord app) {
+        mProcess = app;
+    }
+
+    ProcessStats.ProcessStateHolder put(String key, ProcessStats.ProcessStateHolder value) {
+        synchronized (this) {
+            mProcess.getWindowProcessController().addPackage(key);
+            return mPkgList.put(key, value);
+        }
+    }
+
+    void clear() {
+        synchronized (this) {
+            mPkgList.clear();
+            mProcess.getWindowProcessController().clearPackageList();
+        }
+    }
+
+    int size() {
+        synchronized (this) {
+            return mPkgList.size();
+        }
+    }
+
+    boolean containsKey(Object key) {
+        synchronized (this) {
+            return mPkgList.containsKey(key);
+        }
+    }
+
+    ProcessStats.ProcessStateHolder get(String pkgName) {
+        synchronized (this) {
+            return mPkgList.get(pkgName);
+        }
+    }
+
+    void forEachPackage(@NonNull Consumer<String> callback) {
+        synchronized (this) {
+            for (int i = 0, size = mPkgList.size(); i < size; i++) {
+                callback.accept(mPkgList.keyAt(i));
+            }
+        }
+    }
+
+    void forEachPackage(@NonNull BiConsumer<String, ProcessStats.ProcessStateHolder> callback) {
+        synchronized (this) {
+            for (int i = 0, size = mPkgList.size(); i < size; i++) {
+                callback.accept(mPkgList.keyAt(i), mPkgList.valueAt(i));
+            }
+        }
+    }
+
+    /**
+     * Search in the package list, invoke the given {@code callback} with each of the package names
+     * in that list; if the callback returns a non-null object, halt the search, return that
+     * object as the return value of this search function.
+     *
+     * @param callback The callback interface to accept the current package name; if it returns
+     *                 a non-null object, the search will be halted and this object will be used
+     *                 as the return value of this search function.
+     */
+    <R> R searchEachPackage(@NonNull Function<String, R> callback) {
+        synchronized (this) {
+            for (int i = 0, size = mPkgList.size(); i < size; i++) {
+                R r = callback.apply(mPkgList.keyAt(i));
+                if (r != null) {
+                    return r;
+                }
+            }
+        }
+        return null;
+    }
+
+    void forEachPackageProcessStats(@NonNull Consumer<ProcessStats.ProcessStateHolder> callback) {
+        synchronized (this) {
+            for (int i = 0, size = mPkgList.size(); i < size; i++) {
+                callback.accept(mPkgList.valueAt(i));
+            }
+        }
+    }
+
+    @GuardedBy("this")
+    ArrayMap<String, ProcessStats.ProcessStateHolder> getPackageListLocked() {
+        return mPkgList;
+    }
+
+    String[] getPackageList() {
+        synchronized (this) {
+            int size = mPkgList.size();
+            if (size == 0) {
+                return null;
+            }
+            final String[] list = new String[size];
+            for (int i = 0; i < size; i++) {
+                list[i] = mPkgList.keyAt(i);
+            }
+            return list;
+        }
+    }
+
+    List<VersionedPackage> getPackageListWithVersionCode() {
+        synchronized (this) {
+            int size = mPkgList.size();
+            if (size == 0) {
+                return null;
+            }
+            List<VersionedPackage> list = new ArrayList<>();
+            for (int i = 0; i < size; i++) {
+                list.add(new VersionedPackage(mPkgList.keyAt(i), mPkgList.valueAt(i).appVersion));
+            }
+            return list;
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        synchronized (this) {
+            pw.print(prefix); pw.print("packageList={");
+            for (int i = 0, size = mPkgList.size(); i < size; i++) {
+                if (i > 0) pw.print(", ");
+                pw.print(mPkgList.keyAt(i));
+            }
+            pw.println("}");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 631b632..0eb48f6 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -24,7 +24,6 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
@@ -65,7 +64,8 @@
     boolean canceled = false;
     /**
      * Map IBinder to duration specified as Pair<Long, Integer>, Long is allowlist duration in
-     * milliseconds, Integer is allowlist type defined at {@link BroadcastOptions.TempAllowListType}
+     * milliseconds, Integer is allowlist type defined at
+     * {@link android.os.PowerWhitelistManager.TempAllowListType}
      */
     private ArrayMap<IBinder, Pair<Long, Integer>> mWhitelistDuration;
     private RemoteCallbackList<IResultReceiver> mCancelCallbacks;
diff --git a/services/core/java/com/android/server/am/PendingTempAllowlists.java b/services/core/java/com/android/server/am/PendingTempAllowlists.java
new file mode 100644
index 0000000..75935c4
--- /dev/null
+++ b/services/core/java/com/android/server/am/PendingTempAllowlists.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 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.am;
+
+import android.util.SparseArray;
+
+/** Allowlists of uids to temporarily bypass Power Save mode. */
+final class PendingTempAllowlists {
+
+    private ActivityManagerService mService;
+
+    private final SparseArray<ActivityManagerService.PendingTempAllowlist> mPendingTempAllowlist =
+            new SparseArray<>();
+
+    PendingTempAllowlists(ActivityManagerService service) {
+        mService = service;
+    }
+
+    void put(int uid, ActivityManagerService.PendingTempAllowlist value) {
+        mPendingTempAllowlist.put(uid, value);
+        mService.mAtmInternal.onUidAddedToPendingTempAllowlist(uid, value.tag);
+    }
+
+    void removeAt(int index) {
+        final int uid = mPendingTempAllowlist.keyAt(index);
+        mPendingTempAllowlist.removeAt(index);
+        mService.mAtmInternal.onUidRemovedFromPendingTempAllowlist(uid);
+    }
+
+    ActivityManagerService.PendingTempAllowlist get(int uid) {
+        return mPendingTempAllowlist.get(uid);
+    }
+
+    int size() {
+        return mPendingTempAllowlist.size();
+    }
+
+    ActivityManagerService.PendingTempAllowlist valueAt(int index) {
+        return mPendingTempAllowlist.valueAt(index);
+    }
+
+    int indexOfKey(int key) {
+        return mPendingTempAllowlist.indexOfKey(key);
+    }
+}
diff --git a/services/core/java/com/android/server/am/PendingTempWhitelists.java b/services/core/java/com/android/server/am/PendingTempWhitelists.java
deleted file mode 100644
index 50d58f0..0000000
--- a/services/core/java/com/android/server/am/PendingTempWhitelists.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2018 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.am;
-
-import android.util.SparseArray;
-
-/** Whitelists of uids to temporarily bypass Power Save mode. */
-final class PendingTempWhitelists {
-
-    private ActivityManagerService mService;
-
-    private final SparseArray<ActivityManagerService.PendingTempWhitelist> mPendingTempWhitelist =
-            new SparseArray<>();
-
-    PendingTempWhitelists(ActivityManagerService service) {
-        mService = service;
-    }
-
-    void put(int uid, ActivityManagerService.PendingTempWhitelist value) {
-        mPendingTempWhitelist.put(uid, value);
-        mService.mAtmInternal.onUidAddedToPendingTempAllowlist(uid, value.tag);
-    }
-
-    void removeAt(int index) {
-        final int uid = mPendingTempWhitelist.keyAt(index);
-        mPendingTempWhitelist.removeAt(index);
-        mService.mAtmInternal.onUidRemovedFromPendingTempAllowlist(uid);
-    }
-
-    ActivityManagerService.PendingTempWhitelist get(int uid) {
-        return mPendingTempWhitelist.get(uid);
-    }
-
-    int size() {
-        return mPendingTempWhitelist.size();
-    }
-
-    ActivityManagerService.PendingTempWhitelist valueAt(int index) {
-        return mPendingTempWhitelist.valueAt(index);
-    }
-
-    int indexOfKey(int key) {
-        return mPendingTempWhitelist.indexOfKey(key);
-    }
-}
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index 5167c57..4f3438fe 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -38,6 +38,7 @@
 
 import libcore.io.IoUtils;
 
+import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -109,10 +110,23 @@
     private final ActivityManagerService mService;
     private final Handler mKillHandler;
 
+    private static final int CGROUP_V1 = 0;
+    private static final int CGROUP_V2 = 1;
+    private static final String[] CGROUP_PATH_PREFIXES = {
+        "/acct/uid_" /* cgroup v1 */,
+        "/sys/fs/cgroup/uid_" /* cgroup v2 */
+    };
+    private static final String CGROUP_PID_PREFIX = "/pid_";
+    private static final String CGROUP_PROCS = "/cgroup.procs";
+
+    @VisibleForTesting
+    int mCgroupVersion = CGROUP_V1;
+
     PhantomProcessList(final ActivityManagerService service) {
         mService = service;
         mKillHandler = service.mProcessList.sKillHandler;
         mInjector = new Injector();
+        probeCgroupVersion();
     }
 
     @VisibleForTesting
@@ -134,13 +148,14 @@
 
     @GuardedBy({"mLock", "mService.mPidsSelfLocked"})
     private void lookForPhantomProcessesLocked(ProcessRecord app) {
-        if (app.appZygote || app.killed || app.killedByAm) {
+        if (app.appZygote || app.isKilled() || app.isKilledByAm()) {
             // process forked from app zygote doesn't have its own acct entry
             return;
         }
-        InputStream input = mCgroupProcsFds.get(app.pid);
+        final int appPid = app.getPid();
+        InputStream input = mCgroupProcsFds.get(appPid);
         if (input == null) {
-            final String path = getCgroupFilePath(app.info.uid, app.pid);
+            final String path = getCgroupFilePath(app.info.uid, appPid);
             try {
                 input = mInjector.openCgroupProcs(path);
             } catch (FileNotFoundException | SecurityException e) {
@@ -150,7 +165,7 @@
                 return;
             }
             // Keep the FD open for better performance
-            mCgroupProcsFds.put(app.pid, input);
+            mCgroupProcsFds.put(appPid, input);
         }
         final byte[] buf = mDataBuffer;
         try {
@@ -166,7 +181,7 @@
                 for (int i = 0; i < read; i++) {
                     final byte b = buf[i];
                     if (b == '\n') {
-                        addChildPidLocked(app, pid);
+                        addChildPidLocked(app, pid, appPid);
                         pid = 0;
                     } else {
                         pid = pid * 10 + (b - '0');
@@ -179,20 +194,29 @@
                 }
             } while (true);
             if (pid != 0) {
-                addChildPidLocked(app, pid);
+                addChildPidLocked(app, pid, appPid);
             }
             // rewind the fd for the next read
             input.skip(-totalRead);
         } catch (IOException e) {
             Slog.e(TAG, "Error in reading cgroup procs from " + app, e);
             IoUtils.closeQuietly(input);
-            mCgroupProcsFds.delete(app.pid);
+            mCgroupProcsFds.delete(appPid);
+        }
+    }
+
+    private void probeCgroupVersion() {
+        for (int i = CGROUP_PATH_PREFIXES.length - 1; i >= 0; i--) {
+            if ((new File(CGROUP_PATH_PREFIXES[i] + Process.SYSTEM_UID)).exists()) {
+                mCgroupVersion = i;
+                break;
+            }
         }
     }
 
     @VisibleForTesting
-    static String getCgroupFilePath(int uid, int pid) {
-        return "/acct/uid_" + uid + "/pid_" + pid + "/cgroup.procs";
+    String getCgroupFilePath(int uid, int pid) {
+        return CGROUP_PATH_PREFIXES[mCgroupVersion] + uid + CGROUP_PID_PREFIX + pid + CGROUP_PROCS;
     }
 
     static String getProcessName(int pid) {
@@ -209,8 +233,8 @@
     }
 
     @GuardedBy({"mLock", "mService.mPidsSelfLocked"})
-    private void addChildPidLocked(final ProcessRecord app, final int pid) {
-        if (app.pid != pid) {
+    private void addChildPidLocked(final ProcessRecord app, final int pid, final int appPid) {
+        if (appPid != pid) {
             // That's something else...
             final ProcessRecord r = mService.mPidsSelfLocked.get(pid);
             if (r != null) {
@@ -305,15 +329,16 @@
         if (r != null) {
             // It's a phantom process, bookkeep it
             try {
+                final int appPid = r.getPid();
                 final PhantomProcessRecord proc = new PhantomProcessRecord(
-                        processName, uid, pid, r.pid, mService,
+                        processName, uid, pid, appPid, mService,
                         this::onPhantomProcessKilledLocked);
                 proc.mUpdateSeq = mUpdateSeq;
                 mPhantomProcesses.put(pid, proc);
-                SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(r.pid);
+                SparseArray<PhantomProcessRecord> array = mAppPhantomProcessMap.get(appPid);
                 if (array == null) {
                     array = new SparseArray<>();
-                    mAppPhantomProcessMap.put(r.pid, array);
+                    mAppPhantomProcessMap.put(appPid, array);
                 }
                 array.put(pid, proc);
                 if (proc.mPidFd != null) {
@@ -391,40 +416,42 @@
      * order of the oom adjs of their parent process.
      */
     void trimPhantomProcessesIfNecessary() {
-        synchronized (mLock) {
-            mTrimPhantomProcessScheduled = false;
-            if (mService.mConstants.MAX_PHANTOM_PROCESSES < mPhantomProcesses.size()) {
-                for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
-                    mTempPhantomProcesses.add(mPhantomProcesses.valueAt(i));
+        synchronized (mService.mProcLock) {
+            synchronized (mLock) {
+                mTrimPhantomProcessScheduled = false;
+                if (mService.mConstants.MAX_PHANTOM_PROCESSES < mPhantomProcesses.size()) {
+                    for (int i = mPhantomProcesses.size() - 1; i >= 0; i--) {
+                        mTempPhantomProcesses.add(mPhantomProcesses.valueAt(i));
+                    }
+                    synchronized (mService.mPidsSelfLocked) {
+                        Collections.sort(mTempPhantomProcesses, (a, b) -> {
+                            final ProcessRecord ra = mService.mPidsSelfLocked.get(a.mPpid);
+                            if (ra == null) {
+                                // parent is gone, this process should have been killed too
+                                return 1;
+                            }
+                            final ProcessRecord rb = mService.mPidsSelfLocked.get(b.mPpid);
+                            if (rb == null) {
+                                // parent is gone, this process should have been killed too
+                                return -1;
+                            }
+                            if (ra.mState.getCurAdj() != rb.mState.getCurAdj()) {
+                                return ra.mState.getCurAdj() - rb.mState.getCurAdj();
+                            }
+                            if (a.mKnownSince != b.mKnownSince) {
+                                // In case of identical oom adj, younger one first
+                                return a.mKnownSince < b.mKnownSince ? 1 : -1;
+                            }
+                            return 0;
+                        });
+                    }
+                    for (int i = mTempPhantomProcesses.size() - 1;
+                            i >= mService.mConstants.MAX_PHANTOM_PROCESSES; i--) {
+                        final PhantomProcessRecord proc = mTempPhantomProcesses.get(i);
+                        proc.killLocked("Trimming phantom processes", true);
+                    }
+                    mTempPhantomProcesses.clear();
                 }
-                synchronized (mService.mPidsSelfLocked) {
-                    Collections.sort(mTempPhantomProcesses, (a, b) -> {
-                        final ProcessRecord ra = mService.mPidsSelfLocked.get(a.mPpid);
-                        if (ra == null) {
-                            // parent is gone, this process should have been killed too
-                            return 1;
-                        }
-                        final ProcessRecord rb = mService.mPidsSelfLocked.get(b.mPpid);
-                        if (rb == null) {
-                            // parent is gone, this process should have been killed too
-                            return -1;
-                        }
-                        if (ra.curAdj != rb.curAdj) {
-                            return ra.curAdj - rb.curAdj;
-                        }
-                        if (a.mKnownSince != b.mKnownSince) {
-                            // In case of identical oom adj, younger one first
-                            return a.mKnownSince < b.mKnownSince ? 1 : -1;
-                        }
-                        return 0;
-                    });
-                }
-                for (int i = mTempPhantomProcesses.size() - 1;
-                        i >= mService.mConstants.MAX_PHANTOM_PROCESSES; i--) {
-                    final PhantomProcessRecord proc = mTempPhantomProcesses.get(i);
-                    proc.killLocked("Trimming phantom processes", true);
-                }
-                mTempPhantomProcesses.clear();
             }
         }
     }
@@ -475,7 +502,7 @@
             }
         }
         // Lastly, kill the parent process too
-        app.kill("Caused by child process: " + msg, reasonCode, subReason, true);
+        app.killLocked("Caused by child process: " + msg, reasonCode, subReason, true);
     }
 
     /**
@@ -485,7 +512,7 @@
     void forEachPhantomProcessOfApp(final ProcessRecord app,
             final Function<PhantomProcessRecord, Boolean> callback) {
         synchronized (mLock) {
-            int index = mAppPhantomProcessMap.indexOfKey(app.pid);
+            int index = mAppPhantomProcessMap.indexOfKey(app.getPid());
             if (index >= 0) {
                 final SparseArray<PhantomProcessRecord> array =
                         mAppPhantomProcessMap.valueAt(index);
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
new file mode 100644
index 0000000..4643610
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * The state info of app when it's cached, used by the optimizer.
+ */
+final class ProcessCachedOptimizerRecord {
+    private final ProcessRecord mApp;
+
+    private final ActivityManagerGlobalLock mProcLock;
+
+    /**
+     * The last time that this process was compacted.
+     */
+    @GuardedBy("mProcLock")
+    private long mLastCompactTime;
+
+    /**
+     * The most recent compaction action requested for this app.
+     */
+    @GuardedBy("mProcLock")
+    private int mReqCompactAction;
+
+    /**
+     * The most recent compaction action performed for this app.
+     */
+    @GuardedBy("mProcLock")
+    private int mLastCompactAction;
+
+    /**
+     * True when the process is frozen.
+     */
+    @GuardedBy("mProcLock")
+    private boolean mFrozen;
+
+    /**
+     * An override on the freeze state is in progress.
+     */
+    @GuardedBy("mProcLock")
+    boolean mFreezerOverride;
+
+    /**
+     * Last time the app was (un)frozen, 0 for never.
+     */
+    @GuardedBy("mProcLock")
+    private long mFreezeUnfreezeTime;
+
+    /**
+     * True if a process has a WPRI binding from an unfrozen process.
+     */
+    @GuardedBy("mProcLock")
+    private boolean mShouldNotFreeze;
+
+    @GuardedBy("mProcLock")
+    long getLastCompactTime() {
+        return mLastCompactTime;
+    }
+
+    @GuardedBy("mProcLock")
+    void setLastCompactTime(long lastCompactTime) {
+        mLastCompactTime = lastCompactTime;
+    }
+
+    @GuardedBy("mProcLock")
+    int getReqCompactAction() {
+        return mReqCompactAction;
+    }
+
+    @GuardedBy("mProcLock")
+    void setReqCompactAction(int reqCompactAction) {
+        mReqCompactAction = reqCompactAction;
+    }
+
+    @GuardedBy("mProcLock")
+    int getLastCompactAction() {
+        return mLastCompactAction;
+    }
+
+    @GuardedBy("mProcLock")
+    void setLastCompactAction(int lastCompactAction) {
+        mLastCompactAction = lastCompactAction;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean isFrozen() {
+        return mFrozen;
+    }
+
+    @GuardedBy("mProcLock")
+    void setFrozen(boolean frozen) {
+        mFrozen = frozen;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean hasFreezerOverride() {
+        return mFreezerOverride;
+    }
+
+    @GuardedBy("mProcLock")
+    void setFreezerOverride(boolean freezerOverride) {
+        mFreezerOverride = freezerOverride;
+    }
+
+    @GuardedBy("mProcLock")
+    long getFreezeUnfreezeTime() {
+        return mFreezeUnfreezeTime;
+    }
+
+    @GuardedBy("mProcLock")
+    void setFreezeUnfreezeTime(long freezeUnfreezeTime) {
+        mFreezeUnfreezeTime = freezeUnfreezeTime;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean shouldNotFreeze() {
+        return mShouldNotFreeze;
+    }
+
+    @GuardedBy("mProcLock")
+    void setShouldNotFreeze(boolean shouldNotFreeze) {
+        mShouldNotFreeze = shouldNotFreeze;
+    }
+
+    ProcessCachedOptimizerRecord(ProcessRecord app) {
+        mApp = app;
+        mProcLock = app.mService.mProcLock;
+    }
+
+    void init(long nowUptime) {
+        mFreezeUnfreezeTime = nowUptime;
+    }
+
+    @GuardedBy("mProcLock")
+    void dump(PrintWriter pw, String prefix, long nowUptime) {
+        pw.print(prefix); pw.print("lastCompactTime="); pw.print(mLastCompactTime);
+        pw.print(" lastCompactAction="); pw.println(mLastCompactAction);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
new file mode 100644
index 0000000..1653123
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
+import static com.android.server.am.ActivityManagerService.MY_PID;
+import static com.android.server.am.ProcessRecord.TAG;
+
+import android.app.ActivityManager;
+import android.app.ApplicationErrorReport;
+import android.app.ApplicationExitInfo;
+import android.content.ComponentName;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IncrementalStatesInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.MemoryPressureUtil;
+import com.android.server.wm.WindowProcessController;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+
+/**
+ * The error state of the process, such as if it's crashing/ANR etc.
+ */
+class ProcessErrorStateRecord {
+    final ProcessRecord mApp;
+    private final ActivityManagerService mService;
+
+    private final ActivityManagerGlobalLock mProcLock;
+
+    /**
+     * True if disabled in the bad process list.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mBad;
+
+    /**
+     * Are we in the process of crashing?
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mCrashing;
+
+    /**
+     * Suppress normal auto-dismiss of crash dialog &amp; report UI?
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mForceCrashReport;
+
+    /**
+     * Does the app have a not responding dialog?
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mNotResponding;
+
+    /**
+     * The report about crash of the app, generated &amp; stored when an app gets into a crash.
+     * Will be "null" when all is OK.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private ActivityManager.ProcessErrorStateInfo mCrashingReport;
+
+    /**
+     * The report about ANR of the app, generated &amp; stored when an app gets into an ANR.
+     * Will be "null" when all is OK.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private ActivityManager.ProcessErrorStateInfo mNotRespondingReport;
+
+    /**
+     * Controller for error dialogs.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private final ErrorDialogController mDialogController;
+
+    /**
+     * Who will be notified of the error. This is usually an activity in the
+     * app that installed the package.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private ComponentName mErrorReportReceiver;
+
+    /**
+     * Optional local handler to be invoked in the process crash.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private Runnable mCrashHandler;
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isBad() {
+        return mBad;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setBad(boolean bad) {
+        mBad = bad;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isCrashing() {
+        return mCrashing;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCrashing(boolean crashing) {
+        mCrashing = crashing;
+        mApp.getWindowProcessController().setCrashing(crashing);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isForceCrashReport() {
+        return mForceCrashReport;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setForceCrashReport(boolean forceCrashReport) {
+        mForceCrashReport = forceCrashReport;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isNotResponding() {
+        return mNotResponding;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setNotResponding(boolean notResponding) {
+        mNotResponding = notResponding;
+        mApp.getWindowProcessController().setNotResponding(notResponding);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    Runnable getCrashHandler() {
+        return mCrashHandler;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCrashHandler(Runnable crashHandler) {
+        mCrashHandler = crashHandler;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ActivityManager.ProcessErrorStateInfo getCrashingReport() {
+        return mCrashingReport;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCrashingReport(ActivityManager.ProcessErrorStateInfo crashingReport) {
+        mCrashingReport = crashingReport;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ActivityManager.ProcessErrorStateInfo getNotRespondingReport() {
+        return mNotRespondingReport;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setNotRespondingReport(ActivityManager.ProcessErrorStateInfo notRespondingReport) {
+        mNotRespondingReport = notRespondingReport;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ComponentName getErrorReportReceiver() {
+        return mErrorReportReceiver;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setErrorReportReceiver(ComponentName errorReportReceiver) {
+        mErrorReportReceiver = errorReportReceiver;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ErrorDialogController getDialogController() {
+        return mDialogController;
+    }
+
+    ProcessErrorStateRecord(ProcessRecord app) {
+        mApp = app;
+        mService = app.mService;
+        mProcLock = mService.mProcLock;
+        mDialogController = new ErrorDialogController(app);
+    }
+
+    void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
+            String parentShortComponentName, WindowProcessController parentProcess,
+            boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
+        ArrayList<Integer> firstPids = new ArrayList<>(5);
+        SparseArray<Boolean> lastPids = new SparseArray<>(20);
+
+        mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
+            synchronized (mService) {
+                mApp.killLocked("anr", ApplicationExitInfo.REASON_ANR, true);
+            }
+        });
+
+        long anrTime = SystemClock.uptimeMillis();
+        if (isMonitorCpuUsage()) {
+            mService.updateCpuStatsNow();
+        }
+
+        final boolean isSilentAnr;
+        final int pid = mApp.getPid();
+        synchronized (mService) {
+            // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
+            if (mService.mAtmInternal.isShuttingDown()) {
+                Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
+                return;
+            } else if (isNotResponding()) {
+                Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
+                return;
+            } else if (isCrashing()) {
+                Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
+                return;
+            } else if (mApp.isKilledByAm()) {
+                Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
+                return;
+            } else if (mApp.isKilled()) {
+                Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
+                return;
+            }
+
+            // In case we come through here for the same app before completing
+            // this one, mark as anring now so we will bail out.
+            synchronized (mProcLock) {
+                setNotResponding(true);
+            }
+
+            // Log the ANR to the event log.
+            EventLog.writeEvent(EventLogTags.AM_ANR, mApp.userId, pid, mApp.processName,
+                    mApp.info.flags, annotation);
+
+            // Dump thread traces as quickly as we can, starting with "interesting" processes.
+            firstPids.add(pid);
+
+            // Don't dump other PIDs if it's a background ANR or is requested to only dump self.
+            isSilentAnr = isSilentAnr();
+            if (!isSilentAnr && !onlyDumpSelf) {
+                int parentPid = pid;
+                if (parentProcess != null && parentProcess.getPid() > 0) {
+                    parentPid = parentProcess.getPid();
+                }
+                if (parentPid != pid) firstPids.add(parentPid);
+
+                if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
+
+                final int ppid = parentPid;
+                mService.mProcessList.forEachLruProcessesLOSP(false, r -> {
+                    if (r != null && r.getThread() != null) {
+                        int myPid = r.getPid();
+                        if (myPid > 0 && myPid != pid && myPid != ppid && myPid != MY_PID) {
+                            if (r.isPersistent()) {
+                                firstPids.add(myPid);
+                                if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
+                            } else if (r.mServices.isTreatedLikeActivity()) {
+                                firstPids.add(myPid);
+                                if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
+                            } else {
+                                lastPids.put(myPid, Boolean.TRUE);
+                                if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
+                            }
+                        }
+                    }
+                });
+            }
+        }
+
+        // Check if package is still being loaded
+        boolean isPackageLoading = false;
+        final PackageManagerInternal packageManagerInternal = mService.getPackageManagerInternal();
+        if (aInfo != null && aInfo.packageName != null) {
+            IncrementalStatesInfo incrementalStatesInfo =
+                    packageManagerInternal.getIncrementalStatesInfo(
+                            aInfo.packageName, mApp.uid, mApp.userId);
+            if (incrementalStatesInfo != null) {
+                isPackageLoading = incrementalStatesInfo.isLoading();
+            }
+        }
+
+        // Log the ANR to the main log.
+        StringBuilder info = new StringBuilder();
+        info.setLength(0);
+        info.append("ANR in ").append(mApp.processName);
+        if (activityShortComponentName != null) {
+            info.append(" (").append(activityShortComponentName).append(")");
+        }
+        info.append("\n");
+        info.append("PID: ").append(pid).append("\n");
+        if (annotation != null) {
+            info.append("Reason: ").append(annotation).append("\n");
+        }
+        if (parentShortComponentName != null
+                && parentShortComponentName.equals(activityShortComponentName)) {
+            info.append("Parent: ").append(parentShortComponentName).append("\n");
+        }
+
+        if (isPackageLoading) {
+            // Report in the main log that the package is still loading
+            final float loadingProgress = packageManagerInternal.getIncrementalStatesInfo(
+                    aInfo.packageName, mApp.uid, mApp.userId).getProgress();
+            info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n");
+        }
+
+        StringBuilder report = new StringBuilder();
+        report.append(MemoryPressureUtil.currentPsiState());
+        ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
+
+        // don't dump native PIDs for background ANRs unless it is the process of interest
+        String[] nativeProcs = null;
+        if (isSilentAnr || onlyDumpSelf) {
+            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
+                if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
+                    nativeProcs = new String[] { mApp.processName };
+                    break;
+                }
+            }
+        } else {
+            nativeProcs = NATIVE_STACKS_OF_INTEREST;
+        }
+
+        int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
+        ArrayList<Integer> nativePids = null;
+
+        if (pids != null) {
+            nativePids = new ArrayList<>(pids.length);
+            for (int i : pids) {
+                nativePids.add(i);
+            }
+        }
+
+        // For background ANRs, don't pass the ProcessCpuTracker to
+        // avoid spending 1/2 second collecting stats to rank lastPids.
+        StringWriter tracesFileException = new StringWriter();
+        // To hold the start and end offset to the ANR trace file respectively.
+        final long[] offsets = new long[2];
+        File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+                isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
+                nativePids, tracesFileException, offsets);
+
+        if (isMonitorCpuUsage()) {
+            mService.updateCpuStatsNow();
+            mService.mAppProfiler.printCurrentCpuState(report, anrTime);
+            info.append(processCpuTracker.printCurrentLoad());
+            info.append(report);
+        }
+        report.append(tracesFileException.getBuffer());
+
+        info.append(processCpuTracker.printCurrentState(anrTime));
+
+        Slog.e(TAG, info.toString());
+        if (tracesFile == null) {
+            // There is no trace file, so dump (only) the alleged culprit's threads to the log
+            Process.sendSignal(pid, Process.SIGNAL_QUIT);
+        } else if (offsets[1] > 0) {
+            // We've dumped into the trace file successfully
+            mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace(
+                    pid, mApp.uid, mApp.getPackageList(), tracesFile, offsets[0], offsets[1]);
+        }
+
+        FrameworkStatsLog.write(FrameworkStatsLog.ANR_OCCURRED, mApp.uid, mApp.processName,
+                activityShortComponentName == null ? "unknown" : activityShortComponentName,
+                annotation,
+                (mApp.info != null) ? (mApp.info.isInstantApp()
+                        ? FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__TRUE
+                        : FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__FALSE)
+                        : FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__UNAVAILABLE,
+                mApp.isInterestingToUserLocked()
+                        ? FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND
+                        : FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND,
+                mApp.getProcessClassEnum(),
+                (mApp.info != null) ? mApp.info.packageName : "", isPackageLoading);
+        final ProcessRecord parentPr = parentProcess != null
+                ? (ProcessRecord) parentProcess.mOwner : null;
+        mService.addErrorToDropBox("anr", mApp, mApp.processName, activityShortComponentName,
+                parentShortComponentName, parentPr, annotation, report.toString(), tracesFile,
+                null);
+
+        if (mApp.getWindowProcessController().appNotResponding(info.toString(),
+                () -> {
+                    synchronized (mService) {
+                        mApp.killLocked("anr", ApplicationExitInfo.REASON_ANR, true);
+                    }
+                },
+                () -> {
+                    synchronized (mService) {
+                        mService.mServices.scheduleServiceTimeoutLocked(mApp);
+                    }
+                })) {
+            return;
+        }
+
+        // Retrieve max ANR delay from AnrControllers without the mService lock since the
+        // controllers might in turn call into apps
+        long anrDialogDelayMs = mService.mActivityTaskManager.getMaxAnrDelayMillis(aInfo);
+        if (aInfo != null && aInfo.packageName != null && anrDialogDelayMs > 0) {
+            Slog.i(TAG, "Delaying ANR dialog for " + aInfo.packageName + " for " + anrDialogDelayMs
+                    + "ms");
+        }
+
+        synchronized (mService) {
+            // mBatteryStatsService can be null if the AMS is constructed with injector only. This
+            // will only happen in tests.
+            if (mService.mBatteryStatsService != null) {
+                mService.mBatteryStatsService.noteProcessAnr(mApp.processName, mApp.uid);
+            }
+
+            if (isSilentAnr() && !mApp.isDebugging()) {
+                mApp.killLocked("bg anr", ApplicationExitInfo.REASON_ANR, true);
+                return;
+            }
+
+            synchronized (mProcLock) {
+                // Set the app's notResponding state, and look up the errorReportReceiver
+                makeAppNotRespondingLSP(activityShortComponentName,
+                        annotation != null ? "ANR " + annotation : "ANR", info.toString());
+            }
+
+            // Notify package manager service to possibly update package state
+            if (aInfo != null && aInfo.packageName != null) {
+                packageManagerInternal.notifyPackageCrashOrAnr(aInfo.packageName);
+            }
+
+            // mUiHandler can be null if the AMS is constructed with injector only. This will only
+            // happen in tests.
+            if (mService.mUiHandler != null) {
+                // Bring up the infamous App Not Responding dialog
+                Message msg = Message.obtain();
+                msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
+                msg.obj = new AppNotRespondingDialog.Data(mApp, aInfo, aboveSystem);
+
+                mService.mUiHandler.sendMessageDelayed(msg, anrDialogDelayMs);
+            }
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void makeAppNotRespondingLSP(String activity, String shortMsg, String longMsg) {
+        setNotResponding(true);
+        // mAppErrors can be null if the AMS is constructed with injector only. This will only
+        // happen in tests.
+        if (mService.mAppErrors != null) {
+            mNotRespondingReport = mService.mAppErrors.generateProcessError(mApp,
+                    ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
+                    activity, shortMsg, longMsg, null);
+        }
+        startAppProblemLSP();
+        mApp.getWindowProcessController().stopFreezingActivities();
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void startAppProblemLSP() {
+        // If this app is not running under the current user, then we can't give it a report button
+        // because that would require launching the report UI under a different user.
+        mErrorReportReceiver = null;
+
+        for (int userId : mService.mUserController.getCurrentProfileIds()) {
+            if (mApp.userId == userId) {
+                mErrorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
+                        mService.mContext, mApp.info.packageName, mApp.info.flags);
+            }
+        }
+        mService.skipCurrentReceiverLocked(mApp);
+    }
+
+    @GuardedBy("mService")
+    private boolean isInterestingForBackgroundTraces() {
+        // The system_server is always considered interesting.
+        if (mApp.getPid() == MY_PID) {
+            return true;
+        }
+
+        // A package is considered interesting if any of the following is true :
+        //
+        // - It's displaying an activity.
+        // - It's the SystemUI.
+        // - It has an overlay or a top UI visible.
+        //
+        // NOTE: The check whether a given ProcessRecord belongs to the systemui
+        // process is a bit of a kludge, but the same pattern seems repeated at
+        // several places in the system server.
+        return mApp.isInterestingToUserLocked()
+                || (mApp.info != null && "com.android.systemui".equals(mApp.info.packageName))
+                || (mApp.mState.hasTopUi() || mApp.mState.hasOverlayUi());
+    }
+
+    private boolean getShowBackground() {
+        return Settings.Secure.getInt(mService.mContext.getContentResolver(),
+                Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
+    }
+
+    /**
+     * Unless configured otherwise, swallow ANRs in background processes & kill the process.
+     * Non-private access is for tests only.
+     */
+    @VisibleForTesting
+    @GuardedBy("mService")
+    boolean isSilentAnr() {
+        return !getShowBackground() && !isInterestingForBackgroundTraces();
+    }
+
+    /** Non-private access is for tests only. */
+    @VisibleForTesting
+    boolean isMonitorCpuUsage() {
+        return mService.mAppProfiler.MONITOR_CPU_USAGE;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void onCleanupApplicationRecordLSP() {
+        // Dismiss any open dialogs.
+        getDialogController().clearAllErrorDialogs();
+
+        setCrashing(false);
+        setNotResponding(false);
+    }
+
+    void dump(PrintWriter pw, String prefix, long nowUptime) {
+        synchronized (mProcLock) {
+            if (mCrashing || mDialogController.hasCrashDialogs() || mNotResponding
+                    || mDialogController.hasAnrDialogs() || mBad) {
+                pw.print(prefix);
+                pw.print(" mCrashing=" + mCrashing);
+                pw.print(" " + mDialogController.getCrashDialogs());
+                pw.print(" mNotResponding=" + mNotResponding);
+                pw.print(" " + mDialogController.getAnrDialogs());
+                pw.print(" bad=" + mBad);
+
+                // mCrashing or mNotResponding is always set before errorReportReceiver
+                if (mErrorReportReceiver != null) {
+                    pw.print(" errorReportReceiver=");
+                    pw.print(mErrorReportReceiver.flattenToShortString());
+                }
+                pw.println();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 2273779..0576345 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -34,10 +34,14 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESSES_CHANGED_UI_MSG;
+import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESS_DIED_UI_MSG;
 import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_DELAY_MS;
 import static com.android.server.am.ActivityManagerService.KILL_APP_ZYGOTE_MSG;
 import static com.android.server.am.ActivityManagerService.PERSISTENT_MASK;
@@ -51,6 +55,7 @@
 import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
 import static com.android.server.am.AppProfiler.TAG_PSS;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
@@ -59,6 +64,7 @@
 import android.app.ApplicationExitInfo.Reason;
 import android.app.ApplicationExitInfo.SubReason;
 import android.app.IApplicationThread;
+import android.app.IProcessObserver;
 import android.app.IUidObserver;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
@@ -86,6 +92,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.SystemClock;
@@ -97,18 +104,21 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.DebugUtils;
 import android.util.EventLog;
 import android.util.LongSparseArray;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
+import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ProcessMap;
-import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -117,6 +127,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
+import com.android.server.am.ActivityManagerService.ProcessChangeItem;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -134,10 +145,14 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * Activity manager code dealing with processes.
@@ -145,6 +160,8 @@
 public final class ProcessList {
     static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
 
+    static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
+
     // A system property to control if app data isolation is enabled.
     static final String ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
             "persist.zygote.app_data_isolation";
@@ -359,6 +376,13 @@
     private static final long NATIVE_MEMTAG_SYNC = 177438394; // This is a bug id.
 
     /**
+     * Enable automatic zero-initialization of native heap memory allocations.
+     */
+    @ChangeId
+    @Disabled
+    private static final long NATIVE_HEAP_ZERO_INIT = 178038272; // This is a bug id.
+
+    /**
      * Enable sampled memory bug detection in the app.
      * @see <a href="https://source.android.com/devices/tech/debug/gwp-asan">GWP-ASan</a>.
      */
@@ -416,7 +440,7 @@
 
     private boolean mVoldAppDataIsolationEnabled = false;
 
-    private ArrayList<String> mAppDataIsolationWhitelistedApps;
+    private ArrayList<String> mAppDataIsolationAllowlistedApps;
 
     /**
      * Temporary to avoid allocations.  Protected by main lock.
@@ -453,34 +477,41 @@
      * List of running applications, sorted by recent usage.
      * The first entry in the list is the least recently used.
      */
-    final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();
+    @CompositeRWLock({"mService", "mProcLock"})
+    private final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();
 
     /**
      * Where in mLruProcesses that the processes hosting activities start.
      */
-    int mLruProcessActivityStart = 0;
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mLruProcessActivityStart = 0;
 
     /**
      * Where in mLruProcesses that the processes hosting services start.
      * This is after (lower index) than mLruProcessesActivityStart.
      */
-    int mLruProcessServiceStart = 0;
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mLruProcessServiceStart = 0;
 
     /**
      * Current sequence id for process LRU updating.
      */
-    int mLruSeq = 0;
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mLruSeq = 0;
 
+    @CompositeRWLock({"mService", "mProcLock"})
     ActiveUids mActiveUids;
 
     /**
      * The currently running isolated processes.
      */
+    @GuardedBy("mService")
     final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<>();
 
     /**
      * The currently running application zygotes.
      */
+    @GuardedBy("mService")
     final ProcessMap<AppZygote> mAppZygotes = new ProcessMap<AppZygote>();
 
     /**
@@ -492,6 +523,7 @@
     /**
      * The processes that are forked off an application zygote.
      */
+    @GuardedBy("mService")
     final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses =
             new ArrayMap<AppZygote, ArrayList<ProcessRecord>>();
 
@@ -519,6 +551,8 @@
      */
     private final int[] mZygoteSigChldMessage = new int[3];
 
+    ActivityManagerGlobalLock mProcLock;
+
     final class IsolatedUidRange {
         @VisibleForTesting
         public final int mFirstUid;
@@ -627,6 +661,7 @@
      * The available isolated UIDs for processes that are not spawned from an application zygote.
      */
     @VisibleForTesting
+    @GuardedBy("mService")
     IsolatedUidRange mGlobalIsolatedUids = new IsolatedUidRange(Process.FIRST_ISOLATED_UID,
             Process.LAST_ISOLATED_UID);
 
@@ -634,6 +669,7 @@
      * An allocator for isolated UID ranges for apps that use an application zygote.
      */
     @VisibleForTesting
+    @GuardedBy("mService")
     IsolatedUidRangeAllocator mAppIsolatedUidRangeAllocator =
             new IsolatedUidRangeAllocator(Process.FIRST_APP_ZYGOTE_ISOLATED_UID,
                     Process.LAST_APP_ZYGOTE_ISOLATED_UID, Process.NUM_UIDS_PER_APP_ZYGOTE);
@@ -641,15 +677,35 @@
     /**
      * Processes that are being forcibly torn down.
      */
+    @GuardedBy("mService")
     final ArrayList<ProcessRecord> mRemovedProcesses = new ArrayList<ProcessRecord>();
 
+    // Self locked with the inner lock within the RemoteCallbackList
+    private final RemoteCallbackList<IProcessObserver> mProcessObservers =
+            new RemoteCallbackList<>();
+
+    // No lock is needed as it's accessed from single thread only
+    private ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
+
+    @GuardedBy("mProcessChangeLock")
+    private final ArrayList<ProcessChangeItem> mPendingProcessChanges = new ArrayList<>();
+
+    @GuardedBy("mProcessChangeLock")
+    final ArrayList<ProcessChangeItem> mAvailProcessChanges = new ArrayList<>();
+
+    /**
+     * A dedicated lock for dispatching the process changes as it occurs frequently
+     */
+    private final Object mProcessChangeLock = new Object();
+
     /**
      * All of the applications we currently have running organized by name.
      * The keys are strings of the application package name (as
      * returned by the package manager), and the keys are ApplicationRecord
      * objects.
      */
-    final MyProcessMap mProcessNames = new MyProcessMap();
+    @CompositeRWLock({"mService", "mProcLock"})
+    private final MyProcessMap mProcessNames = new MyProcessMap();
 
     final class MyProcessMap extends ProcessMap<ProcessRecord> {
         @Override
@@ -717,13 +773,14 @@
         mService = service;
         mActiveUids = activeUids;
         mPlatformCompat = platformCompat;
+        mProcLock = service.mProcLock;
         // Get this after boot, and won't be changed until it's rebooted, as we don't
         // want some apps enabled while some apps disabled
         mAppDataIsolationEnabled =
                 SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
         mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
                 ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
-        mAppDataIsolationWhitelistedApps = new ArrayList<>(
+        mAppDataIsolationAllowlistedApps = new ArrayList<>(
                 SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
 
         if (sKillHandler == null) {
@@ -810,13 +867,13 @@
      */
     Map<Integer, String> getProcessesWithPendingBindMounts(int userId) {
         final Map<Integer, String> pidPackageMap = new HashMap<>();
-        synchronized (mService) {
+        synchronized (mProcLock) {
             for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
                 final ProcessRecord record = mLruProcesses.get(i);
-                if (record.userId != userId || !record.bindMountPending) {
+                if (record.userId != userId || !record.isBindMountPending()) {
                     continue;
                 }
-                final int pid = record.pid;
+                final int pid = record.getPid();
                 // It can happen when app process is starting, but zygote work is not done yet so
                 // system does not this pid record yet.
                 if (pid == 0) {
@@ -825,8 +882,8 @@
                 }
                 pidPackageMap.put(pid, record.info.packageName);
             }
-            return pidPackageMap;
         }
+        return pidPackageMap;
     }
 
     private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
@@ -1502,6 +1559,7 @@
         }
     }
 
+    @GuardedBy("mService")
     final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean
             keepIfLarge) {
         if (uid == SYSTEM_UID) {
@@ -1522,48 +1580,33 @@
         }
         ProcessRecord proc = mProcessNames.get(processName, uid);
         if (false && proc != null && !keepIfLarge
-                && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY
-                && proc.lastCachedPss >= 4000) {
+                && proc.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_CACHED_EMPTY
+                && proc.mProfile.getLastCachedPss() >= 4000) {
             // Turn this condition on to cause killing to happen regularly, for testing.
-            synchronized (mService.mProcessStats.mLock) {
-                if (proc.baseProcessTracker != null) {
-                    proc.baseProcessTracker.reportCachedKill(
-                            proc.pkgList.mPkgList, proc.lastCachedPss);
-                    for (int ipkg = proc.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                        ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
-                        FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED,
-                                proc.info.uid,
-                                holder.state.getName(),
-                                holder.state.getPackage(),
-                                proc.lastCachedPss, holder.appVersion);
-                    }
-                }
+            synchronized (mService.mAppProfiler.mProfilerLock) {
+                proc.mProfile.reportCachedKill();
             }
-            proc.kill(Long.toString(proc.lastCachedPss) + "k from cached",
+            proc.killLocked(Long.toString(proc.mProfile.getLastCachedPss()) + "k from cached",
                     ApplicationExitInfo.REASON_OTHER,
                     ApplicationExitInfo.SUBREASON_LARGE_CACHED,
                     true);
         } else if (proc != null && !keepIfLarge
                 && !mService.mAppProfiler.isLastMemoryLevelNormal()
-                && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
-            if (DEBUG_PSS) Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + proc
-                    .lastCachedPss);
-            if (proc.lastCachedPss >= getCachedRestoreThresholdKb()) {
-                synchronized (mService.mProcessStats.mLock) {
-                    if (proc.baseProcessTracker != null) {
-                        proc.baseProcessTracker.reportCachedKill(proc.pkgList.mPkgList,
-                                proc.lastCachedPss);
-                        for (int ipkg = proc.pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                            ProcessStats.ProcessStateHolder holder = proc.pkgList.valueAt(ipkg);
-                            FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED,
-                                    proc.info.uid,
-                                    holder.state.getName(),
-                                    holder.state.getPackage(),
-                                    proc.lastCachedPss, holder.appVersion);
-                        }
-                    }
+                && proc.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
+            final long lastCachedPss;
+            boolean doKilling = false;
+            synchronized (mService.mAppProfiler.mProfilerLock) {
+                lastCachedPss = proc.mProfile.getLastCachedPss();
+                if (lastCachedPss >= getCachedRestoreThresholdKb()) {
+                    proc.mProfile.reportCachedKill();
+                    doKilling = true;
                 }
-                proc.kill(Long.toString(proc.lastCachedPss) + "k from cached",
+            }
+            if (DEBUG_PSS) {
+                Slog.d(TAG_PSS, "May not keep " + proc + ": pss=" + lastCachedPss);
+            }
+            if (doKilling) {
+                proc.killLocked(Long.toString(lastCachedPss) + "k from cached",
                         ApplicationExitInfo.REASON_OTHER,
                         ApplicationExitInfo.SUBREASON_LARGE_CACHED,
                         true);
@@ -1585,14 +1628,16 @@
         outInfo.foregroundAppThreshold = getMemLevel(FOREGROUND_APP_ADJ);
     }
 
-    ProcessRecord findAppProcessLocked(IBinder app, String reason) {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ProcessRecord findAppProcessLOSP(IBinder app, String reason) {
         final int NP = mProcessNames.getMap().size();
         for (int ip = 0; ip < NP; ip++) {
             SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
             final int NA = apps.size();
             for (int ia = 0; ia < NA; ia++) {
                 ProcessRecord p = apps.valueAt(ia);
-                if (p.thread != null && p.thread.asBinder() == app) {
+                final IApplicationThread thread = p.getThread();
+                if (thread != null && thread.asBinder() == app) {
                     return p;
                 }
             }
@@ -1666,12 +1711,28 @@
         return gidArray;
     }
 
-    // Returns the memory tagging level to be enabled. If memory tagging isn't
-    // requested, returns zero.
-    private int getMemtagLevel(ProcessRecord app) {
-        // Ensure the hardware + kernel actually supports MTE.
-        if (!Zygote.nativeSupportsMemoryTagging()) {
-            return 0;
+    private int memtagModeToZygoteMemtagLevel(int memtagMode) {
+        switch (memtagMode) {
+            case ApplicationInfo.MEMTAG_ASYNC:
+                return Zygote.MEMORY_TAG_LEVEL_ASYNC;
+            case ApplicationInfo.MEMTAG_SYNC:
+                return Zygote.MEMORY_TAG_LEVEL_SYNC;
+            default:
+                return Zygote.MEMORY_TAG_LEVEL_NONE;
+        }
+    }
+
+    // Returns the requested memory tagging level.
+    private int getRequestedMemtagLevel(ProcessRecord app) {
+        // Look at the process attribute first.
+        if (app.processInfo != null
+                && app.processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
+            return memtagModeToZygoteMemtagLevel(app.processInfo.memtagMode);
+        }
+
+        // Then at the application attribute.
+        if (app.info.getMemtagMode() != ApplicationInfo.MEMTAG_DEFAULT) {
+            return memtagModeToZygoteMemtagLevel(app.info.getMemtagMode());
         }
 
         if (mPlatformCompat.isChangeEnabled(NATIVE_MEMTAG_SYNC, app.info)) {
@@ -1682,40 +1743,43 @@
             return Zygote.MEMORY_TAG_LEVEL_ASYNC;
         }
 
-        return 0;
-    }
-
-    private boolean shouldEnableTaggedPointers(ProcessRecord app) {
-        // Ensure we have platform + kernel support for TBI.
-        if (!Zygote.nativeSupportsTaggedPointers()) {
-            return false;
-        }
-
         // Check to ensure the app hasn't explicitly opted-out of TBI via. the manifest attribute.
         if (!app.info.allowsNativeHeapPointerTagging()) {
-            return false;
+            return Zygote.MEMORY_TAG_LEVEL_NONE;
         }
 
         // Check to see that the compat feature for TBI is enabled.
-        if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private int decideTaggingLevel(ProcessRecord app) {
-        // Check MTE support first, as it should take precedence over TBI.
-        int memtagLevel = getMemtagLevel(app);
-        if (memtagLevel != 0) {
-            return memtagLevel;
-        }
-
-        if (shouldEnableTaggedPointers(app)) {
+        if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
             return Zygote.MEMORY_TAG_LEVEL_TBI;
         }
 
-        return 0;
+        return Zygote.MEMORY_TAG_LEVEL_NONE;
+    }
+
+    private int decideTaggingLevel(ProcessRecord app) {
+        // Get the desired tagging level (app manifest + compat features).
+        int level = getRequestedMemtagLevel(app);
+
+        // Take into account the hardware capabilities.
+        if (Zygote.nativeSupportsMemoryTagging()) {
+            // MTE devices can not do TBI, because the Zygote process already has live MTE
+            // allocations. Downgrade TBI to NONE.
+            if (level == Zygote.MEMORY_TAG_LEVEL_TBI) {
+                level = Zygote.MEMORY_TAG_LEVEL_NONE;
+            }
+        } else if (Zygote.nativeSupportsTaggedPointers()) {
+            // TBI-but-not-MTE devices downgrade MTE modes to TBI.
+            // The idea is that if an app opts into full hardware tagging (MTE), it must be ok with
+            // the "fake" pointer tagging (TBI).
+            if (level == Zygote.MEMORY_TAG_LEVEL_ASYNC || level == Zygote.MEMORY_TAG_LEVEL_SYNC) {
+                level = Zygote.MEMORY_TAG_LEVEL_TBI;
+            }
+        } else {
+            // Otherwise disable all tagging.
+            level = Zygote.MEMORY_TAG_LEVEL_NONE;
+        }
+
+        return level;
     }
 
     private int decideGwpAsanLevel(ProcessRecord app) {
@@ -1726,7 +1790,7 @@
                     ? Zygote.GWP_ASAN_LEVEL_ALWAYS
                     : Zygote.GWP_ASAN_LEVEL_NEVER;
         }
-        // Then at the applicaton attribute.
+        // Then at the application attribute.
         if (app.info.getGwpAsanMode() != ApplicationInfo.GWP_ASAN_DEFAULT) {
             return app.info.getGwpAsanMode() == ApplicationInfo.GWP_ASAN_ALWAYS
                     ? Zygote.GWP_ASAN_LEVEL_ALWAYS
@@ -1743,25 +1807,40 @@
         return Zygote.GWP_ASAN_LEVEL_NEVER;
     }
 
+    private boolean enableNativeHeapZeroInit(ProcessRecord app) {
+        // Look at the process attribute first.
+        if (app.processInfo != null && app.processInfo.nativeHeapZeroInit != null) {
+            return app.processInfo.nativeHeapZeroInit;
+        }
+        // Then at the application attribute.
+        if (app.info.isNativeHeapZeroInit() != null) {
+            return app.info.isNativeHeapZeroInit();
+        }
+        // Compat feature last.
+        if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_ZERO_INIT, app.info)) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * @return {@code true} if process start is successful, false otherwise.
      */
     @GuardedBy("mService")
     boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
-            int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
-            String abiOverride) {
-        if (app.pendingStart) {
+            int zygotePolicyFlags, boolean disableHiddenApiChecks, String abiOverride) {
+        if (app.isPendingStart()) {
             return true;
         }
         long startTime = SystemClock.uptimeMillis();
-        if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) {
+        if (app.getPid() > 0 && app.getPid() != ActivityManagerService.MY_PID) {
             checkSlow(startTime, "startProcess: removing from pids map");
-            mService.removePidLocked(app);
-            app.bindMountPending = false;
+            mService.removePidLocked(app.getPid(), app);
+            app.setBindMountPending(false);
             mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
             checkSlow(startTime, "startProcess: done removing from pids map");
             app.setPid(0);
-            app.startSeq = 0;
+            app.setStartSeq(0);
         }
 
         if (DEBUG_PROCESSES && mService.mProcessesOnHold.contains(app)) Slog.v(
@@ -1770,7 +1849,7 @@
         mService.mProcessesOnHold.remove(app);
 
         checkSlow(startTime, "startProcess: starting to update cpu stats");
-        mService.updateCpuStatsLocked();
+        mService.updateCpuStats();
         checkSlow(startTime, "startProcess: done updating cpu stats");
 
         try {
@@ -1816,7 +1895,7 @@
 
                 gids = computeGidsForProcess(mountExternal, uid, permGids);
             }
-            app.mountMode = mountExternal;
+            app.setMountMode(mountExternal);
             checkSlow(startTime, "startProcess: building args");
             if (mService.mAtmInternal.isFactoryTestProcess(app.getWindowProcessController())) {
                 uid = 0;
@@ -1873,10 +1952,16 @@
                 mService.mNativeDebuggingApp = null;
             }
 
-            if (app.info.isEmbeddedDexUsed()
-                    || (app.info.isPrivilegedApp()
-                        && DexManager.isPackageSelectedToRunOob(app.pkgList.mPkgList.keySet()))) {
+            if (app.info.isEmbeddedDexUsed()) {
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
+            } else if (app.info.isPrivilegedApp()) {
+                final PackageList pkgList = app.getPkgList();
+                synchronized (pkgList) {
+                    if (DexManager.isPackageSelectedToRunOob(
+                            pkgList.getPackageListLocked().keySet())) {
+                        runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
+                    }
+                }
             }
 
             if (!disableHiddenApiChecks && !mService.mHiddenApiBlacklist.isDisabled()) {
@@ -1889,10 +1974,6 @@
                     throw new IllegalStateException("Invalid API policy: " + policy);
                 }
                 runtimeFlags |= policyBits;
-
-                if (disableTestApiChecks) {
-                    runtimeFlags |= Zygote.DISABLE_TEST_API_ENFORCEMENT_POLICY;
-                }
             }
 
             String useAppImageCache = SystemProperties.get(
@@ -1930,9 +2011,9 @@
                 instructionSet = VMRuntime.getInstructionSet(requiredAbi);
             }
 
-            app.gids = gids;
+            app.setGids(gids);
             app.setRequiredAbi(requiredAbi);
-            app.instructionSet = instructionSet;
+            app.setInstructionSet(instructionSet);
 
             // If instructionSet is non-null, this indicates that the system_server is spawning a
             // process with an ISA that may be different from its own. System (kernel and hardware)
@@ -1948,6 +2029,10 @@
                 runtimeFlags |= decideTaggingLevel(app);
             }
 
+            if (enableNativeHeapZeroInit(app)) {
+                runtimeFlags |= Zygote.NATIVE_HEAP_ZERO_INIT;
+            }
+
             // the per-user SELinux context must be set
             if (TextUtils.isEmpty(app.info.seInfoUser)) {
                 Slog.wtf(ActivityManagerService.TAG, "SELinux tag not defined",
@@ -1983,23 +2068,26 @@
             int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
             String seInfo, String requiredAbi, String instructionSet, String invokeWith,
             long startTime) {
-        app.pendingStart = true;
-        app.killedByAm = false;
-        app.removed = false;
-        app.killed = false;
-        if (app.startSeq != 0) {
-            Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
-                    + " with non-zero startSeq:" + app.startSeq);
+        app.setPendingStart(true);
+        app.setRemoved(false);
+        synchronized (mProcLock) {
+            app.setKilledByAm(false);
+            app.setKilled(false);
         }
-        if (app.pid != 0) {
+        if (app.getStartSeq() != 0) {
             Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
-                    + " with non-zero pid:" + app.pid);
+                    + " with non-zero startSeq:" + app.getStartSeq());
         }
-        app.mDisabledCompatChanges = null;
+        if (app.getPid() != 0) {
+            Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
+                    + " with non-zero pid:" + app.getPid());
+        }
+        app.setDisabledCompatChanges(null);
         if (mPlatformCompat != null) {
-            app.mDisabledCompatChanges = mPlatformCompat.getDisabledChanges(app.info);
+            app.setDisabledCompatChanges(mPlatformCompat.getDisabledChanges(app.info));
         }
-        final long startSeq = app.startSeq = ++mProcStartSeqCounter;
+        final long startSeq = ++mProcStartSeqCounter;
+        app.setStartSeq(startSeq);
         app.setStartParams(uid, hostingRecord, seInfo, startTime);
         app.setUsingWrapper(invokeWith != null
                 || Zygote.getWrapProperty(app.processName) != null);
@@ -2023,11 +2111,11 @@
             } catch (RuntimeException e) {
                 Slog.e(ActivityManagerService.TAG, "Failure starting process "
                         + app.processName, e);
-                app.pendingStart = false;
+                app.setPendingStart(false);
                 mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
                         false, false, true, false, false, app.userId, "start failure");
             }
-            return app.pid > 0;
+            return app.getPid() > 0;
         }
     }
 
@@ -2040,11 +2128,11 @@
             final int[] gids, final int runtimeFlags, int zygotePolicyFlags,
             final int mountExternal, final String requiredAbi, final String instructionSet,
             final String invokeWith, final long startSeq) {
-        // If there is a precede instance of the process, wait for its death with a timeout.
+        // If there is a preceding instance of the process, wait for its death with a timeout.
         // Use local reference since we are not using locks here
-        final ProcessRecord precedence = app.mPrecedence;
-        if (precedence != null) {
-            final int pid = precedence.pid;
+        final ProcessRecord predecessor = app.mPredecessor;
+        if (predecessor != null) {
+            final int pid = predecessor.getPid();
             long now = System.currentTimeMillis();
             final long end = now + PROC_KILL_TIMEOUT;
             final int oldPolicy = StrictMode.getThreadPolicyMask();
@@ -2052,34 +2140,34 @@
                 StrictMode.setThreadPolicyMask(0);
                 Process.waitForProcessDeath(pid, PROC_KILL_TIMEOUT);
                 // It's killed successfully, but we'd make sure the cleanup work is done.
-                synchronized (precedence) {
-                    if (app.mPrecedence != null) {
+                synchronized (predecessor) {
+                    if (app.mPredecessor != null) {
                         now = System.currentTimeMillis();
                         if (now < end) {
                             try {
-                                precedence.wait(end - now);
+                                predecessor.wait(end - now);
                             } catch (InterruptedException e) {
                             }
                         }
                     }
-                    if (app.mPrecedence != null) {
+                    if (app.mPredecessor != null) {
                         // The cleanup work hasn't be done yet, let's log it and continue.
-                        Slog.w(TAG, precedence + " has died, but its cleanup isn't done");
+                        Slog.w(TAG, predecessor + " has died, but its cleanup isn't done");
                     }
                 }
             } catch (Exception e) {
                 // It's still alive... maybe blocked at uninterruptible sleep ?
-                Slog.wtf(TAG, precedence.toString() + " refused to die, but we need to launch "
+                Slog.wtf(TAG, predecessor.toString() + " refused to die, but we need to launch "
                         + app, e);
             } finally {
                 StrictMode.setThreadPolicyMask(oldPolicy);
             }
         }
         try {
-            final Process.ProcessStartResult startResult = startProcess(app.hostingRecord,
-                    entryPoint, app, app.startUid, gids, runtimeFlags, zygotePolicyFlags,
-                    mountExternal, app.seInfo, requiredAbi, instructionSet, invokeWith,
-                    app.startTime);
+            final Process.ProcessStartResult startResult = startProcess(app.getHostingRecord(),
+                    entryPoint, app, app.getStartUid(), gids, runtimeFlags, zygotePolicyFlags,
+                    mountExternal, app.getSeInfo(), requiredAbi, instructionSet, invokeWith,
+                    app.getStartTime());
 
             synchronized (mService) {
                 handleProcessStartedLocked(app, startResult, startSeq);
@@ -2089,7 +2177,7 @@
                 Slog.e(ActivityManagerService.TAG, "Failure starting process "
                         + app.processName, e);
                 mPendingStarts.remove(startSeq);
-                app.pendingStart = false;
+                app.setPendingStart(false);
                 mService.forceStopPackageLocked(app.info.packageName,
                         UserHandle.getAppId(app.uid),
                         false, false, true, false, false, app.userId, "start failure");
@@ -2115,19 +2203,19 @@
         // Free the isolated uid for this process
         final IsolatedUidRange appUidRange =
                 mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(app.info.processName,
-                        app.hostingRecord.getDefiningUid());
+                        app.getHostingRecord().getDefiningUid());
         if (appUidRange != null) {
             appUidRange.freeIsolatedUidLocked(app.uid);
         }
 
         final AppZygote appZygote = mAppZygotes.get(app.info.processName,
-                app.hostingRecord.getDefiningUid());
+                app.getHostingRecord().getDefiningUid());
         if (appZygote != null) {
             ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote);
             zygoteProcesses.remove(app);
             if (zygoteProcesses.size() == 0) {
                 mService.mHandler.removeMessages(KILL_APP_ZYGOTE_MSG);
-                if (app.removed) {
+                if (app.isRemoved()) {
                     // If we stopped this process because the package hosting it was removed,
                     // there's no point in delaying the app zygote kill.
                     killAppZygoteIfNeededLocked(appZygote, false /* force */);
@@ -2144,7 +2232,7 @@
         synchronized (mService) {
             // The UID for the app zygote should be the UID of the application hosting
             // the service.
-            final int uid = app.hostingRecord.getDefiningUid();
+            final int uid = app.getHostingRecord().getDefiningUid();
             AppZygote appZygote = mAppZygotes.get(app.info.processName, uid);
             final ArrayList<ProcessRecord> zygoteProcessList;
             if (appZygote == null) {
@@ -2153,7 +2241,7 @@
                 }
                 final IsolatedUidRange uidRange =
                         mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(
-                                app.info.processName, app.hostingRecord.getDefiningUid());
+                                app.info.processName, app.getHostingRecord().getDefiningUid());
                 final int userId = UserHandle.getUserId(uid);
                 // Create the app-zygote and provide it with the UID-range it's allowed
                 // to setresuid/setresgid to.
@@ -2166,7 +2254,7 @@
                 // packageName and uid of the defining application. This is because the
                 // preloading only makes sense in the context of the defining application,
                 // not the calling one.
-                appInfo.packageName = app.hostingRecord.getDefiningPackageName();
+                appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
                 appInfo.uid = uid;
                 appZygote = new AppZygote(appInfo, uid, firstUid, lastUid);
                 mAppZygotes.put(app.info.processName, uid, appZygote);
@@ -2213,14 +2301,15 @@
 
     private boolean needsStorageDataIsolation(StorageManagerInternal storageManagerInternal,
             ProcessRecord app) {
+        final int mountMode = app.getMountMode();
         return mVoldAppDataIsolationEnabled && UserHandle.isApp(app.uid)
                 && !storageManagerInternal.isExternalStorageService(app.uid)
                 // Special mounting mode doesn't need to have data isolation as they won't
                 // access /mnt/user anyway.
-                && app.mountMode != Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
-                && app.mountMode != Zygote.MOUNT_EXTERNAL_PASS_THROUGH
-                && app.mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER
-                && app.mountMode != Zygote.MOUNT_EXTERNAL_NONE;
+                && mountMode != Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
+                && mountMode != Zygote.MOUNT_EXTERNAL_PASS_THROUGH
+                && mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER
+                && mountMode != Zygote.MOUNT_EXTERNAL_NONE;
     }
 
     private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
@@ -2236,11 +2325,11 @@
                 // Use has-foreground-activities as a temporary hint so the current scheduling
                 // group won't be lost when the process is attaching. The actual state will be
                 // refreshed when computing oom-adj.
-                app.setHasForegroundActivities(true);
+                app.mState.setHasForegroundActivities(true);
             }
 
             Map<String, Pair<String, Long>> pkgDataInfoMap;
-            Map<String, Pair<String, Long>> whitelistedAppDataInfoMap;
+            Map<String, Pair<String, Long>> allowlistedAppDataInfoMap;
             boolean bindMountAppStorageDirs = false;
             boolean bindMountAppsData = mAppDataIsolationEnabled
                     && (UserHandle.isApp(app.uid) || UserHandle.isIsolated(app.uid))
@@ -2248,7 +2337,7 @@
 
             // Get all packages belongs to the same shared uid. sharedPackages is empty array
             // if it doesn't have shared uid.
-            final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
+            final PackageManagerInternal pmInt = mService.getPackageManagerInternal();
             final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
                     app.info.packageName, app.userId);
             final String[] targetPackagesList = sharedPackages.length == 0
@@ -2261,16 +2350,16 @@
                 bindMountAppsData = false;
             }
 
-            // Remove all packages in pkgDataInfoMap from mAppDataIsolationWhitelistedApps, so
+            // Remove all packages in pkgDataInfoMap from mAppDataIsolationAllowlistedApps, so
             // it won't be mounted twice.
-            final Set<String> whitelistedApps = new ArraySet<>(mAppDataIsolationWhitelistedApps);
+            final Set<String> allowlistedApps = new ArraySet<>(mAppDataIsolationAllowlistedApps);
             for (String pkg : targetPackagesList) {
-                whitelistedApps.remove(pkg);
+                allowlistedApps.remove(pkg);
             }
 
-            whitelistedAppDataInfoMap = getPackageAppDataInfoMap(pmInt,
-                    whitelistedApps.toArray(new String[0]), uid);
-            if (whitelistedAppDataInfoMap == null) {
+            allowlistedAppDataInfoMap = getPackageAppDataInfoMap(pmInt,
+                    allowlistedApps.toArray(new String[0]), uid);
+            if (allowlistedAppDataInfoMap == null) {
                 // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
                 // tmp free pass.
                 bindMountAppsData = false;
@@ -2286,7 +2375,7 @@
                         app.processName)) {
                     // Cannot prepare Android/app and Android/obb directory or inode == 0,
                     // so we won't mount it in zygote, but resume the mount after unlocking device.
-                    app.bindMountPending = true;
+                    app.setBindMountPending(true);
                     bindMountAppStorageDirs = false;
                 }
             }
@@ -2295,7 +2384,7 @@
             // since it has no access to them anyway.
             if (app.isolated) {
                 pkgDataInfoMap = null;
-                whitelistedAppDataInfoMap = null;
+                allowlistedAppDataInfoMap = null;
             }
 
             final Process.ProcessStartResult startResult;
@@ -2303,8 +2392,9 @@
                 startResult = startWebView(entryPoint,
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
-                        app.info.dataDir, null, app.info.packageName, app.mDisabledCompatChanges,
-                        new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+                        app.info.dataDir, null, app.info.packageName,
+                        app.getDisabledCompatChanges(),
+                        new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             } else if (hostingRecord.usesAppZygote()) {
                 final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);
 
@@ -2314,17 +2404,17 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
                         /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
-                        app.mDisabledCompatChanges, pkgDataInfoMap, whitelistedAppDataInfoMap,
+                        app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
                         false, false,
-                        new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+                        new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             } else {
                 startResult = Process.start(entryPoint,
                         app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
-                        isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
-                        whitelistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
-                        new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+                        isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap,
+                        allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
+                        new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
             }
             checkSlow(startTime, "startProcess: returned from zygote!");
             return startResult;
@@ -2339,15 +2429,14 @@
     }
 
     @GuardedBy("mService")
-    final boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
+    boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
             int zygotePolicyFlags, String abiOverride) {
         return startProcessLocked(app, hostingRecord, zygotePolicyFlags,
-                false /* disableHiddenApiChecks */, false /* disableTestApiChecks */,
-                abiOverride);
+                false /* disableHiddenApiChecks */, abiOverride);
     }
 
     @GuardedBy("mService")
-    final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
+    ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
             boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
             int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
             boolean keepIfLarge, String abiOverride, String entryPoint, String[] entryPointArgs,
@@ -2373,14 +2462,14 @@
                 // if it had been bad.
                 if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid
                         + "/" + processName);
-                mService.mAppErrors.resetProcessCrashTimeLocked(processName, info.uid);
+                mService.mAppErrors.resetProcessCrashTime(processName, info.uid);
                 if (mService.mAppErrors.isBadProcess(processName, info.uid)) {
                     EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
                             UserHandle.getUserId(info.uid), info.uid,
                             info.processName);
                     mService.mAppErrors.clearBadProcess(processName, info.uid);
                     if (app != null) {
-                        app.bad = false;
+                        app.mErrorState.setBad(false);
                     }
                 }
             }
@@ -2397,11 +2486,11 @@
         //     already running.
         if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "startProcess: name=" + processName
                 + " app=" + app + " knownToBeDead=" + knownToBeDead
-                + " thread=" + (app != null ? app.thread : null)
-                + " pid=" + (app != null ? app.pid : -1));
-        ProcessRecord precedence = null;
-        if (app != null && app.pid > 0) {
-            if ((!knownToBeDead && !app.killed) || app.thread == null) {
+                + " thread=" + (app != null ? app.getThread() : null)
+                + " pid=" + (app != null ? app.getPid() : -1));
+        ProcessRecord predecessor = null;
+        if (app != null && app.getPid() > 0) {
+            if ((!knownToBeDead && !app.isKilled()) || app.getThread() == null) {
                 // We already have the app running, or are waiting for it to
                 // come up (we have a pid but not yet its thread), so keep it.
                 if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "App already running: " + app);
@@ -2415,13 +2504,13 @@
             // clean it up now.
             if (DEBUG_PROCESSES) Slog.v(TAG_PROCESSES, "App died: " + app);
             checkSlow(startTime, "startProcess: bad proc running, killing");
-            ProcessList.killProcessGroup(app.uid, app.pid);
+            ProcessList.killProcessGroup(app.uid, app.getPid());
             checkSlow(startTime, "startProcess: done killing old proc");
 
-            if (!app.killed
+            if (!app.isKilled()
                     || mService.mAppProfiler.isLastMemoryLevelNormal()
-                    || app.setProcState < ActivityManager.PROCESS_STATE_CACHED_EMPTY
-                    || app.lastCachedPss < getCachedRestoreThresholdKb()) {
+                    || app.mState.getSetProcState() < ActivityManager.PROCESS_STATE_CACHED_EMPTY
+                    || app.mProfile.getLastCachedPss() < getCachedRestoreThresholdKb()) {
                 // Throw a wtf if it's not killed, or killed but not because the system was in
                 // memory pressure + the app was in "cch-empty" and used large amount of memory
                 Slog.wtf(TAG_PROCESSES, app.toString() + " is attached to a previous process");
@@ -2429,8 +2518,8 @@
                 Slog.w(TAG_PROCESSES, app.toString() + " is attached to a previous process");
             }
             // We are not going to re-use the ProcessRecord, as we haven't dealt with the cleanup
-            // routine of it yet, but we'd set it as the precedence of the new process.
-            precedence = app;
+            // routine of it yet, but we'd set it as the predecessor of the new process.
+            predecessor = app;
             app = null;
         }
 
@@ -2442,12 +2531,12 @@
                         + processName + "/" + info.uid + " isolated=" + isolated);
                 return null;
             }
-            app.crashHandler = crashHandler;
-            app.isolatedEntryPoint = entryPoint;
-            app.isolatedEntryPointArgs = entryPointArgs;
-            if (precedence != null) {
-                app.mPrecedence = precedence;
-                precedence.mSuccessor = app;
+            app.mErrorState.setCrashHandler(crashHandler);
+            app.setIsolatedEntryPoint(entryPoint);
+            app.setIsolatedEntryPointArgs(entryPointArgs);
+            if (predecessor != null) {
+                app.mPredecessor = predecessor;
+                predecessor.mSuccessor = app;
             }
             checkSlow(startTime, "startProcess: done creating new process record");
         } else {
@@ -2480,7 +2569,7 @@
     @GuardedBy("mService")
     private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
         StringBuilder sb = null;
-        if (app.killedByAm) {
+        if (app.isKilledByAm()) {
             if (sb == null) sb = new StringBuilder();
             sb.append("killedByAm=true;");
         }
@@ -2488,13 +2577,13 @@
             if (sb == null) sb = new StringBuilder();
             sb.append("No entry in mProcessNames;");
         }
-        if (!app.pendingStart) {
+        if (!app.isPendingStart()) {
             if (sb == null) sb = new StringBuilder();
             sb.append("pendingStart=false;");
         }
-        if (app.startSeq > expectedStartSeq) {
+        if (app.getStartSeq() > expectedStartSeq) {
             if (sb == null) sb = new StringBuilder();
-            sb.append("seq=" + app.startSeq + ",expected=" + expectedStartSeq + ";");
+            sb.append("seq=" + app.getStartSeq() + ",expected=" + expectedStartSeq + ";");
         }
         try {
             AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, app.userId);
@@ -2517,7 +2606,7 @@
             Process.ProcessStartResult startResult, long expectedStartSeq) {
         // Indicates that this process start has been taken care of.
         if (mPendingStarts.get(expectedStartSeq) == null) {
-            if (pending.pid == startResult.pid) {
+            if (pending.getPid() == startResult.pid) {
                 pending.setUsingWrapper(startResult.usingWrapper);
                 // TODO: Update already existing clients of usingWrapper
             }
@@ -2536,31 +2625,31 @@
             Slog.w(TAG_PROCESSES, app + " start not valid, killing pid=" +
                     pid
                     + ", " + reason);
-            app.pendingStart = false;
+            app.setPendingStart(false);
             killProcessQuiet(pid);
-            Process.killProcessGroup(app.uid, app.pid);
+            Process.killProcessGroup(app.uid, app.getPid());
             noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
                     ApplicationExitInfo.SUBREASON_INVALID_START, reason);
             return false;
         }
         mService.mBatteryStatsService.noteProcessStart(app.processName, app.info.uid);
-        checkSlow(app.startTime, "startProcess: done updating battery stats");
+        checkSlow(app.getStartTime(), "startProcess: done updating battery stats");
 
         EventLog.writeEvent(EventLogTags.AM_PROC_START,
-                UserHandle.getUserId(app.startUid), pid, app.startUid,
-                app.processName, app.hostingRecord.getType(),
-                app.hostingRecord.getName() != null ? app.hostingRecord.getName() : "");
+                UserHandle.getUserId(app.getStartUid()), pid, app.getStartUid(),
+                app.processName, app.getHostingRecord().getType(),
+                app.getHostingRecord().getName() != null ? app.getHostingRecord().getName() : "");
 
         try {
             AppGlobals.getPackageManager().logAppProcessStartIfNeeded(app.info.packageName,
-                    app.processName, app.uid, app.seInfo, app.info.sourceDir, pid);
+                    app.processName, app.uid, app.getSeInfo(), app.info.sourceDir, pid);
         } catch (RemoteException ex) {
             // Ignore
         }
 
         Watchdog.getInstance().processStarted(app.processName, pid);
 
-        checkSlow(app.startTime, "startProcess: building log message");
+        checkSlow(app.getStartTime(), "startProcess: building log message");
         StringBuilder buf = mStringBuilder;
         buf.setLength(0);
         buf.append("Start proc ");
@@ -2568,23 +2657,25 @@
         buf.append(':');
         buf.append(app.processName);
         buf.append('/');
-        UserHandle.formatUid(buf, app.startUid);
-        if (app.isolatedEntryPoint != null) {
+        UserHandle.formatUid(buf, app.getStartUid());
+        if (app.getIsolatedEntryPoint() != null) {
             buf.append(" [");
-            buf.append(app.isolatedEntryPoint);
+            buf.append(app.getIsolatedEntryPoint());
             buf.append("]");
         }
         buf.append(" for ");
-        buf.append(app.hostingRecord.getType());
-        if (app.hostingRecord.getName() != null) {
+        buf.append(app.getHostingRecord().getType());
+        if (app.getHostingRecord().getName() != null) {
             buf.append(" ");
-            buf.append(app.hostingRecord.getName());
+            buf.append(app.getHostingRecord().getName());
         }
-        mService.reportUidInfoMessageLocked(TAG, buf.toString(), app.startUid);
-        app.setPid(pid);
-        app.setUsingWrapper(usingWrapper);
-        app.pendingStart = false;
-        checkSlow(app.startTime, "startProcess: starting to update pids map");
+        mService.reportUidInfoMessageLocked(TAG, buf.toString(), app.getStartUid());
+        synchronized (mProcLock) {
+            app.setPid(pid);
+            app.setUsingWrapper(usingWrapper);
+            app.setPendingStart(false);
+        }
+        checkSlow(app.getStartTime(), "startProcess: starting to update pids map");
         ProcessRecord oldApp;
         synchronized (mService.mPidsSelfLocked) {
             oldApp = mService.mPidsSelfLocked.get(pid);
@@ -2593,11 +2684,11 @@
         if (oldApp != null && !app.isolated) {
             // Clean up anything relating to this pid first
             Slog.wtf(TAG, "handleProcessStartedLocked process:" + app.processName
-                    + " startSeq:" + app.startSeq
+                    + " startSeq:" + app.getStartSeq()
                     + " pid:" + pid
                     + " belongs to another existing app:" + oldApp.processName
-                    + " startSeq:" + oldApp.startSeq);
-            mService.cleanUpApplicationRecordLocked(oldApp, false, false, -1,
+                    + " startSeq:" + oldApp.getStartSeq());
+            mService.cleanUpApplicationRecordLocked(oldApp, pid, false, false, -1,
                     true /*replacingPid*/);
         }
         mService.addPidLocked(app);
@@ -2609,43 +2700,46 @@
                         ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
             }
         }
-        checkSlow(app.startTime, "startProcess: done updating pids map");
+        checkSlow(app.getStartTime(), "startProcess: done updating pids map");
         return true;
     }
 
-    final void removeLruProcessLocked(ProcessRecord app) {
+    @GuardedBy("mService")
+    void removeLruProcessLocked(ProcessRecord app) {
         int lrui = mLruProcesses.lastIndexOf(app);
         if (lrui >= 0) {
-            if (!app.killed) {
-                if (app.isPersistent()) {
-                    Slog.w(TAG, "Removing persistent process that hasn't been killed: " + app);
-                } else {
-                    Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
-                    if (app.pid > 0) {
-                        killProcessQuiet(app.pid);
-                        ProcessList.killProcessGroup(app.uid, app.pid);
-                        noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
-                                ApplicationExitInfo.SUBREASON_REMOVE_LRU, "hasn't been killed");
+            synchronized (mProcLock) {
+                if (!app.isKilled()) {
+                    if (app.isPersistent()) {
+                        Slog.w(TAG, "Removing persistent process that hasn't been killed: " + app);
                     } else {
-                        app.pendingStart = false;
+                        Slog.wtfStack(TAG, "Removing process that hasn't been killed: " + app);
+                        if (app.getPid() > 0) {
+                            killProcessQuiet(app.getPid());
+                            ProcessList.killProcessGroup(app.uid, app.getPid());
+                            noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
+                                    ApplicationExitInfo.SUBREASON_REMOVE_LRU, "hasn't been killed");
+                        } else {
+                            app.setPendingStart(false);
+                        }
                     }
                 }
+                if (lrui < mLruProcessActivityStart) {
+                    mLruProcessActivityStart--;
+                }
+                if (lrui < mLruProcessServiceStart) {
+                    mLruProcessServiceStart--;
+                }
+                mLruProcesses.remove(lrui);
             }
-            if (lrui < mLruProcessActivityStart) {
-                mLruProcessActivityStart--;
-            }
-            if (lrui < mLruProcessServiceStart) {
-                mLruProcessServiceStart--;
-            }
-            mLruProcesses.remove(lrui);
-            mService.removeOomAdjTargetLocked(app, true);
         }
+        mService.removeOomAdjTargetLocked(app, true);
     }
 
-    @GuardedBy("mService")
-    boolean killPackageProcessesLocked(String packageName, int appId, int userId, int minOomAdj,
+    @GuardedBy({"mService", "mProcLock"})
+    boolean killPackageProcessesLSP(String packageName, int appId, int userId, int minOomAdj,
             int reasonCode, int subReason, String reason) {
-        return killPackageProcessesLocked(packageName, appId, userId, minOomAdj,
+        return killPackageProcessesLSP(packageName, appId, userId, minOomAdj,
                 false /* callerWillRestart */, true /* allowRestart */, true /* doit */,
                 false /* evenPersistent */, false /* setRemoved */, reasonCode,
                 subReason, reason);
@@ -2678,8 +2772,8 @@
         }
     }
 
-    @GuardedBy("mService")
-    final boolean killPackageProcessesLocked(String packageName, int appId,
+    @GuardedBy({"mService", "mProcLock"})
+    boolean killPackageProcessesLSP(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
             boolean doit, boolean evenPersistent, boolean setRemoved, int reasonCode,
             int subReason, String reason) {
@@ -2698,7 +2792,7 @@
                     // we don't kill persistent processes
                     continue;
                 }
-                if (app.removed) {
+                if (app.isRemoved()) {
                     if (doit) {
                         procs.add(app);
                     }
@@ -2706,7 +2800,7 @@
                 }
 
                 // Skip process if it doesn't meet our oom adj requirement.
-                if (app.setAdj < minOomAdj) {
+                if (app.mState.getSetAdj() < minOomAdj) {
                     // Note it is still possible to have a process with oom adj 0 in the killed
                     // processes, but it does not mean misjudgment. E.g. a bound service process
                     // and its client activity process are both in the background, so they are
@@ -2728,15 +2822,15 @@
                     // that match it.  We need to qualify this by the processes
                     // that are running under the specified app and user ID.
                 } else {
-                    final boolean isDep = app.pkgDeps != null
-                            && app.pkgDeps.contains(packageName);
+                    final boolean isDep = app.getPkgDeps() != null
+                            && app.getPkgDeps().contains(packageName);
                     if (!isDep && UserHandle.getAppId(app.uid) != appId) {
                         continue;
                     }
                     if (userId != UserHandle.USER_ALL && app.userId != userId) {
                         continue;
                     }
-                    if (!app.pkgList.containsKey(packageName) && !isDep) {
+                    if (!app.getPkgList().containsKey(packageName) && !isDep) {
                         continue;
                     }
                 }
@@ -2746,7 +2840,7 @@
                     return true;
                 }
                 if (setRemoved) {
-                    app.removed = true;
+                    app.setRemoved(true);
                 }
                 procs.add(app);
             }
@@ -2787,17 +2881,17 @@
         mService.mAtmInternal.clearHeavyWeightProcessIfEquals(app.getWindowProcessController());
 
         boolean needRestart = false;
-        if ((app.pid > 0 && app.pid != ActivityManagerService.MY_PID) || (app.pid == 0 && app
-                .pendingStart)) {
-            int pid = app.pid;
+        final int pid = app.getPid();
+        if ((pid > 0 && pid != ActivityManagerService.MY_PID)
+                || (pid == 0 && app.isPendingStart())) {
             if (pid > 0) {
-                mService.removePidLocked(app);
-                app.bindMountPending = false;
+                mService.removePidLocked(pid, app);
+                app.setBindMountPending(false);
                 mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
                 mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
                 if (app.isolated) {
                     mService.mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
-                    mService.getPackageManagerInternalLocked().removeIsolatedUid(app.uid);
+                    mService.getPackageManagerInternal().removeIsolatedUid(app.uid);
                 }
             }
             boolean willRestart = false;
@@ -2808,8 +2902,8 @@
                     needRestart = true;
                 }
             }
-            app.kill(reason, reasonCode, subReason, true);
-            mService.handleAppDiedLocked(app, willRestart, allowRestart);
+            app.killLocked(reason, reasonCode, subReason, true);
+            mService.handleAppDiedLocked(app, pid, willRestart, allowRestart);
             if (willRestart) {
                 removeLruProcessLocked(app);
                 mService.addAppLocked(app.info, null, false, null /* ABI override */,
@@ -2823,48 +2917,51 @@
     }
 
     @GuardedBy("mService")
-    final void addProcessNameLocked(ProcessRecord proc) {
+    void addProcessNameLocked(ProcessRecord proc) {
         // We shouldn't already have a process under this name, but just in case we
         // need to clean up whatever may be there now.
-        ProcessRecord old = removeProcessNameLocked(proc.processName, proc.uid);
-        if (old == proc && proc.isPersistent()) {
-            // We are re-adding a persistent process.  Whatevs!  Just leave it there.
-            Slog.w(TAG, "Re-adding persistent process " + proc);
-        } else if (old != null) {
-            if (old.killed) {
-                // The old process has been killed, we probably haven't had
-                // a chance to clean up the old record, just log a warning
-                Slog.w(TAG, "Existing proc " + old + " was killed "
-                        + (SystemClock.uptimeMillis() - old.mKillTime)
-                        + "ms ago when adding " + proc);
-            } else {
-                Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc);
+        synchronized (mProcLock) {
+            ProcessRecord old = removeProcessNameLocked(proc.processName, proc.uid);
+            if (old == proc && proc.isPersistent()) {
+                // We are re-adding a persistent process.  Whatevs!  Just leave it there.
+                Slog.w(TAG, "Re-adding persistent process " + proc);
+            } else if (old != null) {
+                if (old.isKilled()) {
+                    // The old process has been killed, we probably haven't had
+                    // a chance to clean up the old record, just log a warning
+                    Slog.w(TAG, "Existing proc " + old + " was killed "
+                            + (SystemClock.uptimeMillis() - old.getKillTime())
+                            + "ms ago when adding " + proc);
+                } else {
+                    Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc);
+                }
             }
-        }
-        UidRecord uidRec = mActiveUids.get(proc.uid);
-        if (uidRec == null) {
-            uidRec = new UidRecord(proc.uid);
-            // This is the first appearance of the uid, report it now!
-            if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                    "Creating new process uid: " + uidRec);
-            if (Arrays.binarySearch(mService.mDeviceIdleTempWhitelist,
-                    UserHandle.getAppId(proc.uid)) >= 0
-                    || mService.mPendingTempWhitelist.indexOfKey(proc.uid) >= 0) {
-                uidRec.setWhitelist = uidRec.curWhitelist = true;
+            UidRecord uidRec = mActiveUids.get(proc.uid);
+            if (uidRec == null) {
+                uidRec = new UidRecord(proc.uid, mService);
+                // This is the first appearance of the uid, report it now!
+                if (DEBUG_UID_OBSERVERS) {
+                    Slog.i(TAG_UID_OBSERVERS, "Creating new process uid: " + uidRec);
+                }
+                if (Arrays.binarySearch(mService.mDeviceIdleTempAllowlist,
+                            UserHandle.getAppId(proc.uid)) >= 0
+                        || mService.mPendingTempAllowlist.indexOfKey(proc.uid) >= 0) {
+                    uidRec.setCurAllowListed(true);
+                    uidRec.setSetAllowListed(true);
+                }
+                uidRec.updateHasInternetPermission();
+                mActiveUids.put(proc.uid, uidRec);
+                EventLogTags.writeAmUidRunning(uidRec.getUid());
+                mService.noteUidProcessState(uidRec.getUid(), uidRec.getCurProcState(),
+                        uidRec.getCurCapability());
             }
-            uidRec.updateHasInternetPermission();
-            mActiveUids.put(proc.uid, uidRec);
-            EventLogTags.writeAmUidRunning(uidRec.uid);
-            mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState(),
-                    uidRec.curCapability);
-        }
-        proc.uidRecord = uidRec;
-        uidRec.procRecords.add(proc);
+            proc.setUidRecord(uidRec);
+            uidRec.addProcess(proc);
 
-        // Reset render thread tid if it was already set, so new process can set it again.
-        proc.renderThreadTid = 0;
-        uidRec.numProcs++;
-        mProcessNames.put(proc.processName, proc.uid, proc);
+            // Reset render thread tid if it was already set, so new process can set it again.
+            proc.setRenderThreadTid(0);
+            mProcessNames.put(proc.processName, proc.uid, proc);
+        }
         if (proc.isolated) {
             mIsolatedProcesses.put(proc.uid, proc);
         }
@@ -2883,7 +2980,7 @@
     }
 
     @GuardedBy("mService")
-    final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
+    ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
             boolean isolated, int isolatedUid, HostingRecord hostingRecord) {
         String proc = customProcess != null ? customProcess : info.processName;
         final int userId = UserHandle.getUserId(info.uid);
@@ -2904,7 +3001,7 @@
                 uid = isolatedUid;
             }
             mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(uid, info.uid);
-            mService.getPackageManagerInternalLocked().addIsolatedUid(uid, info.uid);
+            mService.getPackageManagerInternal().addIsolatedUid(uid, info.uid);
 
             // Register the isolated UID with this application so BatteryStats knows to
             // attribute resource usage to the application.
@@ -2917,57 +3014,63 @@
                     FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__CREATED);
         }
         final ProcessRecord r = new ProcessRecord(mService, info, proc, uid);
+        final ProcessStateRecord state = r.mState;
 
         if (!mService.mBooted && !mService.mBooting
                 && userId == UserHandle.USER_SYSTEM
                 && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
             // The system process is initialized to SCHED_GROUP_DEFAULT in init.rc.
-            r.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
-            r.setSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+            state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
+            state.setSetSchedGroup(ProcessList.SCHED_GROUP_DEFAULT);
             r.setPersistent(true);
-            r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
+            state.setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ);
         }
         if (isolated && isolatedUid != 0) {
             // Special case for startIsolatedProcess (internal only) - assume the process
             // is required by the system server to prevent it being killed.
-            r.maxAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
+            state.setMaxAdj(ProcessList.PERSISTENT_SERVICE_ADJ);
         }
         addProcessNameLocked(r);
         return r;
     }
 
     @GuardedBy("mService")
-    final ProcessRecord removeProcessNameLocked(final String name, final int uid) {
+    ProcessRecord removeProcessNameLocked(final String name, final int uid) {
         return removeProcessNameLocked(name, uid, null);
     }
 
     @GuardedBy("mService")
-    final ProcessRecord removeProcessNameLocked(final String name, final int uid,
+    ProcessRecord removeProcessNameLocked(final String name, final int uid,
             final ProcessRecord expecting) {
         ProcessRecord old = mProcessNames.get(name, uid);
-        // Only actually remove when the currently recorded value matches the
-        // record that we expected; if it doesn't match then we raced with a
-        // newly created process and we don't want to destroy the new one.
-        if ((expecting == null) || (old == expecting)) {
-            mProcessNames.remove(name, uid);
-        }
         final ProcessRecord record = expecting != null ? expecting : old;
-        if (record != null && record.uidRecord != null) {
-            final UidRecord uidRecord = record.uidRecord;
-            uidRecord.numProcs--;
-            uidRecord.procRecords.remove(record);
-            if (uidRecord.numProcs == 0) {
-                // No more processes using this uid, tell clients it is gone.
-                if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
-                        "No more processes in " + uidRecord);
-                mService.enqueueUidChangeLocked(uidRecord, -1,
-                        UidRecord.CHANGE_GONE);
-                EventLogTags.writeAmUidStopped(uid);
-                mActiveUids.remove(uid);
-                mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT,
-                        ActivityManager.PROCESS_CAPABILITY_NONE);
+        synchronized (mProcLock) {
+            // Only actually remove when the currently recorded value matches the
+            // record that we expected; if it doesn't match then we raced with a
+            // newly created process and we don't want to destroy the new one.
+            if ((expecting == null) || (old == expecting)) {
+                mProcessNames.remove(name, uid);
             }
-            record.uidRecord = null;
+            if (record != null) {
+                final UidRecord uidRecord = record.getUidRecord();
+                if (uidRecord != null) {
+                    uidRecord.removeProcess(record);
+                    if (uidRecord.getNumOfProcs() == 0) {
+                        // No more processes using this uid, tell clients it is gone.
+                        if (DEBUG_UID_OBSERVERS) {
+                            Slog.i(TAG_UID_OBSERVERS, "No more processes in " + uidRecord);
+                        }
+                        mService.enqueueUidChangeLocked(uidRecord, -1,
+                                UidRecord.CHANGE_GONE);
+                        EventLogTags.writeAmUidStopped(uid);
+                        mActiveUids.remove(uid);
+                        mService.mFgsStartTempAllowList.remove(record.info.uid);
+                        mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT,
+                                ActivityManager.PROCESS_CAPABILITY_NONE);
+                    }
+                    record.setUidRecord(null);
+                }
+            }
         }
         mIsolatedProcesses.remove(uid);
         mGlobalIsolatedUids.freeIsolatedUidLocked(uid);
@@ -2980,13 +3083,14 @@
     }
 
     /** Call setCoreSettings on all LRU processes, with the new settings. */
-    @GuardedBy("mService")
-    void updateCoreSettingsLocked(Bundle settings) {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    void updateCoreSettingsLOSP(Bundle settings) {
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord processRecord = mLruProcesses.get(i);
+            final IApplicationThread thread = processRecord.getThread();
             try {
-                if (processRecord.thread != null) {
-                    processRecord.thread.setCoreSettings(settings);
+                if (thread != null) {
+                    thread.setCoreSettings(settings);
                 }
             } catch (RemoteException re) {
                 /* ignore */
@@ -3000,8 +3104,8 @@
      * @param minTargetSdk
      * @param maxProcState
      */
-    @GuardedBy("mService")
-    void killAllBackgroundProcessesExceptLocked(int minTargetSdk, int maxProcState) {
+    @GuardedBy({"mService", "mProcLock"})
+    void killAllBackgroundProcessesExceptLSP(int minTargetSdk, int maxProcState) {
         final ArrayList<ProcessRecord> procs = new ArrayList<>();
         final int NP = mProcessNames.getMap().size();
         for (int ip = 0; ip < NP; ip++) {
@@ -3009,8 +3113,9 @@
             final int NA = apps.size();
             for (int ia = 0; ia < NA; ia++) {
                 final ProcessRecord app = apps.valueAt(ia);
-                if (app.removed || ((minTargetSdk < 0 || app.info.targetSdkVersion < minTargetSdk)
-                        && (maxProcState < 0 || app.setProcState > maxProcState))) {
+                if (app.isRemoved()
+                        || ((minTargetSdk < 0 || app.info.targetSdkVersion < minTargetSdk)
+                        && (maxProcState < 0 || app.mState.getSetProcState() > maxProcState))) {
                     procs.add(app);
                 }
             }
@@ -3027,13 +3132,14 @@
      * Call updateTimePrefs on all LRU processes
      * @param timePref The time pref to pass to each process
      */
-    @GuardedBy("mService")
-    void updateAllTimePrefsLocked(int timePref) {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    void updateAllTimePrefsLOSP(int timePref) {
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord r = mLruProcesses.get(i);
-            if (r.thread != null) {
+            final IApplicationThread thread = r.getThread();
+            if (thread != null) {
                 try {
-                    r.thread.updateTimePrefs(timePref);
+                    thread.updateTimePrefs(timePref);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to update preferences for: "
                             + r.info.processName);
@@ -3044,15 +3150,16 @@
 
     void setAllHttpProxy() {
         // Update the HTTP proxy for each application thread.
-        synchronized (mService) {
+        synchronized (mProcLock) {
             for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
                 ProcessRecord r = mLruProcesses.get(i);
+                IApplicationThread thread = r.getThread();
                 // Don't dispatch to isolated processes as they can't access ConnectivityManager and
                 // don't have network privileges anyway. Exclude system server and update it
                 // separately outside the AMS lock, to avoid deadlock with Connectivity Service.
-                if (r.pid != ActivityManagerService.MY_PID && r.thread != null && !r.isolated) {
+                if (r.getPid() != ActivityManagerService.MY_PID && thread != null && !r.isolated) {
                     try {
-                        r.thread.updateHttpProxy();
+                        thread.updateHttpProxy();
                     } catch (RemoteException ex) {
                         Slog.w(TAG, "Failed to update http proxy for: "
                                 + r.info.processName);
@@ -3063,13 +3170,14 @@
         ActivityThread.updateHttpProxy(mService.mContext);
     }
 
-    @GuardedBy("mService")
-    void clearAllDnsCacheLocked() {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    void clearAllDnsCacheLOSP() {
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord r = mLruProcesses.get(i);
-            if (r.thread != null) {
+            final IApplicationThread thread = r.getThread();
+            if (thread != null) {
                 try {
-                    r.thread.clearDnsCache();
+                    thread.clearDnsCache();
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to clear dns cache for: " + r.info.processName);
                 }
@@ -3077,13 +3185,14 @@
         }
     }
 
-    @GuardedBy("mService")
-    void handleAllTrustStorageUpdateLocked() {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    void handleAllTrustStorageUpdateLOSP() {
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord r = mLruProcesses.get(i);
-            if (r.thread != null) {
+            final IApplicationThread thread = r.getThread();
+            if (thread != null) {
                 try {
-                    r.thread.handleTrustStorageUpdate();
+                    thread.handleTrustStorageUpdate();
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to handle trust storage update for: " +
                             r.info.processName);
@@ -3092,10 +3201,10 @@
         }
     }
 
-    @GuardedBy("mService")
-    int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
+    @GuardedBy({"mService", "mProcLock"})
+    private int updateLruProcessInternalLSP(ProcessRecord app, long now, int index,
             int lruSeq, String what, Object obj, ProcessRecord srcApp) {
-        app.lastActivityTime = now;
+        app.setLastActivityTime(now);
 
         if (app.hasActivitiesOrRecentTasks()) {
             // Don't want to touch dependent processes that are hosting activities.
@@ -3127,7 +3236,7 @@
         if (DEBUG_LRU) Slog.d(TAG_LRU, "Moving dep from " + lrui + " to " + index
                 + " in LRU list: " + app);
         mLruProcesses.add(index, app);
-        app.lruSeq = lruSeq;
+        app.setLruSeq(lruSeq);
         return index;
     }
 
@@ -3147,45 +3256,51 @@
      * @param endIndex The current end of the top being processed.  Typically topI - 1.  That is,
      *                 where we are going to start potentially adjusting other entries in the list.
      */
-    private void updateClientActivitiesOrdering(final ProcessRecord topApp, final int topI,
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateClientActivitiesOrderingLSP(final ProcessRecord topApp, final int topI,
             final int bottomI, int endIndex) {
-        if (topApp.hasActivitiesOrRecentTasks() || topApp.treatLikeActivity
-                || !topApp.hasClientActivities()) {
+        final ProcessServiceRecord topPsr = topApp.mServices;
+        if (topApp.hasActivitiesOrRecentTasks() || topPsr.isTreatedLikeActivity()
+                || !topPsr.hasClientActivities()) {
             // If this is not a special process that has client activities, then there is
             // nothing to do.
             return;
         }
 
         final int uid = topApp.info.uid;
-        if (topApp.connectionGroup > 0) {
-            int endImportance = topApp.connectionImportance;
+        final int topConnectionGroup = topPsr.getConnectionGroup();
+        if (topConnectionGroup > 0) {
+            int endImportance = topPsr.getConnectionImportance();
             for (int i = endIndex; i >= bottomI; i--) {
                 final ProcessRecord subProc = mLruProcesses.get(i);
+                final ProcessServiceRecord subPsr = subProc.mServices;
+                final int subConnectionGroup = subPsr.getConnectionGroup();
+                final int subConnectionImportance = subPsr.getConnectionImportance();
                 if (subProc.info.uid == uid
-                        && subProc.connectionGroup == topApp.connectionGroup) {
-                    if (i == endIndex && subProc.connectionImportance >= endImportance) {
+                        && subConnectionGroup == topConnectionGroup) {
+                    if (i == endIndex && subConnectionImportance >= endImportance) {
                         // This process is already in the group, and its importance
                         // is not as strong as the process before it, so keep it
                         // correctly positioned in the group.
                         if (DEBUG_LRU) Slog.d(TAG_LRU,
                                 "Keeping in-place above " + subProc
                                         + " endImportance=" + endImportance
-                                        + " group=" + subProc.connectionGroup
-                                        + " importance=" + subProc.connectionImportance);
+                                        + " group=" + subConnectionGroup
+                                        + " importance=" + subConnectionImportance);
                         endIndex--;
-                        endImportance = subProc.connectionImportance;
+                        endImportance = subConnectionImportance;
                     } else {
                         // We want to pull this up to be with the rest of the group,
                         // and order within the group by importance.
                         if (DEBUG_LRU) Slog.d(TAG_LRU,
                                 "Pulling up " + subProc
                                         + " to position in group with importance="
-                                        + subProc.connectionImportance);
+                                        + subConnectionImportance);
                         boolean moved = false;
                         for (int pos = topI; pos > endIndex; pos--) {
                             final ProcessRecord posProc = mLruProcesses.get(pos);
-                            if (subProc.connectionImportance
-                                    <= posProc.connectionImportance) {
+                            if (subConnectionImportance
+                                    <= posProc.mServices.getConnectionImportance()) {
                                 mLruProcesses.remove(i);
                                 mLruProcesses.add(pos, subProc);
                                 if (DEBUG_LRU) Slog.d(TAG_LRU,
@@ -3206,7 +3321,7 @@
                                             + " from position " + i + " to end of group @ "
                                             + endIndex);
                             endIndex--;
-                            endImportance = subProc.connectionImportance;
+                            endImportance = subConnectionImportance;
                         }
                     }
                 }
@@ -3218,6 +3333,8 @@
         int i = endIndex;
         while (i >= bottomI) {
             ProcessRecord subProc = mLruProcesses.get(i);
+            final ProcessServiceRecord subPsr = subProc.mServices;
+            final int subConnectionGroup = subPsr.getConnectionGroup();
             if (DEBUG_LRU) Slog.d(TAG_LRU,
                     "Looking to spread old procs, at " + subProc + " @ " + i);
             if (subProc.info.uid != uid) {
@@ -3240,7 +3357,8 @@
                         subProc = mLruProcesses.get(i);
                         if (DEBUG_LRU) Slog.d(TAG_LRU,
                                 "Looking at next app at " + i + ": " + subProc);
-                        if (subProc.hasActivitiesOrRecentTasks() || subProc.treatLikeActivity) {
+                        if (subProc.hasActivitiesOrRecentTasks()
+                                || subPsr.isTreatedLikeActivity()) {
                             if (DEBUG_LRU) Slog.d(TAG_LRU,
                                     "This is hosting an activity!");
                             if (hasActivity) {
@@ -3250,7 +3368,7 @@
                                 break;
                             }
                             hasActivity = true;
-                        } else if (subProc.hasClientActivities()) {
+                        } else if (subPsr.hasClientActivities()) {
                             if (DEBUG_LRU) Slog.d(TAG_LRU,
                                     "This is a client of an activity");
                             if (hasActivity) {
@@ -3261,21 +3379,21 @@
                                             "Already found a different activity: connUid="
                                             + connUid + " uid=" + subProc.info.uid);
                                     break;
-                                } else if (connGroup == 0 || connGroup != subProc.connectionGroup) {
+                                } else if (connGroup == 0 || connGroup != subConnectionGroup) {
                                     // Previously saw a different group or not from a group,
                                     // want to treat these as different things.
                                     if (DEBUG_LRU) Slog.d(TAG_LRU,
                                             "Already found a different group: connGroup="
-                                            + connGroup + " group=" + subProc.connectionGroup);
+                                            + connGroup + " group=" + subConnectionGroup);
                                     break;
                                 }
                             } else {
                                 if (DEBUG_LRU) Slog.d(TAG_LRU,
                                         "This is an activity client!  uid="
-                                        + subProc.info.uid + " group=" + subProc.connectionGroup);
+                                        + subProc.info.uid + " group=" + subConnectionGroup);
                                 hasActivity = true;
                                 connUid = subProc.info.uid;
-                                connGroup = subProc.connectionGroup;
+                                connGroup = subConnectionGroup;
                             }
                         }
                         endIndex--;
@@ -3296,13 +3414,16 @@
                 }
                 if (endIndex >= bottomI) {
                     final ProcessRecord endProc = mLruProcesses.get(endIndex);
+                    final ProcessServiceRecord endPsr = endProc.mServices;
+                    final int endConnectionGroup = endPsr.getConnectionGroup();
                     for (endIndex--; endIndex >= bottomI; endIndex--) {
                         final ProcessRecord nextEndProc = mLruProcesses.get(endIndex);
+                        final int nextConnectionGroup = nextEndProc.mServices.getConnectionGroup();
                         if (nextEndProc.info.uid != uid
-                                || nextEndProc.connectionGroup != endProc.connectionGroup) {
+                                || nextConnectionGroup != endConnectionGroup) {
                             if (DEBUG_LRU) Slog.d(TAG_LRU,
                                     "Found next group or app: " + nextEndProc + " @ "
-                                            + endIndex + " group=" + nextEndProc.connectionGroup);
+                                            + endIndex + " group=" + nextConnectionGroup);
                             break;
                         }
                     }
@@ -3316,10 +3437,11 @@
         }
     }
 
-    final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
-            ProcessRecord client) {
-        final boolean hasActivity = app.hasActivitiesOrRecentTasks() || app.hasClientActivities()
-                || app.treatLikeActivity;
+    @GuardedBy("mService")
+    void updateLruProcessLocked(ProcessRecord app, boolean activityChange, ProcessRecord client) {
+        final ProcessServiceRecord psr = app.mServices;
+        final boolean hasActivity = app.hasActivitiesOrRecentTasks() || psr.hasClientActivities()
+                || psr.isTreatedLikeActivity();
         final boolean hasService = false; // not impl yet. app.services.size() > 0;
         if (!activityChange && hasActivity) {
             // The process has activities, so we are only allowing activity-based adjustments
@@ -3329,9 +3451,18 @@
             return;
         }
 
+        synchronized (mProcLock) {
+            updateLruProcessLSP(app, client, hasActivity, hasService);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateLruProcessLSP(ProcessRecord app, ProcessRecord client,
+            boolean hasActivity, boolean hasService) {
         mLruSeq++;
         final long now = SystemClock.uptimeMillis();
-        app.lastActivityTime = now;
+        final ProcessServiceRecord psr = app.mServices;
+        app.setLastActivityTime(now);
 
         // First a quick reject: if the app is already at the position we will
         // put it, then there is nothing to do.
@@ -3425,14 +3556,14 @@
         if (hasActivity) {
             final int N = mLruProcesses.size();
             nextIndex = mLruProcessServiceStart;
-            if (!app.hasActivitiesOrRecentTasks() && !app.treatLikeActivity
+            if (!app.hasActivitiesOrRecentTasks() && !psr.isTreatedLikeActivity()
                     && mLruProcessActivityStart < (N - 1)) {
                 // Process doesn't have activities, but has clients with
                 // activities...  move it up, but below the app that is binding to it.
                 if (DEBUG_LRU) Slog.d(TAG_LRU,
                         "Adding to second-top of LRU activity list: " + app
-                        + " group=" + app.connectionGroup
-                        + " importance=" + app.connectionImportance);
+                        + " group=" + psr.getConnectionGroup()
+                        + " importance=" + psr.getConnectionImportance());
                 int pos = N - 1;
                 while (pos > mLruProcessActivityStart) {
                     final ProcessRecord posproc = mLruProcesses.get(pos);
@@ -3452,7 +3583,7 @@
                     endIndex = mLruProcessActivityStart;
                 }
                 nextActivityIndex = endIndex;
-                updateClientActivitiesOrdering(app, pos, mLruProcessActivityStart, endIndex);
+                updateClientActivitiesOrderingLSP(app, pos, mLruProcessActivityStart, endIndex);
             } else {
                 // Process has activities, put it at the very tipsy-top.
                 if (DEBUG_LRU) Slog.d(TAG_LRU, "Adding to top of LRU activity list: " + app);
@@ -3489,46 +3620,48 @@
             mLruProcessActivityStart++;
             mLruProcessServiceStart++;
             if (index > 1) {
-                updateClientActivitiesOrdering(app, mLruProcessServiceStart - 1, 0, index - 1);
+                updateClientActivitiesOrderingLSP(app, mLruProcessServiceStart - 1, 0, index - 1);
             }
         }
 
-        app.lruSeq = mLruSeq;
+        app.setLruSeq(mLruSeq);
 
         // If the app is currently using a content provider or service,
         // bump those processes as well.
-        for (int j = app.connections.size() - 1; j >= 0; j--) {
-            ConnectionRecord cr = app.connections.valueAt(j);
+        for (int j = psr.numberOfConnections() - 1; j >= 0; j--) {
+            ConnectionRecord cr = psr.getConnectionAt(j);
             if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
                     && cr.binding.service.app != null
-                    && cr.binding.service.app.lruSeq != mLruSeq
+                    && cr.binding.service.app.getLruSeq() != mLruSeq
                     && (cr.flags & Context.BIND_REDUCTION_FLAGS) == 0
                     && !cr.binding.service.app.isPersistent()) {
-                if (cr.binding.service.app.hasClientActivities()) {
+                if (cr.binding.service.app.mServices.hasClientActivities()) {
                     if (nextActivityIndex >= 0) {
-                        nextActivityIndex = updateLruProcessInternalLocked(cr.binding.service.app,
+                        nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app,
                                 now,
                                 nextActivityIndex, mLruSeq,
                                 "service connection", cr, app);
                     }
                 } else {
-                    nextIndex = updateLruProcessInternalLocked(cr.binding.service.app,
+                    nextIndex = updateLruProcessInternalLSP(cr.binding.service.app,
                             now,
                             nextIndex, mLruSeq,
                             "service connection", cr, app);
                 }
             }
         }
-        for (int j = app.conProviders.size() - 1; j >= 0; j--) {
-            ContentProviderRecord cpr = app.conProviders.get(j).provider;
-            if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.isPersistent()) {
-                nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, mLruSeq,
+        final ProcessProviderRecord ppr = app.mProviders;
+        for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) {
+            ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider;
+            if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) {
+                nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq,
                         "provider reference", cpr, app);
             }
         }
     }
 
-    final ProcessRecord getLRURecordForAppLocked(IApplicationThread thread) {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ProcessRecord getLRURecordForAppLOSP(IApplicationThread thread) {
         if (thread == null) {
             return null;
         }
@@ -3536,18 +3669,20 @@
         // Find the application record.
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             final ProcessRecord rec = mLruProcesses.get(i);
-            if (rec.thread != null && rec.thread.asBinder() == threadBinder) {
+            final IApplicationThread t = rec.getThread();
+            if (t != null && t.asBinder() == threadBinder) {
                 return rec;
             }
         }
         return null;
     }
 
-    boolean haveBackgroundProcessLocked() {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean haveBackgroundProcessLOSP() {
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             final ProcessRecord rec = mLruProcesses.get(i);
-            if (rec.thread != null
-                    && rec.setProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+            if (rec.getThread() != null
+                    && rec.mState.getSetProcState() >= PROCESS_STATE_CACHED_ACTIVITY) {
                 return true;
             }
         }
@@ -3567,11 +3702,11 @@
         return imp;
     }
 
-    @GuardedBy("mService")
-    void fillInProcMemInfoLocked(ProcessRecord app,
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    void fillInProcMemInfoLOSP(ProcessRecord app,
             ActivityManager.RunningAppProcessInfo outInfo,
             int clientTargetSdk) {
-        outInfo.pid = app.pid;
+        outInfo.pid = app.getPid();
         outInfo.uid = app.info.uid;
         if (app.getWindowProcessController().isHeavyWeightProcess()) {
             outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE;
@@ -3582,50 +3717,54 @@
         if (app.hasActivities()) {
             outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES;
         }
-        outInfo.lastTrimLevel = app.trimMemoryLevel;
-        int adj = app.curAdj;
-        int procState = app.getCurProcState();
+        outInfo.lastTrimLevel = app.mProfile.getTrimMemoryLevel();
+        final ProcessStateRecord state = app.mState;
+        int adj = state.getCurAdj();
+        int procState = state.getCurProcState();
         outInfo.importance = procStateToImportance(procState, adj, outInfo,
                 clientTargetSdk);
-        outInfo.importanceReasonCode = app.adjTypeCode;
-        outInfo.processState = app.getCurProcState();
-        outInfo.isFocused = (app == mService.getTopAppLocked());
-        outInfo.lastActivityTime = app.lastActivityTime;
+        outInfo.importanceReasonCode = state.getAdjTypeCode();
+        outInfo.processState = procState;
+        outInfo.isFocused = (app == mService.getTopApp());
+        outInfo.lastActivityTime = app.getLastActivityTime();
     }
 
-    @GuardedBy("mService")
-    List<ActivityManager.RunningAppProcessInfo> getRunningAppProcessesLocked(boolean allUsers,
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    List<ActivityManager.RunningAppProcessInfo> getRunningAppProcessesLOSP(boolean allUsers,
             int userId, boolean allUids, int callingUid, int clientTargetSdk) {
         // Lazy instantiation of list
         List<ActivityManager.RunningAppProcessInfo> runList = null;
 
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord app = mLruProcesses.get(i);
+            final ProcessStateRecord state = app.mState;
+            final ProcessErrorStateRecord errState = app.mErrorState;
             if ((!allUsers && app.userId != userId)
                     || (!allUids && app.uid != callingUid)) {
                 continue;
             }
-            if ((app.thread != null) && (!app.isCrashing() && !app.isNotResponding())) {
+            if ((app.getThread() != null)
+                    && (!errState.isCrashing() && !errState.isNotResponding())) {
                 // Generate process state info for running application
                 ActivityManager.RunningAppProcessInfo currApp =
                         new ActivityManager.RunningAppProcessInfo(app.processName,
-                                app.pid, app.getPackageList());
-                fillInProcMemInfoLocked(app, currApp, clientTargetSdk);
-                if (app.adjSource instanceof ProcessRecord) {
-                    currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid;
+                                app.getPid(), app.getPackageList());
+                fillInProcMemInfoLOSP(app, currApp, clientTargetSdk);
+                if (state.getAdjSource() instanceof ProcessRecord) {
+                    currApp.importanceReasonPid = ((ProcessRecord) state.getAdjSource()).getPid();
                     currApp.importanceReasonImportance =
                             ActivityManager.RunningAppProcessInfo.procStateToImportance(
-                                    app.adjSourceProcState);
-                } else if (app.adjSource instanceof ActivityServiceConnectionsHolder) {
+                                    state.getAdjSourceProcState());
+                } else if (state.getAdjSource() instanceof ActivityServiceConnectionsHolder) {
                     final ActivityServiceConnectionsHolder r =
-                            (ActivityServiceConnectionsHolder) app.adjSource;
+                            (ActivityServiceConnectionsHolder) state.getAdjSource();
                     final int pid = r.getActivityPid();
                     if (pid != -1) {
                         currApp.importanceReasonPid = pid;
                     }
                 }
-                if (app.adjTarget instanceof ComponentName) {
-                    currApp.importanceReasonComponent = (ComponentName)app.adjTarget;
+                if (state.getAdjTarget() instanceof ComponentName) {
+                    currApp.importanceReasonComponent = (ComponentName) state.getAdjTarget();
                 }
                 //Slog.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance
                 //        + " lru=" + currApp.lru);
@@ -3638,11 +3777,110 @@
         return runList;
     }
 
-    @GuardedBy("mService")
-    int getLruSizeLocked() {
+    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    int getLruSizeLOSP() {
         return mLruProcesses.size();
     }
 
+    /**
+     * Return the reference to the LRU list, call this function for read-only access
+     */
+    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    ArrayList<ProcessRecord> getLruProcessesLOSP() {
+        return mLruProcesses;
+    }
+
+    /**
+     * Return the reference to the LRU list, call this function for read/write access
+     */
+    @GuardedBy({"mService", "mProfileLock"})
+    ArrayList<ProcessRecord> getLruProcessesLSP() {
+        return mLruProcesses;
+    }
+
+    /**
+     * For test only
+     */
+    @VisibleForTesting
+    @GuardedBy({"mService", "mProfileLock"})
+    void setLruProcessServiceStartLSP(int pos) {
+        mLruProcessServiceStart = pos;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    int getLruProcessServiceStartLOSP() {
+        return mLruProcessServiceStart;
+    }
+
+    /**
+     * Iterate the whole LRU list, invoke the given {@code callback} with each of the ProcessRecord
+     * in that list.
+     *
+     * @param iterateForward If {@code true}, iterate the LRU list from the least recent used
+     *                       to most recent used ProcessRecord.
+     * @param callback The callback interface to accept the current ProcessRecord.
+     */
+    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    void forEachLruProcessesLOSP(boolean iterateForward,
+            @NonNull Consumer<ProcessRecord> callback) {
+        if (iterateForward) {
+            for (int i = 0, size = mLruProcesses.size(); i < size; i++) {
+                callback.accept(mLruProcesses.get(i));
+            }
+        } else {
+            for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+                callback.accept(mLruProcesses.get(i));
+            }
+        }
+    }
+
+    /**
+     * Search in the LRU list, invoke the given {@code callback} with each of the ProcessRecord
+     * in that list; if the callback returns a non-null object, halt the search, return that
+     * object as the return value of this search function.
+     *
+     * @param iterateForward If {@code true}, iterate the LRU list from the least recent used
+     *                       to most recent used ProcessRecord.
+     * @param callback The callback interface to accept the current ProcessRecord; if it returns
+     *                 a non-null object, the search will be halted and this object will be used
+     *                 as the return value of this search function.
+     */
+    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    <R> R searchEachLruProcessesLOSP(boolean iterateForward,
+            @NonNull Function<ProcessRecord, R> callback) {
+        if (iterateForward) {
+            for (int i = 0, size = mLruProcesses.size(); i < size; i++) {
+                R r;
+                if ((r = callback.apply(mLruProcesses.get(i))) != null) {
+                    return r;
+                }
+            }
+        } else {
+            for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+                R r;
+                if ((r = callback.apply(mLruProcesses.get(i))) != null) {
+                    return r;
+                }
+            }
+        }
+        return null;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    boolean isInLruListLOSP(ProcessRecord app) {
+        return mLruProcesses.contains(app);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    int getLruSeqLOSP() {
+        return mLruSeq;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProfileLock"})
+    MyProcessMap getProcessNamesLOSP() {
+        return mProcessNames;
+    }
+
     @GuardedBy("mService")
     void dumpLruListHeaderLocked(PrintWriter pw) {
         pw.print("  Process LRU list (sorted by oom_adj, "); pw.print(mLruProcesses.size());
@@ -3654,7 +3892,741 @@
     }
 
     @GuardedBy("mService")
-    ArrayList<ProcessRecord> collectProcessesLocked(int start, boolean allPkgs, String[] args) {
+    private void dumpLruEntryLocked(PrintWriter pw, int index, ProcessRecord proc, String prefix) {
+        pw.print(prefix);
+        pw.print('#');
+        if (index < 10) {
+            pw.print(' ');
+        }
+        pw.print(index);
+        pw.print(": ");
+        pw.print(makeOomAdjString(proc.mState.getSetAdj(), false));
+        pw.print(' ');
+        pw.print(makeProcStateString(proc.mState.getCurProcState()));
+        pw.print(' ');
+        ActivityManager.printCapabilitiesSummary(pw, proc.mState.getCurCapability());
+        pw.print(' ');
+        pw.print(proc.toShortString());
+        final ProcessServiceRecord psr = proc.mServices;
+        if (proc.hasActivitiesOrRecentTasks() || psr.hasClientActivities()
+                || psr.isTreatedLikeActivity()) {
+            pw.print(" act:");
+            boolean printed = false;
+            if (proc.hasActivities()) {
+                pw.print("activities");
+                printed = true;
+            }
+            if (proc.hasRecentTasks()) {
+                if (printed) {
+                    pw.print("|");
+                }
+                pw.print("recents");
+                printed = true;
+            }
+            if (psr.hasClientActivities()) {
+                if (printed) {
+                    pw.print("|");
+                }
+                pw.print("client");
+                printed = true;
+            }
+            if (psr.isTreatedLikeActivity()) {
+                if (printed) {
+                    pw.print("|");
+                }
+                pw.print("treated");
+            }
+        }
+        pw.println();
+    }
+
+    @GuardedBy("mService")
+    boolean dumpLruLocked(PrintWriter pw, String dumpPackage, String prefix) {
+        final int lruSize = mLruProcesses.size();
+        final String innerPrefix;
+        if (prefix == null) {
+            pw.println("ACTIVITY MANAGER LRU PROCESSES (dumpsys activity lru)");
+            innerPrefix = "  ";
+        } else {
+            boolean haveAny = false;
+            for (int i = lruSize - 1; i >= 0; i--) {
+                final ProcessRecord r = mLruProcesses.get(i);
+                if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                    continue;
+                }
+                haveAny = true;
+                break;
+            }
+            if (!haveAny) {
+                return false;
+            }
+            pw.print(prefix);
+            pw.println("Raw LRU list (dumpsys activity lru):");
+            innerPrefix = prefix + "  ";
+        }
+        int i;
+        boolean first = true;
+        for (i = lruSize - 1; i >= mLruProcessActivityStart; i--) {
+            final ProcessRecord r = mLruProcesses.get(i);
+            if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                continue;
+            }
+            if (first) {
+                pw.print(innerPrefix);
+                pw.println("Activities:");
+                first = false;
+            }
+            dumpLruEntryLocked(pw, i, r, innerPrefix);
+        }
+        first = true;
+        for (; i >= mLruProcessServiceStart; i--) {
+            final ProcessRecord r = mLruProcesses.get(i);
+            if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                continue;
+            }
+            if (first) {
+                pw.print(innerPrefix);
+                pw.println("Services:");
+                first = false;
+            }
+            dumpLruEntryLocked(pw, i, r, innerPrefix);
+        }
+        first = true;
+        for (; i >= 0; i--) {
+            final ProcessRecord r = mLruProcesses.get(i);
+            if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                continue;
+            }
+            if (first) {
+                pw.print(innerPrefix);
+                pw.println("Other:");
+                first = false;
+            }
+            dumpLruEntryLocked(pw, i, r, innerPrefix);
+        }
+        return true;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void dumpProcessesLSP(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
+        boolean needSep = false;
+        int numPers = 0;
+
+        pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
+
+        if (dumpAll || dumpPackage != null) {
+            final int numOfNames = mProcessNames.getMap().size();
+            for (int ip = 0; ip < numOfNames; ip++) {
+                SparseArray<ProcessRecord> procs = mProcessNames.getMap().valueAt(ip);
+                for (int ia = 0, size = procs.size(); ia < size; ia++) {
+                    ProcessRecord r = procs.valueAt(ia);
+                    if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                        continue;
+                    }
+                    if (!needSep) {
+                        pw.println("  All known processes:");
+                        needSep = true;
+                    }
+                    pw.print(r.isPersistent() ? "  *PERS*" : "  *APP*");
+                    pw.print(" UID "); pw.print(procs.keyAt(ia));
+                    pw.print(" "); pw.println(r);
+                    r.dump(pw, "    ");
+                    if (r.isPersistent()) {
+                        numPers++;
+                    }
+                }
+            }
+        }
+
+        if (mIsolatedProcesses.size() > 0) {
+            boolean printed = false;
+            for (int i = 0, size = mIsolatedProcesses.size(); i < size; i++) {
+                ProcessRecord r = mIsolatedProcesses.valueAt(i);
+                if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    pw.println("  Isolated process list (sorted by uid):");
+                    printed = true;
+                    needSep = true;
+                }
+                pw.print("    Isolated #"); pw.print(i); pw.print(": ");
+                pw.println(r);
+            }
+        }
+
+        needSep = mService.dumpActiveInstruments(pw, dumpPackage, needSep);
+
+        if (dumpOomLocked(fd, pw, needSep, args, opti, dumpAll, dumpPackage, false)) {
+            needSep = true;
+        }
+
+        if (mActiveUids.size() > 0) {
+            needSep |= mActiveUids.dump(pw, dumpPackage, dumpAppId,
+                    "UID states:", needSep);
+        }
+
+        if (dumpAll) {
+            needSep |= mService.mUidObserverController.dumpValidateUids(pw,
+                    dumpPackage, dumpAppId, "UID validation:", needSep);
+        }
+
+        if (needSep) {
+            pw.println();
+        }
+        if (dumpLruLocked(pw, dumpPackage, "  ")) {
+            needSep = true;
+        }
+
+        if (getLruSizeLOSP() > 0) {
+            if (needSep) {
+                pw.println();
+            }
+            dumpLruListHeaderLocked(pw);
+            dumpProcessOomList(pw, mService, mLruProcesses, "    ", "Proc", "PERS", false,
+                    dumpPackage);
+            needSep = true;
+        }
+
+        mService.dumpOtherProcessesInfoLSP(fd, pw, dumpAll, dumpPackage, dumpAppId, numPers,
+                needSep);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void writeProcessesToProtoLSP(ProtoOutputStream proto, String dumpPackage) {
+        int numPers = 0;
+
+        final int numOfNames = mProcessNames.getMap().size();
+        for (int ip = 0; ip < numOfNames; ip++) {
+            SparseArray<ProcessRecord> procs = mProcessNames.getMap().valueAt(ip);
+            for (int ia = 0, size = procs.size(); ia < size; ia++) {
+                ProcessRecord r = procs.valueAt(ia);
+                if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                    continue;
+                }
+                r.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.PROCS,
+                        mLruProcesses.indexOf(r)
+                );
+                if (r.isPersistent()) {
+                    numPers++;
+                }
+            }
+        }
+
+        for (int i = 0, size = mIsolatedProcesses.size(); i < size; i++) {
+            ProcessRecord r = mIsolatedProcesses.valueAt(i);
+            if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                continue;
+            }
+            r.dumpDebug(proto, ActivityManagerServiceDumpProcessesProto.ISOLATED_PROCS,
+                    mLruProcesses.indexOf(r)
+            );
+        }
+
+        final int dumpAppId = mService.getAppId(dumpPackage);
+        mActiveUids.dumpProto(proto, dumpPackage, dumpAppId,
+                ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
+
+        if (getLruSizeLOSP() > 0) {
+            long lruToken = proto.start(ActivityManagerServiceDumpProcessesProto.LRU_PROCS);
+            int total = getLruSizeLOSP();
+            proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.SIZE, total);
+            proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.NON_ACT_AT,
+                    total - mLruProcessActivityStart);
+            proto.write(ActivityManagerServiceDumpProcessesProto.LruProcesses.NON_SVC_AT,
+                    total - mLruProcessServiceStart);
+            writeProcessOomListToProto(proto,
+                    ActivityManagerServiceDumpProcessesProto.LruProcesses.LIST, mService,
+                    mLruProcesses, false, dumpPackage);
+            proto.end(lruToken);
+        }
+
+        mService.writeOtherProcessesInfoToProtoLSP(proto, dumpPackage, dumpAppId, numPers);
+    }
+
+    private static ArrayList<Pair<ProcessRecord, Integer>> sortProcessOomList(
+            List<ProcessRecord> origList, String dumpPackage) {
+        ArrayList<Pair<ProcessRecord, Integer>> list =
+                new ArrayList<Pair<ProcessRecord, Integer>>(origList.size());
+        for (int i = 0, size = origList.size(); i < size; i++) {
+            ProcessRecord r = origList.get(i);
+            if (dumpPackage != null && !r.getPkgList().containsKey(dumpPackage)) {
+                continue;
+            }
+            list.add(new Pair<ProcessRecord, Integer>(origList.get(i), i));
+        }
+
+        Comparator<Pair<ProcessRecord, Integer>> comparator =
+                new Comparator<Pair<ProcessRecord, Integer>>() {
+            @Override
+            public int compare(Pair<ProcessRecord, Integer> object1,
+                    Pair<ProcessRecord, Integer> object2) {
+                final int adj = object2.first.mState.getSetAdj() - object1.first.mState.getSetAdj();
+                if (adj != 0) {
+                    return adj;
+                }
+                final int procState = object2.first.mState.getSetProcState()
+                        - object1.first.mState.getSetProcState();
+                if (procState != 0) {
+                    return procState;
+                }
+                final int val = object2.second - object1.second;
+                if (val != 0) {
+                    return val;
+                }
+                return 0;
+            }
+        };
+
+        Collections.sort(list, comparator);
+        return list;
+    }
+
+    private static boolean writeProcessOomListToProto(ProtoOutputStream proto, long fieldId,
+            ActivityManagerService service, List<ProcessRecord> origList,
+            boolean inclDetails, String dumpPackage) {
+        ArrayList<Pair<ProcessRecord, Integer>> list = sortProcessOomList(origList, dumpPackage);
+        if (list.isEmpty()) return false;
+
+        final long curUptime = SystemClock.uptimeMillis();
+
+        for (int i = list.size() - 1; i >= 0; i--) {
+            ProcessRecord r = list.get(i).first;
+            final ProcessStateRecord state = r.mState;
+            final ProcessServiceRecord psr = r.mServices;
+            long token = proto.start(fieldId);
+            String oomAdj = makeOomAdjString(state.getSetAdj(), true);
+            proto.write(ProcessOomProto.PERSISTENT, r.isPersistent());
+            proto.write(ProcessOomProto.NUM, (origList.size() - 1) - list.get(i).second);
+            proto.write(ProcessOomProto.OOM_ADJ, oomAdj);
+            int schedGroup = ProcessOomProto.SCHED_GROUP_UNKNOWN;
+            switch (state.getSetSchedGroup()) {
+                case SCHED_GROUP_BACKGROUND:
+                    schedGroup = ProcessOomProto.SCHED_GROUP_BACKGROUND;
+                    break;
+                case SCHED_GROUP_DEFAULT:
+                    schedGroup = ProcessOomProto.SCHED_GROUP_DEFAULT;
+                    break;
+                case SCHED_GROUP_TOP_APP:
+                    schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP;
+                    break;
+                case SCHED_GROUP_TOP_APP_BOUND:
+                    schedGroup = ProcessOomProto.SCHED_GROUP_TOP_APP_BOUND;
+                    break;
+            }
+            if (schedGroup != ProcessOomProto.SCHED_GROUP_UNKNOWN) {
+                proto.write(ProcessOomProto.SCHED_GROUP, schedGroup);
+            }
+            if (state.hasForegroundActivities()) {
+                proto.write(ProcessOomProto.ACTIVITIES, true);
+            } else if (psr.hasForegroundServices()) {
+                proto.write(ProcessOomProto.SERVICES, true);
+            }
+            proto.write(ProcessOomProto.STATE,
+                    makeProcStateProtoEnum(state.getCurProcState()));
+            proto.write(ProcessOomProto.TRIM_MEMORY_LEVEL, r.mProfile.getTrimMemoryLevel());
+            r.dumpDebug(proto, ProcessOomProto.PROC);
+            proto.write(ProcessOomProto.ADJ_TYPE, state.getAdjType());
+            if (state.getAdjSource() != null || state.getAdjTarget() != null) {
+                if (state.getAdjTarget() instanceof  ComponentName) {
+                    ComponentName cn = (ComponentName) state.getAdjTarget();
+                    cn.dumpDebug(proto, ProcessOomProto.ADJ_TARGET_COMPONENT_NAME);
+                } else if (state.getAdjTarget() != null) {
+                    proto.write(ProcessOomProto.ADJ_TARGET_OBJECT, state.getAdjTarget().toString());
+                }
+                if (state.getAdjSource() instanceof ProcessRecord) {
+                    ProcessRecord p = (ProcessRecord) state.getAdjSource();
+                    p.dumpDebug(proto, ProcessOomProto.ADJ_SOURCE_PROC);
+                } else if (state.getAdjSource() != null) {
+                    proto.write(ProcessOomProto.ADJ_SOURCE_OBJECT, state.getAdjSource().toString());
+                }
+            }
+            if (inclDetails) {
+                long detailToken = proto.start(ProcessOomProto.DETAIL);
+                proto.write(ProcessOomProto.Detail.MAX_ADJ, state.getMaxAdj());
+                proto.write(ProcessOomProto.Detail.CUR_RAW_ADJ, state.getCurRawAdj());
+                proto.write(ProcessOomProto.Detail.SET_RAW_ADJ, state.getSetRawAdj());
+                proto.write(ProcessOomProto.Detail.CUR_ADJ, state.getCurAdj());
+                proto.write(ProcessOomProto.Detail.SET_ADJ, state.getSetAdj());
+                proto.write(ProcessOomProto.Detail.CURRENT_STATE,
+                        makeProcStateProtoEnum(state.getCurProcState()));
+                proto.write(ProcessOomProto.Detail.SET_STATE,
+                        makeProcStateProtoEnum(state.getSetProcState()));
+                proto.write(ProcessOomProto.Detail.LAST_PSS, DebugUtils.sizeValueToString(
+                        r.mProfile.getLastPss() * 1024, new StringBuilder()));
+                proto.write(ProcessOomProto.Detail.LAST_SWAP_PSS, DebugUtils.sizeValueToString(
+                        r.mProfile.getLastSwapPss() * 1024, new StringBuilder()));
+                proto.write(ProcessOomProto.Detail.LAST_CACHED_PSS, DebugUtils.sizeValueToString(
+                        r.mProfile.getLastCachedPss() * 1024, new StringBuilder()));
+                proto.write(ProcessOomProto.Detail.CACHED, state.isCached());
+                proto.write(ProcessOomProto.Detail.EMPTY, state.isEmpty());
+                proto.write(ProcessOomProto.Detail.HAS_ABOVE_CLIENT, psr.hasAboveClient());
+
+                if (state.getSetProcState() >= ActivityManager.PROCESS_STATE_SERVICE) {
+                    long lastCpuTime = r.mProfile.mLastCpuTime.get();
+                    if (lastCpuTime != 0) {
+                        long uptimeSince = curUptime - service.mLastPowerCheckUptime;
+                        long timeUsed = r.mProfile.mCurCpuTime.get() - lastCpuTime;
+                        long cpuTimeToken = proto.start(ProcessOomProto.Detail.SERVICE_RUN_TIME);
+                        proto.write(ProcessOomProto.Detail.CpuRunTime.OVER_MS, uptimeSince);
+                        proto.write(ProcessOomProto.Detail.CpuRunTime.USED_MS, timeUsed);
+                        proto.write(ProcessOomProto.Detail.CpuRunTime.ULTILIZATION,
+                                (100.0 * timeUsed) / uptimeSince);
+                        proto.end(cpuTimeToken);
+                    }
+                }
+                proto.end(detailToken);
+            }
+            proto.end(token);
+        }
+
+        return true;
+    }
+
+    private static boolean dumpProcessOomList(PrintWriter pw,
+            ActivityManagerService service, List<ProcessRecord> origList,
+            String prefix, String normalLabel, String persistentLabel,
+            boolean inclDetails, String dumpPackage) {
+
+        ArrayList<Pair<ProcessRecord, Integer>> list = sortProcessOomList(origList, dumpPackage);
+        if (list.isEmpty()) return false;
+
+        final long curUptime = SystemClock.uptimeMillis();
+        final long uptimeSince = curUptime - service.mLastPowerCheckUptime;
+
+        for (int i = list.size() - 1; i >= 0; i--) {
+            ProcessRecord r = list.get(i).first;
+            final ProcessStateRecord state = r.mState;
+            final ProcessServiceRecord psr = r.mServices;
+            String oomAdj = makeOomAdjString(state.getSetAdj(), false);
+            char schedGroup;
+            switch (state.getSetSchedGroup()) {
+                case SCHED_GROUP_BACKGROUND:
+                    schedGroup = 'b';
+                    break;
+                case SCHED_GROUP_DEFAULT:
+                    schedGroup = 'F';
+                    break;
+                case SCHED_GROUP_TOP_APP:
+                    schedGroup = 'T';
+                    break;
+                case SCHED_GROUP_RESTRICTED:
+                    schedGroup = 'R';
+                    break;
+                case SCHED_GROUP_TOP_APP_BOUND:
+                    schedGroup = 'B';
+                    break;
+                default:
+                    schedGroup = '?';
+                    break;
+            }
+            char foreground;
+            if (state.hasForegroundActivities()) {
+                foreground = 'A';
+            } else if (psr.hasForegroundServices()) {
+                foreground = 'S';
+            } else {
+                foreground = ' ';
+            }
+            String procState = makeProcStateString(state.getCurProcState());
+            pw.print(prefix);
+            pw.print(r.isPersistent() ? persistentLabel : normalLabel);
+            pw.print(" #");
+            int num = (origList.size() - 1) - list.get(i).second;
+            if (num < 10) pw.print(' ');
+            pw.print(num);
+            pw.print(": ");
+            pw.print(oomAdj);
+            pw.print(' ');
+            pw.print(schedGroup);
+            pw.print('/');
+            pw.print(foreground);
+            pw.print('/');
+            pw.print(procState);
+            pw.print(' ');
+            ActivityManager.printCapabilitiesSummary(pw, state.getCurCapability());
+            pw.print(' ');
+            pw.print(" t:");
+            if (r.mProfile.getTrimMemoryLevel() < 10) pw.print(' ');
+            pw.print(r.mProfile.getTrimMemoryLevel());
+            pw.print(' ');
+            pw.print(r.toShortString());
+            pw.print(" (");
+            pw.print(state.getAdjType());
+            pw.println(')');
+            if (state.getAdjSource() != null || state.getAdjTarget() != null) {
+                pw.print(prefix);
+                pw.print("    ");
+                if (state.getAdjTarget() instanceof ComponentName) {
+                    pw.print(((ComponentName) state.getAdjTarget()).flattenToShortString());
+                } else if (state.getAdjTarget() != null) {
+                    pw.print(state.getAdjTarget().toString());
+                } else {
+                    pw.print("{null}");
+                }
+                pw.print("<=");
+                if (state.getAdjSource() instanceof ProcessRecord) {
+                    pw.print("Proc{");
+                    pw.print(((ProcessRecord) state.getAdjSource()).toShortString());
+                    pw.println("}");
+                } else if (state.getAdjSource() != null) {
+                    pw.println(state.getAdjSource().toString());
+                } else {
+                    pw.println("{null}");
+                }
+            }
+            if (inclDetails) {
+                pw.print(prefix);
+                pw.print("    ");
+                pw.print("oom: max="); pw.print(state.getMaxAdj());
+                pw.print(" curRaw="); pw.print(state.getCurRawAdj());
+                pw.print(" setRaw="); pw.print(state.getSetRawAdj());
+                pw.print(" cur="); pw.print(state.getCurAdj());
+                pw.print(" set="); pw.println(state.getSetAdj());
+                pw.print(prefix);
+                pw.print("    ");
+                pw.print("state: cur="); pw.print(makeProcStateString(state.getCurProcState()));
+                pw.print(" set="); pw.print(makeProcStateString(state.getSetProcState()));
+                pw.print(" lastPss=");
+                DebugUtils.printSizeValue(pw, r.mProfile.getLastPss() * 1024);
+                pw.print(" lastSwapPss=");
+                DebugUtils.printSizeValue(pw, r.mProfile.getLastSwapPss() * 1024);
+                pw.print(" lastCachedPss=");
+                DebugUtils.printSizeValue(pw, r.mProfile.getLastCachedPss() * 1024);
+                pw.println();
+                pw.print(prefix);
+                pw.print("    ");
+                pw.print("cached="); pw.print(state.isCached());
+                pw.print(" empty="); pw.print(state.isEmpty());
+                pw.print(" hasAboveClient="); pw.println(psr.hasAboveClient());
+
+                if (state.getSetProcState() >= ActivityManager.PROCESS_STATE_SERVICE) {
+                    long lastCpuTime = r.mProfile.mLastCpuTime.get();
+                    if (lastCpuTime != 0) {
+                        long timeUsed = r.mProfile.mCurCpuTime.get() - lastCpuTime;
+                        pw.print(prefix);
+                        pw.print("    ");
+                        pw.print("run cpu over ");
+                        TimeUtils.formatDuration(uptimeSince, pw);
+                        pw.print(" used ");
+                        TimeUtils.formatDuration(timeUsed, pw);
+                        pw.print(" (");
+                        pw.print((timeUsed * 100) / uptimeSince);
+                        pw.println("%)");
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    private void printOomLevel(PrintWriter pw, String name, int adj) {
+        pw.print("    ");
+        if (adj >= 0) {
+            pw.print(' ');
+            if (adj < 10) pw.print(' ');
+        } else {
+            if (adj > -10) pw.print(' ');
+        }
+        pw.print(adj);
+        pw.print(": ");
+        pw.print(name);
+        pw.print(" (");
+        pw.print(ActivityManagerService.stringifySize(getMemLevel(adj), 1024));
+        pw.println(")");
+    }
+
+    @GuardedBy("mService")
+    boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, boolean needSep, String[] args,
+            int opti, boolean dumpAll, String dumpPackage, boolean inclGc) {
+        if (getLruSizeLOSP() > 0) {
+            if (needSep) pw.println();
+            needSep = true;
+            pw.println("  OOM levels:");
+            printOomLevel(pw, "SYSTEM_ADJ", SYSTEM_ADJ);
+            printOomLevel(pw, "PERSISTENT_PROC_ADJ", PERSISTENT_PROC_ADJ);
+            printOomLevel(pw, "PERSISTENT_SERVICE_ADJ", PERSISTENT_SERVICE_ADJ);
+            printOomLevel(pw, "FOREGROUND_APP_ADJ", FOREGROUND_APP_ADJ);
+            printOomLevel(pw, "VISIBLE_APP_ADJ", VISIBLE_APP_ADJ);
+            printOomLevel(pw, "PERCEPTIBLE_APP_ADJ", PERCEPTIBLE_APP_ADJ);
+            printOomLevel(pw, "PERCEPTIBLE_LOW_APP_ADJ", PERCEPTIBLE_LOW_APP_ADJ);
+            printOomLevel(pw, "BACKUP_APP_ADJ", BACKUP_APP_ADJ);
+            printOomLevel(pw, "HEAVY_WEIGHT_APP_ADJ", HEAVY_WEIGHT_APP_ADJ);
+            printOomLevel(pw, "SERVICE_ADJ", SERVICE_ADJ);
+            printOomLevel(pw, "HOME_APP_ADJ", HOME_APP_ADJ);
+            printOomLevel(pw, "PREVIOUS_APP_ADJ", PREVIOUS_APP_ADJ);
+            printOomLevel(pw, "SERVICE_B_ADJ", SERVICE_B_ADJ);
+            printOomLevel(pw, "CACHED_APP_MIN_ADJ", CACHED_APP_MIN_ADJ);
+            printOomLevel(pw, "CACHED_APP_MAX_ADJ", CACHED_APP_MAX_ADJ);
+
+            if (needSep) pw.println();
+            pw.print("  Process OOM control ("); pw.print(getLruSizeLOSP());
+            pw.print(" total, non-act at ");
+            pw.print(getLruSizeLOSP() - mLruProcessActivityStart);
+            pw.print(", non-svc at ");
+            pw.print(getLruSizeLOSP() - mLruProcessServiceStart);
+            pw.println("):");
+            dumpProcessOomList(pw, mService, mLruProcesses,
+                    "    ", "Proc", "PERS", true, dumpPackage);
+            needSep = true;
+        }
+
+        synchronized (mService.mAppProfiler.mProfilerLock) {
+            mService.mAppProfiler.dumpProcessesToGc(pw, needSep, dumpPackage);
+        }
+
+        pw.println();
+        mService.mAtmInternal.dumpForOom(pw);
+
+        return true;
+    }
+
+    void registerProcessObserver(IProcessObserver observer) {
+        mProcessObservers.register(observer);
+    }
+
+    void unregisterProcessObserver(IProcessObserver observer) {
+        mProcessObservers.unregister(observer);
+    }
+
+    void dispatchProcessesChanged() {
+        int numOfChanges;
+        synchronized (mProcessChangeLock) {
+            numOfChanges = mPendingProcessChanges.size();
+            if (mActiveProcessChanges.length < numOfChanges) {
+                mActiveProcessChanges = new ProcessChangeItem[numOfChanges];
+            }
+            mPendingProcessChanges.toArray(mActiveProcessChanges);
+            mPendingProcessChanges.clear();
+            if (DEBUG_PROCESS_OBSERVERS) {
+                Slog.i(TAG_PROCESS_OBSERVERS,
+                        "*** Delivering " + numOfChanges + " process changes");
+            }
+        }
+
+        int i = mProcessObservers.beginBroadcast();
+        while (i > 0) {
+            i--;
+            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+            if (observer != null) {
+                try {
+                    for (int j = 0; j < numOfChanges; j++) {
+                        ProcessChangeItem item = mActiveProcessChanges[j];
+                        if ((item.changes & ProcessChangeItem.CHANGE_ACTIVITIES) != 0) {
+                            if (DEBUG_PROCESS_OBSERVERS) {
+                                Slog.i(TAG_PROCESS_OBSERVERS,
+                                        "ACTIVITIES CHANGED pid=" + item.pid + " uid="
+                                        + item.uid + ": " + item.foregroundActivities);
+                            }
+                            observer.onForegroundActivitiesChanged(item.pid, item.uid,
+                                    item.foregroundActivities);
+                        }
+                        if ((item.changes & ProcessChangeItem.CHANGE_FOREGROUND_SERVICES) != 0) {
+                            if (DEBUG_PROCESS_OBSERVERS) {
+                                Slog.i(TAG_PROCESS_OBSERVERS,
+                                        "FOREGROUND SERVICES CHANGED pid=" + item.pid + " uid="
+                                        + item.uid + ": " + item.foregroundServiceTypes);
+                            }
+                            observer.onForegroundServicesChanged(item.pid, item.uid,
+                                    item.foregroundServiceTypes);
+                        }
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        mProcessObservers.finishBroadcast();
+
+        synchronized (mProcessChangeLock) {
+            for (int j = 0; j < numOfChanges; j++) {
+                mAvailProcessChanges.add(mActiveProcessChanges[j]);
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) {
+        synchronized (mProcessChangeLock) {
+            int i = mPendingProcessChanges.size() - 1;
+            ActivityManagerService.ProcessChangeItem item = null;
+            while (i >= 0) {
+                item = mPendingProcessChanges.get(i);
+                if (item.pid == pid) {
+                    if (DEBUG_PROCESS_OBSERVERS) {
+                        Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item);
+                    }
+                    break;
+                }
+                i--;
+            }
+
+            if (i < 0) {
+                // No existing item in pending changes; need a new one.
+                final int num = mAvailProcessChanges.size();
+                if (num > 0) {
+                    item = mAvailProcessChanges.remove(num - 1);
+                    if (DEBUG_PROCESS_OBSERVERS) {
+                        Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item);
+                    }
+                } else {
+                    item = new ActivityManagerService.ProcessChangeItem();
+                    if (DEBUG_PROCESS_OBSERVERS) {
+                        Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item);
+                    }
+                }
+                item.changes = 0;
+                item.pid = pid;
+                item.uid = uid;
+                if (mPendingProcessChanges.size() == 0) {
+                    if (DEBUG_PROCESS_OBSERVERS) {
+                        Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!");
+                    }
+                    mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
+                            .sendToTarget();
+                }
+                mPendingProcessChanges.add(item);
+            }
+
+            return item;
+        }
+    }
+
+    @GuardedBy("mService")
+    void scheduleDispatchProcessDiedLocked(int pid, int uid) {
+        synchronized (mProcessChangeLock) {
+            for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
+                ProcessChangeItem item = mPendingProcessChanges.get(i);
+                if (pid > 0 && item.pid == pid) {
+                    mPendingProcessChanges.remove(i);
+                    mAvailProcessChanges.add(item);
+                }
+            }
+            mService.mUiHandler.obtainMessage(DISPATCH_PROCESS_DIED_UI_MSG, pid, uid,
+                    null).sendToTarget();
+        }
+    }
+
+    void dispatchProcessDied(int pid, int uid) {
+        int i = mProcessObservers.beginBroadcast();
+        while (i > 0) {
+            i--;
+            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+            if (observer != null) {
+                try {
+                    observer.onProcessDied(pid, uid);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        mProcessObservers.finishBroadcast();
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ArrayList<ProcessRecord> collectProcessesLOSP(int start, boolean allPkgs, String[] args) {
         ArrayList<ProcessRecord> procs;
         if (args != null && args.length > start
                 && args[start].charAt(0) != '-') {
@@ -3666,10 +4638,10 @@
             }
             for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
                 ProcessRecord proc = mLruProcesses.get(i);
-                if (proc.pid > 0 && proc.pid == pid) {
+                if (proc.getPid() > 0 && proc.getPid() == pid) {
                     procs.add(proc);
-                } else if (allPkgs && proc.pkgList != null
-                        && proc.pkgList.containsKey(args[start])) {
+                } else if (allPkgs && proc.getPkgList() != null
+                        && proc.getPkgList().containsKey(args[start])) {
                     procs.add(proc);
                 } else if (proc.processName.equals(args[start])) {
                     procs.add(proc);
@@ -3684,12 +4656,12 @@
         return procs;
     }
 
-    @GuardedBy("mService")
-    void updateApplicationInfoLocked(List<String> packagesToUpdate, int userId,
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    void updateApplicationInfoLOSP(List<String> packagesToUpdate, int userId,
             boolean updateFrameworkRes) {
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             final ProcessRecord app = mLruProcesses.get(i);
-            if (app.thread == null) {
+            if (app.getThread() == null) {
                 continue;
             }
 
@@ -3697,27 +4669,23 @@
                 continue;
             }
 
-            final int packageCount = app.pkgList.size();
-            for (int j = 0; j < packageCount; j++) {
-                final String packageName = app.pkgList.keyAt(j);
-                if (!updateFrameworkRes && !packagesToUpdate.contains(packageName)) {
-                    continue;
-                }
-                try {
-                    final ApplicationInfo ai = AppGlobals.getPackageManager()
-                            .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
-                    if (ai == null) {
-                        continue;
+            app.getPkgList().forEachPackage(packageName -> {
+                if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
+                    try {
+                        final ApplicationInfo ai = AppGlobals.getPackageManager()
+                                .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
+                        if (ai != null) {
+                            app.getThread().scheduleApplicationInfoChanged(ai);
+                            if (ai.packageName.equals(app.info.packageName)) {
+                                app.info = ai;
+                            }
+                        }
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
+                                    packageName, app));
                     }
-                    app.thread.scheduleApplicationInfoChanged(ai);
-                    if (ai.packageName.equals(app.info.packageName)) {
-                        app.info = ai;
-                    }
-                } catch (RemoteException e) {
-                    Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
-                            packageName, app));
                 }
-            }
+            });
         }
     }
 
@@ -3726,14 +4694,15 @@
         boolean foundProcess = false;
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord r = mLruProcesses.get(i);
-            if (r.thread != null && (userId == UserHandle.USER_ALL || r.userId == userId)) {
+            final IApplicationThread thread = r.getThread();
+            if (thread != null && (userId == UserHandle.USER_ALL || r.userId == userId)) {
                 try {
                     for (int index = packages.length - 1; index >= 0 && !foundProcess; index--) {
                         if (packages[index].equals(r.info.packageName)) {
                             foundProcess = true;
                         }
                     }
-                    r.thread.dispatchPackageBroadcast(cmd, packages);
+                    thread.dispatchPackageBroadcast(cmd, packages);
                 } catch (RemoteException ex) {
                 }
             }
@@ -3748,15 +4717,15 @@
     }
 
     /** Returns the uid's process state or PROCESS_STATE_NONEXISTENT if not running */
-    @GuardedBy("mService")
-    int getUidProcStateLocked(int uid) {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getUidProcStateLOSP(int uid) {
         UidRecord uidRec = mActiveUids.get(uid);
         return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState();
     }
 
     /** Returns the UidRecord for the given uid, if it exists. */
-    @GuardedBy("mService")
-    UidRecord getUidRecordLocked(int uid) {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    UidRecord getUidRecordLOSP(int uid) {
         return mActiveUids.get(uid);
     }
 
@@ -3773,10 +4742,10 @@
                 continue;
             }
             final UidRecord uidRec = mActiveUids.valueAt(i);
-            if (!uidRec.idle) {
+            if (!uidRec.isIdle()) {
                 continue;
             }
-            mService.doStopUidLocked(uidRec.uid, uidRec);
+            mService.doStopUidLocked(uidRec.getUid(), uidRec);
         }
     }
 
@@ -3790,14 +4759,16 @@
      *         {@link #NETWORK_STATE_NO_CHANGE}.
      */
     @VisibleForTesting
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
     int getBlockStateForUid(UidRecord uidRec) {
         // Denotes whether uid's process state is currently allowed network access.
         final boolean isAllowed =
                 isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getCurProcState())
                 || isProcStateAllowedWhileOnRestrictBackground(uidRec.getCurProcState());
         // Denotes whether uid's process state was previously allowed network access.
-        final boolean wasAllowed = isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.setProcState)
-                || isProcStateAllowedWhileOnRestrictBackground(uidRec.setProcState);
+        final boolean wasAllowed =
+                isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getSetProcState())
+                || isProcStateAllowedWhileOnRestrictBackground(uidRec.getSetProcState());
 
         // When the uid is coming to foreground, AMS should inform the app thread that it should
         // block for the network rules to get updated before launching an activity.
@@ -3818,8 +4789,8 @@
      * {@link ProcessList#mProcStateSeqCounter} and notifies the app if it needs to block.
      */
     @VisibleForTesting
-    @GuardedBy("mService")
-    void incrementProcStateSeqAndNotifyAppsLocked(ActiveUids activeUids) {
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    void incrementProcStateSeqAndNotifyAppsLOSP(ActiveUids activeUids) {
         if (mService.mWaitForNetworkTimeoutMs <= 0) {
             return;
         }
@@ -3828,14 +4799,14 @@
         for (int i = activeUids.size() - 1; i >= 0; --i) {
             final UidRecord uidRec = activeUids.valueAt(i);
             // If the network is not restricted for uid, then nothing to do here.
-            if (!mService.mInjector.isNetworkRestrictedForUid(uidRec.uid)) {
+            if (!mService.mInjector.isNetworkRestrictedForUid(uidRec.getUid())) {
                 continue;
             }
-            if (!UserHandle.isApp(uidRec.uid) || !uidRec.hasInternetPermission) {
+            if (!UserHandle.isApp(uidRec.getUid()) || !uidRec.hasInternetPermission) {
                 continue;
             }
             // If process state is not changed, then there's nothing to do.
-            if (uidRec.setProcState == uidRec.getCurProcState()) {
+            if (uidRec.getSetProcState() == uidRec.getCurProcState()) {
                 continue;
             }
             final int blockState = getBlockStateForUid(uidRec);
@@ -3850,7 +4821,7 @@
                     if (blockingUids == null) {
                         blockingUids = new ArrayList<>();
                     }
-                    blockingUids.add(uidRec.uid);
+                    blockingUids.add(uidRec.getUid());
                 } else {
                     if (DEBUG_NETWORK) {
                         Slog.d(TAG_NETWORK, "uid going to background, notifying all blocking"
@@ -3873,15 +4844,16 @@
             if (!blockingUids.contains(app.uid)) {
                 continue;
             }
-            if (!app.killedByAm && app.thread != null) {
-                final UidRecord uidRec = getUidRecordLocked(app.uid);
+            final IApplicationThread thread = app.getThread();
+            if (!app.isKilledByAm() && thread != null) {
+                final UidRecord uidRec = getUidRecordLOSP(app.uid);
                 try {
                     if (DEBUG_NETWORK) {
                         Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: "
                                 + uidRec);
                     }
                     if (uidRec != null) {
-                        app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+                        thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
                     }
                 } catch (RemoteException ignored) {
                 }
@@ -3953,7 +4925,7 @@
             Slog.i(TAG, "note: " + app + " died, saving the exit info");
         }
 
-        Watchdog.getInstance().processDied(app.processName, app.pid);
+        Watchdog.getInstance().processDied(app.processName, app.getPid());
         mAppExitInfoTracker.scheduleNoteProcessDied(app);
     }
 
@@ -4075,10 +5047,10 @@
             }
 
             final Bundle bundle = new Bundle();
-            bundle.putInt(EXTRA_PID, app.pid);
+            bundle.putInt(EXTRA_PID, app.getPid());
             bundle.putInt(EXTRA_UID, app.uid);
             // Since the pid could be reused, let's get the actual start time of each process
-            bundle.putLong(EXTRA_TIMESTAMP, app.startTime);
+            bundle.putLong(EXTRA_TIMESTAMP, app.getStartTime());
             bundle.putString(EXTRA_REASON, reason);
             bundle.putInt(EXTRA_REQUESTER, requester);
             List<Bundle> list = mWorkItems.get(app.uid);
@@ -4103,7 +5075,7 @@
             boolean diff = mIdle != idle;
             mIdle = idle;
             if (diff && idle) {
-                synchronized (this) {
+                synchronized (mService) {
                     if (mWorkItems.size() > 0) {
                         mHandler.sendEmptyMessage(H.MSG_DEVICE_IDLE);
                     }
@@ -4170,28 +5142,30 @@
                 app = mService.mPidsSelfLocked.get(pid);
             }
 
-            if (app == null || app.pid != pid || app.uid != uid || app.startTime != timestamp) {
+            if (app == null || app.getPid() != pid || app.uid != uid
+                    || app.getStartTime() != timestamp) {
                 // This process record has been reused for another process, meaning the old process
                 // has been gone.
                 return true;
             }
 
-            final int pkgSize = app.pkgList.size();
-            for (int ip = 0; ip < pkgSize; ip++) {
-                if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.contains(
-                        app.pkgList.keyAt(ip))) {
+            if (app.getPkgList().searchEachPackage(pkgName -> {
+                if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.contains(pkgName)) {
                     // One of the packages in this process is exempted
-                    return true;
+                    return Boolean.TRUE;
                 }
+                return null;
+            }) != null) {
+                return true;
             }
 
             if (mService.mConstants.IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.contains(
-                    app.getReportedProcState())) {
+                    app.mState.getReportedProcState())) {
                 // We need to reschedule it.
                 return false;
             }
 
-            app.kill(reason, ApplicationExitInfo.REASON_OTHER,
+            app.killLocked(reason, ApplicationExitInfo.REASON_OTHER,
                     ApplicationExitInfo.SUBREASON_IMPERCEPTIBLE, true);
 
             if (!app.isolated) {
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
new file mode 100644
index 0000000..e533cc3
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManager.processStateAmToProto;
+
+import android.app.IApplicationThread;
+import android.content.pm.ApplicationInfo;
+import android.os.Debug;
+import android.os.SystemClock;
+import android.util.DebugUtils;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.ProcessState;
+import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.am.ProcessList.ProcStateMemTracker;
+
+import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Profiling info of the process, such as PSS, cpu, etc.
+ */
+final class ProcessProfileRecord {
+    final ProcessRecord mApp;
+
+    private final ActivityManagerService mService;
+
+    final Object mProfilerLock;
+
+    @GuardedBy("mProfilerLock")
+    private final ProcessList.ProcStateMemTracker mProcStateMemTracker =
+            new ProcessList.ProcStateMemTracker();
+
+    /**
+     * Stats of pss, cpu, etc.
+     */
+    @GuardedBy("mService.mProcessStats.mLock")
+    private ProcessState mBaseProcessTracker;
+
+    /**
+     * Last time we retrieved PSS data.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastPssTime;
+
+    /**
+     * Next time we want to request PSS data.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mNextPssTime;
+
+    /**
+     * Initial memory pss of process for idle maintenance.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mInitialIdlePss;
+
+    /**
+     * Last computed memory pss.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastPss;
+
+    /**
+     * Last computed SwapPss.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastSwapPss;
+
+    /**
+     * Last computed pss when in cached state.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastCachedPss;
+
+    /**
+     * Last computed SwapPss when in cached state.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastCachedSwapPss;
+
+    /**
+     * Last computed memory rss.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastRss;
+
+    /**
+     * Cache of last retrieve memory info, to throttle how frequently apps can request it.
+     */
+    @GuardedBy("mProfilerLock")
+    private Debug.MemoryInfo mLastMemInfo;
+
+    /**
+     * Cache of last retrieve memory uptime, to throttle how frequently apps can request it.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastMemInfoTime;
+
+    /**
+     * Currently requesting pss for.
+     */
+    @GuardedBy("mProfilerLock")
+    private int mPssProcState = PROCESS_STATE_NONEXISTENT;
+
+    /**
+     * The type of stat collection that we are currently requesting.
+     */
+    @GuardedBy("mProfilerLock")
+    private int mPssStatType;
+
+    /**
+     * How long proc has run CPU at last check.
+     */
+    final AtomicLong mLastCpuTime = new AtomicLong(0);
+
+    /**
+     * How long proc has run CPU most recently.
+     */
+    final AtomicLong mCurCpuTime = new AtomicLong(0);
+
+    /**
+     * Last selected memory trimming level.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mTrimMemoryLevel;
+
+    /**
+     * Want to clean up resources from showing UI?
+     */
+    @GuardedBy("mProcLock")
+    private boolean mPendingUiClean;
+
+    /**
+     * Pointer to the battery stats of this process.
+     */
+    private BatteryStatsImpl.Uid.Proc mCurProcBatteryStats;
+
+    /**
+     * When we last asked the app to do a gc.
+     */
+    @GuardedBy("mProfilerLock")
+    private long mLastRequestedGc;
+
+    /**
+     * When we last told the app that memory is low.
+     */
+    @CompositeRWLock({"mService", "mProfilerLock"})
+    private long mLastLowMemory;
+
+    /**
+     * Set to true when waiting to report low mem.
+     */
+    @GuardedBy("mProfilerLock")
+    private boolean mReportLowMemory;
+
+    // ========================================================================
+    // Local copies of some process info, to avoid holding global AMS lock
+    @GuardedBy("mProfilerLock")
+    private int mPid;
+
+    @GuardedBy("mProfilerLock")
+    private IApplicationThread mThread;
+
+    @GuardedBy("mProfilerLock")
+    private int mSetProcState;
+
+    @GuardedBy("mProfilerLock")
+    private int mSetAdj;
+
+    @GuardedBy("mProfilerLock")
+    private int mCurRawAdj;
+
+    @GuardedBy("mProfilerLock")
+    private long mLastStateTime;
+
+    private final ActivityManagerGlobalLock mProcLock;
+
+    ProcessProfileRecord(final ProcessRecord app) {
+        mApp = app;
+        mService = app.mService;
+        mProcLock = mService.mProcLock;
+        mProfilerLock = mService.mAppProfiler.mProfilerLock;
+    }
+
+    void init(long now) {
+        mLastPssTime = mNextPssTime = now;
+    }
+
+    @GuardedBy("mService.mProcessStats.mLock")
+    ProcessState getBaseProcessTracker() {
+        return mBaseProcessTracker;
+    }
+
+    @GuardedBy("mService.mProcessStats.mLock")
+    void setBaseProcessTracker(ProcessState baseProcessTracker) {
+        mBaseProcessTracker = baseProcessTracker;
+    }
+
+    void onProcessActive(IApplicationThread thread, ProcessStatsService tracker) {
+        if (mThread == null) {
+            synchronized (mProfilerLock) {
+                synchronized (tracker.mLock) {
+                    final ProcessState origBase = getBaseProcessTracker();
+                    final PackageList pkgList = mApp.getPkgList();
+                    if (origBase != null) {
+                        synchronized (pkgList) {
+                            origBase.setState(ProcessStats.STATE_NOTHING,
+                                    tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
+                                    pkgList.getPackageListLocked());
+                            pkgList.forEachPackage((pkgName, holder) ->
+                                    FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+                                        mApp.uid, mApp.processName, pkgName,
+                                        processStateAmToProto(ProcessStats.STATE_NOTHING),
+                                        holder.appVersion)
+                            );
+                        }
+                        origBase.makeInactive();
+                    }
+                    final ApplicationInfo info = mApp.info;
+                    final ProcessState baseProcessTracker = tracker.getProcessStateLocked(
+                            info.packageName, info.uid, info.longVersionCode, mApp.processName);
+                    setBaseProcessTracker(baseProcessTracker);
+                    baseProcessTracker.makeActive();
+                    pkgList.forEachPackage((pkgName, holder) -> {
+                        if (holder.state != null && holder.state != origBase) {
+                            holder.state.makeInactive();
+                        }
+                        tracker.updateProcessStateHolderLocked(holder, pkgName, mApp.info.uid,
+                                mApp.info.longVersionCode, mApp.processName);
+                        if (holder.state != baseProcessTracker) {
+                            holder.state.makeActive();
+                        }
+                    });
+                    mThread = thread;
+                }
+            }
+        } else {
+            synchronized (mProfilerLock) {
+                mThread = thread;
+            }
+        }
+    }
+
+    void onProcessInactive(ProcessStatsService tracker) {
+        synchronized (mProfilerLock) {
+            synchronized (tracker.mLock) {
+                final ProcessState origBase = getBaseProcessTracker();
+                if (origBase != null) {
+                    final PackageList pkgList = mApp.getPkgList();
+                    synchronized (pkgList) {
+                        origBase.setState(ProcessStats.STATE_NOTHING,
+                                tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
+                                pkgList.getPackageListLocked());
+                        pkgList.forEachPackage((pkgName, holder) ->
+                                FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+                                    mApp.uid, mApp.processName, pkgName,
+                                    processStateAmToProto(ProcessStats.STATE_NOTHING),
+                                    holder.appVersion)
+                        );
+                    }
+                    origBase.makeInactive();
+                    setBaseProcessTracker(null);
+                    pkgList.forEachPackageProcessStats(holder -> {
+                        if (holder.state != null && holder.state != origBase) {
+                            holder.state.makeInactive();
+                        }
+                        holder.pkg = null;
+                        holder.state = null;
+                    });
+                }
+                mThread = null;
+            }
+        }
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastPssTime() {
+        return mLastPssTime;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastPssTime(long lastPssTime) {
+        mLastPssTime = lastPssTime;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getNextPssTime() {
+        return mNextPssTime;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setNextPssTime(long nextPssTime) {
+        mNextPssTime = nextPssTime;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getInitialIdlePss() {
+        return mInitialIdlePss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setInitialIdlePss(long initialIdlePss) {
+        mInitialIdlePss = initialIdlePss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastPss() {
+        return mLastPss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastPss(long lastPss) {
+        mLastPss = lastPss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastCachedPss() {
+        return mLastCachedPss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastCachedPss(long lastCachedPss) {
+        mLastCachedPss = lastCachedPss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastSwapPss() {
+        return mLastSwapPss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastSwapPss(long lastSwapPss) {
+        mLastSwapPss = lastSwapPss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastCachedSwapPss() {
+        return mLastCachedSwapPss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastCachedSwapPss(long lastCachedSwapPss) {
+        mLastCachedSwapPss = lastCachedSwapPss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastRss() {
+        return mLastRss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastRss(long lastRss) {
+        mLastRss = lastRss;
+    }
+
+    @GuardedBy("mProfilerLock")
+    Debug.MemoryInfo getLastMemInfo() {
+        return mLastMemInfo;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastMemInfo(Debug.MemoryInfo lastMemInfo) {
+        mLastMemInfo = lastMemInfo;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastMemInfoTime() {
+        return mLastMemInfoTime;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastMemInfoTime(long lastMemInfoTime) {
+        mLastMemInfoTime = lastMemInfoTime;
+    }
+
+    @GuardedBy("mProfilerLock")
+    int getPssProcState() {
+        return mPssProcState;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setPssProcState(int pssProcState) {
+        mPssProcState = pssProcState;
+    }
+
+    @GuardedBy("mProfilerLock")
+    int getPssStatType() {
+        return mPssStatType;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setPssStatType(int pssStatType) {
+        mPssStatType = pssStatType;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getTrimMemoryLevel() {
+        return mTrimMemoryLevel;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setTrimMemoryLevel(int trimMemoryLevel) {
+        mTrimMemoryLevel = trimMemoryLevel;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean hasPendingUiClean() {
+        return mPendingUiClean;
+    }
+
+    @GuardedBy("mProcLock")
+    void setPendingUiClean(boolean pendingUiClean) {
+        mPendingUiClean = pendingUiClean;
+        mApp.getWindowProcessController().setPendingUiClean(pendingUiClean);
+    }
+
+    BatteryStatsImpl.Uid.Proc getCurProcBatteryStats() {
+        return mCurProcBatteryStats;
+    }
+
+    void setCurProcBatteryStats(BatteryStatsImpl.Uid.Proc curProcBatteryStats) {
+        mCurProcBatteryStats = curProcBatteryStats;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastRequestedGc() {
+        return mLastRequestedGc;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setLastRequestedGc(long lastRequestedGc) {
+        mLastRequestedGc = lastRequestedGc;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProfilerLock"})
+    long getLastLowMemory() {
+        return mLastLowMemory;
+    }
+
+    @GuardedBy({"mService", "mProfilerLock"})
+    void setLastLowMemory(long lastLowMemory) {
+        mLastLowMemory = lastLowMemory;
+    }
+
+    @GuardedBy("mProfilerLock")
+    boolean getReportLowMemory() {
+        return mReportLowMemory;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setReportLowMemory(boolean reportLowMemory) {
+        mReportLowMemory = reportLowMemory;
+    }
+
+    void addPss(long pss, long uss, long rss, boolean always, int type, long duration) {
+        synchronized (mService.mProcessStats.mLock) {
+            final ProcessState tracker = mBaseProcessTracker;
+            if (tracker != null) {
+                final PackageList pkgList = mApp.getPkgList();
+                synchronized (pkgList) {
+                    tracker.addPss(pss, uss, rss, always, type, duration,
+                            pkgList.getPackageListLocked());
+                }
+            }
+        }
+    }
+
+    void reportExcessiveCpu() {
+        synchronized (mService.mProcessStats.mLock) {
+            final ProcessState tracker = mBaseProcessTracker;
+            if (tracker != null) {
+                final PackageList pkgList = mApp.getPkgList();
+                synchronized (pkgList) {
+                    tracker.reportExcessiveCpu(pkgList.getPackageListLocked());
+                }
+            }
+        }
+    }
+
+    void reportCachedKill() {
+        synchronized (mService.mProcessStats.mLock) {
+            final ProcessState tracker = mBaseProcessTracker;
+            if (tracker != null) {
+                final PackageList pkgList = mApp.getPkgList();
+                synchronized (pkgList) {
+                    tracker.reportCachedKill(pkgList.getPackageListLocked(), mLastCachedPss);
+                    pkgList.forEachPackageProcessStats(holder ->
+                            FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED,
+                                mApp.info.uid,
+                                holder.state.getName(),
+                                holder.state.getPackage(),
+                                mLastCachedPss,
+                                holder.appVersion)
+                    );
+                }
+            }
+        }
+    }
+
+    void setProcessTrackerState(int procState, int memFactor, long now) {
+        synchronized (mService.mProcessStats.mLock) {
+            final ProcessState tracker = mBaseProcessTracker;
+            if (tracker != null) {
+                if (procState != PROCESS_STATE_NONEXISTENT) {
+                    final PackageList pkgList = mApp.getPkgList();
+                    synchronized (pkgList) {
+                        tracker.setState(procState, memFactor, now,
+                                pkgList.getPackageListLocked());
+                    }
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mProfilerLock")
+    void commitNextPssTime() {
+        commitNextPssTime(mProcStateMemTracker);
+    }
+
+    @GuardedBy("mProfilerLock")
+    void abortNextPssTime() {
+        abortNextPssTime(mProcStateMemTracker);
+    }
+
+    @GuardedBy("mProfilerLock")
+    long computeNextPssTime(int procState, boolean test, boolean sleeping, long now) {
+        return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now);
+    }
+
+    private static void commitNextPssTime(ProcStateMemTracker tracker) {
+        if (tracker.mPendingMemState >= 0) {
+            tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState;
+            tracker.mScalingFactor[tracker.mPendingMemState] = tracker.mPendingScalingFactor;
+            tracker.mTotalHighestMem = tracker.mPendingHighestMemState;
+            tracker.mPendingMemState = -1;
+        }
+    }
+
+    private static void abortNextPssTime(ProcStateMemTracker tracker) {
+        tracker.mPendingMemState = -1;
+    }
+
+    @GuardedBy("mProfilerLock")
+    int getPid() {
+        return mPid;
+    }
+
+    @GuardedBy("mProfilerLock")
+    void setPid(int pid) {
+        mPid = pid;
+    }
+
+    @GuardedBy("mProfilerLock")
+    IApplicationThread getThread() {
+        return mThread;
+    }
+
+    @GuardedBy("mProfilerLock")
+    int getSetProcState() {
+        return mSetProcState;
+    }
+
+    @GuardedBy("mProfilerLock")
+    int getSetAdj() {
+        return mSetAdj;
+    }
+
+    @GuardedBy("mProfilerLock")
+    int getCurRawAdj() {
+        return mCurRawAdj;
+    }
+
+    @GuardedBy("mProfilerLock")
+    long getLastStateTime() {
+        return mLastStateTime;
+    }
+
+    @GuardedBy({"mService", "mProfilerLock"})
+    void updateProcState(ProcessStateRecord state) {
+        mSetProcState = state.getCurProcState();
+        mSetAdj = state.getCurAdj();
+        mCurRawAdj = state.getCurRawAdj();
+        mLastStateTime = state.getLastStateTime();
+    }
+
+    @GuardedBy("mService")
+    void dumpPss(PrintWriter pw, String prefix, long nowUptime) {
+        synchronized (mProfilerLock) {
+            pw.print(" lastPssTime=");
+            TimeUtils.formatDuration(mLastPssTime, nowUptime, pw);
+            pw.print(" pssProcState=");
+            pw.print(mPssProcState);
+            pw.print(" pssStatType=");
+            pw.print(mPssStatType);
+            pw.print(" nextPssTime=");
+            TimeUtils.formatDuration(mNextPssTime, nowUptime, pw);
+            pw.println();
+            pw.print(prefix);
+            pw.print("lastPss=");
+            DebugUtils.printSizeValue(pw, mLastPss * 1024);
+            pw.print(" lastSwapPss=");
+            DebugUtils.printSizeValue(pw, mLastSwapPss * 1024);
+            pw.print(" lastCachedPss=");
+            DebugUtils.printSizeValue(pw, mLastCachedPss * 1024);
+            pw.print(" lastCachedSwapPss=");
+            DebugUtils.printSizeValue(pw, mLastCachedSwapPss * 1024);
+            pw.print(" lastRss=");
+            DebugUtils.printSizeValue(pw, mLastRss * 1024);
+            pw.println();
+            pw.print(prefix);
+            pw.print(" trimMemoryLevel=");
+            pw.println(mTrimMemoryLevel);
+            pw.println();
+            pw.print(prefix); pw.print("procStateMemTracker: ");
+            mProcStateMemTracker.dumpLine(pw);
+            pw.print(prefix);
+            pw.print("lastRequestedGc=");
+            TimeUtils.formatDuration(mLastRequestedGc, nowUptime, pw);
+            pw.print(" lastLowMemory=");
+            TimeUtils.formatDuration(mLastLowMemory, nowUptime, pw);
+            pw.print(" reportLowMemory=");
+            pw.println(mReportLowMemory);
+        }
+    }
+
+    void dumpCputime(PrintWriter pw, String prefix) {
+        final long lastCpuTime = mLastCpuTime.get();
+        pw.print(prefix);
+        pw.print("lastCpuTime=");
+        pw.print(lastCpuTime);
+        if (lastCpuTime > 0) {
+            pw.print(" timeUsed=");
+            TimeUtils.formatDuration(mCurCpuTime.get() - lastCpuTime, pw);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessProviderRecord.java b/services/core/java/com/android/server/am/ProcessProviderRecord.java
new file mode 100644
index 0000000..751e8a82
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessProviderRecord.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import android.util.ArrayMap;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * The state info of all content providers in the process.
+ */
+final class ProcessProviderRecord {
+    final ProcessRecord mApp;
+    private final ActivityManagerService mService;
+
+    /**
+     * The last time someone else was using a provider in this process.
+     */
+    private long mLastProviderTime;
+
+    /**
+     * class (String) -> ContentProviderRecord.
+     */
+    private final ArrayMap<String, ContentProviderRecord> mPubProviders = new ArrayMap<>();
+
+    /**
+     * All ContentProviderRecord process is using.
+     */
+    private final ArrayList<ContentProviderConnection> mConProviders = new ArrayList<>();
+
+    long getLastProviderTime() {
+        return mLastProviderTime;
+    }
+
+    void setLastProviderTime(long lastProviderTime) {
+        mLastProviderTime = lastProviderTime;
+    }
+
+    boolean hasProvider(String name) {
+        return mPubProviders.containsKey(name);
+    }
+
+    ContentProviderRecord getProvider(String name) {
+        return mPubProviders.get(name);
+    }
+
+    int numberOfProviders() {
+        return mPubProviders.size();
+    }
+
+    ContentProviderRecord getProviderAt(int index) {
+        return mPubProviders.valueAt(index);
+    }
+
+    void installProvider(String name, ContentProviderRecord provider) {
+        mPubProviders.put(name, provider);
+    }
+
+    void removeProvider(String name) {
+        mPubProviders.remove(name);
+    }
+
+    void ensureProviderCapacity(int capacity) {
+        mPubProviders.ensureCapacity(capacity);
+    }
+
+    int numberOfProviderConnections() {
+        return mConProviders.size();
+    }
+
+    ContentProviderConnection getProviderConnectionAt(int index) {
+        return mConProviders.get(index);
+    }
+
+    void addProviderConnection(ContentProviderConnection connection) {
+        mConProviders.add(connection);
+    }
+
+    boolean removeProviderConnection(ContentProviderConnection connection) {
+        return mConProviders.remove(connection);
+    }
+
+    ProcessProviderRecord(ProcessRecord app) {
+        mApp = app;
+        mService = app.mService;
+    }
+
+    /**
+     * @return Should the process restart or not.
+     */
+    @GuardedBy("mService")
+    boolean onCleanupApplicationRecordLocked(boolean allowRestart) {
+        boolean restart = false;
+        // Remove published content providers.
+        for (int i = mPubProviders.size() - 1; i >= 0; i--) {
+            final ContentProviderRecord cpr = mPubProviders.valueAt(i);
+            if (cpr.proc != mApp) {
+                // If the hosting process record isn't really us, bail out
+                continue;
+            }
+            final boolean alwaysRemove = mApp.mErrorState.isBad() || !allowRestart;
+            final boolean inLaunching = mService.mCpHelper
+                    .removeDyingProviderLocked(mApp, cpr, alwaysRemove);
+            if (!alwaysRemove && inLaunching && cpr.hasConnectionOrHandle()) {
+                // We left the provider in the launching list, need to
+                // restart it.
+                restart = true;
+            }
+
+            cpr.provider = null;
+            cpr.setProcess(null);
+        }
+        mPubProviders.clear();
+
+        // Take care of any launching providers waiting for this process.
+        if (mService.mCpHelper.cleanupAppInLaunchingProvidersLocked(mApp, false)) {
+            mService.mProcessList.noteProcessDiedLocked(mApp);
+            restart = true;
+        }
+
+        // Unregister from connected content providers.
+        if (!mConProviders.isEmpty()) {
+            for (int i = mConProviders.size() - 1; i >= 0; i--) {
+                final ContentProviderConnection conn = mConProviders.get(i);
+                conn.provider.connections.remove(conn);
+                mService.stopAssociationLocked(mApp.uid, mApp.processName, conn.provider.uid,
+                        conn.provider.appInfo.longVersionCode, conn.provider.name,
+                        conn.provider.info.processName);
+            }
+            mConProviders.clear();
+        }
+
+        return restart;
+    }
+
+    void dump(PrintWriter pw, String prefix, long nowUptime) {
+        if (mLastProviderTime > 0) {
+            pw.print(prefix); pw.print("lastProviderTime=");
+            TimeUtils.formatDuration(mLastProviderTime, nowUptime, pw);
+            pw.println();
+        }
+        if (mPubProviders.size() > 0) {
+            pw.print(prefix); pw.println("Published Providers:");
+            for (int i = 0, size = mPubProviders.size(); i < size; i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(mPubProviders.keyAt(i));
+                pw.print(prefix); pw.print("    -> "); pw.println(mPubProviders.valueAt(i));
+            }
+        }
+        if (mConProviders.size() > 0) {
+            pw.print(prefix); pw.println("Connected Providers:");
+            for (int i = 0, size = mConProviders.size(); i < size; i++) {
+                pw.print(prefix); pw.print("  - ");
+                pw.println(mConProviders.get(i).toShortString());
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessReceiverRecord.java b/services/core/java/com/android/server/am/ProcessReceiverRecord.java
new file mode 100644
index 0000000..8d3e9669
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessReceiverRecord.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * The state info of all broadcast receivers in the process.
+ */
+final class ProcessReceiverRecord {
+    final ProcessRecord mApp;
+    private final ActivityManagerService mService;
+
+    /**
+     * mReceivers currently running in the app.
+     */
+    private final ArraySet<BroadcastRecord> mCurReceivers = new ArraySet<BroadcastRecord>();
+
+    /**
+     * All IIntentReceivers that are registered from this process.
+     */
+    private final ArraySet<ReceiverList> mReceivers = new ArraySet<>();
+
+    int numberOfCurReceivers() {
+        return mCurReceivers.size();
+    }
+
+    BroadcastRecord getCurReceiverAt(int index) {
+        return mCurReceivers.valueAt(index);
+    }
+
+    boolean hasCurReceiver(BroadcastRecord receiver) {
+        return mCurReceivers.contains(receiver);
+    }
+
+    void addCurReceiver(BroadcastRecord receiver) {
+        mCurReceivers.add(receiver);
+    }
+
+    void removeCurReceiver(BroadcastRecord receiver) {
+        mCurReceivers.remove(receiver);
+    }
+
+    int numberOfReceivers() {
+        return mReceivers.size();
+    }
+
+    ReceiverList getReceiverAt(int index) {
+        return mReceivers.valueAt(index);
+    }
+
+    void addReceiver(ReceiverList receiver) {
+        mReceivers.add(receiver);
+    }
+
+    void removeReceiver(ReceiverList receiver) {
+        mReceivers.remove(receiver);
+    }
+
+    ProcessReceiverRecord(ProcessRecord app) {
+        mApp = app;
+        mService = app.mService;
+    }
+
+    @GuardedBy("mService")
+    void onCleanupApplicationRecordLocked() {
+        // Unregister any mReceivers.
+        for (int i = mReceivers.size() - 1; i >= 0; i--) {
+            mService.removeReceiverLocked(mReceivers.valueAt(i));
+        }
+        mReceivers.clear();
+    }
+
+    void dump(PrintWriter pw, String prefix, long nowUptime) {
+        if (!mCurReceivers.isEmpty()) {
+            pw.print(prefix); pw.println("Current mReceivers:");
+            for (int i = 0, size = mCurReceivers.size(); i < size; i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(mCurReceivers.valueAt(i));
+            }
+        }
+        if (mReceivers.size() > 0) {
+            pw.print(prefix); pw.println("mReceivers:");
+            for (int i = 0, size = mReceivers.size(); i < size; i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(mReceivers.valueAt(i));
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 520a28b..da8aeb5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,89 +16,65 @@
 
 package com.android.server.am;
 
-import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
-import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
-import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Process.NFC_UID;
-import static android.os.Process.ROOT_UID;
-import static android.os.Process.SHELL_UID;
-import static android.os.Process.SYSTEM_UID;
-
-import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.MY_PID;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ApplicationErrorReport;
 import android.app.ApplicationExitInfo;
 import android.app.ApplicationExitInfo.Reason;
 import android.app.ApplicationExitInfo.SubReason;
-import android.app.Dialog;
 import android.app.IApplicationThread;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IncrementalStatesInfo;
-import android.content.pm.PackageManagerInternal;
 import android.content.pm.ProcessInfo;
-import android.content.pm.ServiceInfo;
 import android.content.pm.VersionedPackage;
 import android.content.res.CompatibilityInfo;
 import android.os.Binder;
-import android.os.Debug;
 import android.os.IBinder;
-import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.server.ServerProtoEnums;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.EventLog;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.app.procstats.ProcessStats;
-import com.android.internal.os.BatteryStatsImpl;
-import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.Zygote;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.MemoryPressureUtil;
 import com.android.server.wm.WindowProcessController;
 import com.android.server.wm.WindowProcessListener;
 
-import java.io.File;
 import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.Consumer;
 
 /**
  * Full information about a particular process that
  * is currently running.
  */
 class ProcessRecord implements WindowProcessListener {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessRecord" : TAG_AM;
+    static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessRecord" : TAG_AM;
 
-    private final ActivityManagerService mService; // where we came from
+    final ActivityManagerService mService; // where we came from
+    private final ActivityManagerGlobalLock mProcLock;
+
+    // =========================================================
+    // Basic info of the process, immutable or semi-immutable over
+    // the lifecycle of the process
+    // =========================================================
     volatile ApplicationInfo info; // all about the first app in the process
     final ProcessInfo processInfo; // if non-null, process-specific manifest info
     final boolean isolated;     // true if this is a special isolated process
@@ -106,296 +82,290 @@
     final int uid;              // uid of process; may be different from 'info' if isolated
     final int userId;           // user of process.
     final String processName;   // name of the process
-    // List of packages running in the process
-    final PackageList pkgList = new PackageList();
-    final class PackageList {
-        final ArrayMap<String, ProcessStats.ProcessStateHolder> mPkgList = new ArrayMap<>();
 
-        ProcessStats.ProcessStateHolder put(String key, ProcessStats.ProcessStateHolder value) {
-            mWindowProcessController.addPackage(key);
-            return mPkgList.put(key, value);
-        }
+    /**
+     * Overall state of process's uid.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private UidRecord mUidRecord;
 
-        void clear() {
-            mPkgList.clear();
-            mWindowProcessController.clearPackageList();
-        }
+    /**
+     * List of packages running in the process.
+     */
+    private final PackageList mPkgList = new PackageList(this);
 
-        int size() {
-            return mPkgList.size();
-        }
+    /**
+     * Additional packages we have a dependency on.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private ArraySet<String> mPkgDeps;
 
-        String keyAt(int index) {
-            return mPkgList.keyAt(index);
-        }
+    /**
+     * The process of this application; 0 if none.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    int mPid;
 
-        public ProcessStats.ProcessStateHolder valueAt(int index) {
-            return mPkgList.valueAt(index);
-        }
+    /**
+     * The gids this process was launched with.
+     */
+    @GuardedBy("mService")
+    private int[] mGids;
 
-        ProcessStats.ProcessStateHolder get(String pkgName) {
-            return mPkgList.get(pkgName);
-        }
+    /**
+     * The ABI this process was launched with.
+     */
+    @GuardedBy("mService")
+    private String mRequiredAbi;
 
-        boolean containsKey(Object key) {
-            return mPkgList.containsKey(key);
-        }
-    }
+    /**
+     * The instruction set this process was launched with.
+     */
+    @GuardedBy("mService")
+    private String mInstructionSet;
 
-    final ProcessList.ProcStateMemTracker procStateMemTracker
-            = new ProcessList.ProcStateMemTracker();
-    UidRecord uidRecord;        // overall state of process's uid.
-    ArraySet<String> pkgDeps;   // additional packages we have a dependency on
-    IApplicationThread thread;  // the actual proc...  may be null only if
-                                // 'persistent' is true (in which case we
-                                // are in the process of launching the app)
-    ProcessState baseProcessTracker;
-    BatteryStatsImpl.Uid.Proc curProcBatteryStats;
-    int pid;                    // The process of this application; 0 if none
-    String procStatFile;        // path to /proc/<pid>/stat
-    int[] gids;                 // The gids this process was launched with
-    private String mRequiredAbi;// The ABI this process was launched with
-    String instructionSet;      // The instruction set this process was launched with
-    boolean starting;           // True if the process is being started
-    long lastActivityTime;      // For managing the LRU list
-    long lastPssTime;           // Last time we retrieved PSS data
-    long nextPssTime;           // Next time we want to request PSS data
-    long lastStateTime;         // Last time setProcState changed
-    long initialIdlePss;        // Initial memory pss of process for idle maintenance.
-    long lastPss;               // Last computed memory pss.
-    long lastSwapPss;           // Last computed SwapPss.
-    long lastCachedPss;         // Last computed pss when in cached state.
-    long lastCachedSwapPss;     // Last computed SwapPss when in cached state.
-    int maxAdj;                 // Maximum OOM adjustment for this process
-    private int mCurRawAdj;     // Current OOM unlimited adjustment for this process
-    int setRawAdj;              // Last set OOM unlimited adjustment for this process
-    int curAdj;                 // Current OOM adjustment for this process
-    int setAdj;                 // Last set OOM adjustment for this process
-    int verifiedAdj;            // The last adjustment that was verified as actually being set
-    int curCapability;          // Current capability flags of this process. For example,
-                                // PROCESS_CAPABILITY_FOREGROUND_LOCATION is one capability.
-    int setCapability;          // Last set capability flags.
-    long lastCompactTime;       // The last time that this process was compacted
-    int reqCompactAction;       // The most recent compaction action requested for this app.
-    int lastCompactAction;      // The most recent compaction action performed for this app.
-    boolean frozen;             // True when the process is frozen.
-    long freezeUnfreezeTime;    // Last time the app was (un)frozen, 0 for never
-    boolean shouldNotFreeze;    // True if a process has a WPRI binding from an unfrozen process
-    private int mCurSchedGroup; // Currently desired scheduling class
-    int setSchedGroup;          // Last set to background scheduling class
-    int trimMemoryLevel;        // Last selected memory trimming level
-    private int mCurProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state
-    private int mRepProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
-    private int mCurRawProcState = PROCESS_STATE_NONEXISTENT; // Temp state during computation
-    int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
-    int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for
-    int pssStatType;            // The type of stat collection that we are currently requesting
-    int savedPriority;          // Previous priority value if we're switching to non-SCHED_OTHER
-    int renderThreadTid;        // TID for RenderThread
-    ServiceRecord connectionService; // Service that applied current connectionGroup/Importance
-    int connectionGroup;        // Last group set by a connection
-    int connectionImportance;   // Last importance set by a connection
-    boolean serviceb;           // Process currently is on the service B list
-    boolean serviceHighRam;     // We are forcing to service B list due to its RAM use
-    boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
-    private boolean mHasClientActivities;  // Are there any client services with activities?
-    boolean hasStartedServices; // Are there any started services running in this process?
-    private boolean mHasForegroundServices; // Running any services that are foreground?
-    private int mFgServiceTypes; // Type of foreground service, if there is a foreground service.
-    private int mRepFgServiceTypes; // Last reported foreground service types.
-    private boolean mHasForegroundActivities; // Running any activities that are foreground?
-    boolean repForegroundActivities; // Last reported foreground activities.
-    boolean systemNoUi;         // This is a system process, but not currently showing UI.
-    boolean hasShownUi;         // Has UI been shown in this process since it was started?
-    private boolean mHasTopUi;  // Is this process currently showing a non-activity UI that the user
-                                // is interacting with? E.g. The status bar when it is expanded, but
-                                // not when it is minimized. When true the
-                                // process will be set to use the ProcessList#SCHED_GROUP_TOP_APP
-                                // scheduling group to boost performance.
-    private boolean mHasOverlayUi; // Is the process currently showing a non-activity UI that
-                                // overlays on-top of activity UIs on screen. E.g. display a window
-                                // of type
-                                // android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
-                                // When true the process will oom adj score will be set to
-                                // ProcessList#PERCEPTIBLE_APP_ADJ at minimum to reduce the chance
-                                // of the process getting killed.
-    boolean runningRemoteAnimation; // Is the process currently running a RemoteAnimation? When true
-                                // the process will be set to use the
-                                // ProcessList#SCHED_GROUP_TOP_APP scheduling group to boost
-                                // performance, as well as oom adj score will be set to
-                                // ProcessList#VISIBLE_APP_ADJ at minimum to reduce the chance
-                                // of the process getting killed.
-    private boolean mPendingUiClean; // Want to clean up resources from showing UI?
-    boolean hasAboveClient;     // Bound using BIND_ABOVE_CLIENT, so want to be lower
-    boolean treatLikeActivity;  // Bound using BIND_TREAT_LIKE_ACTIVITY
-    boolean bad;                // True if disabled in the bad process list
-    boolean killedByAm;         // True when proc has been killed by activity manager, not for RAM
-    boolean killed;             // True once we know the process has been killed
-    boolean procStateChanged;   // Keep track of whether we changed 'setAdj'.
-    boolean reportedInteraction;// Whether we have told usage stats about it being an interaction
-    boolean unlocked;           // True when proc was started in user unlocked state
-    private long mInteractionEventTime; // The time we sent the last interaction event
-    private long mFgInteractionTime; // When we became foreground for interaction purposes
-    String waitingToKill;       // Process is waiting to be killed when in the bg, and reason
-    Object forcingToImportant;  // Token that is forcing this process to be important
-    int adjSeq;                 // Sequence id for identifying oom_adj assignment cycles
-    int completedAdjSeq;        // Sequence id for identifying oom_adj assignment cycles
-    boolean containsCycle;      // Whether this app has encountered a cycle in the most recent update
-    int lruSeq;                 // Sequence id for identifying LRU update cycles
-    CompatibilityInfo compat;   // last used compatibility mode
-    IBinder.DeathRecipient deathRecipient; // Who is watching for the death.
-    private ActiveInstrumentation mInstr; // Set to currently active instrumentation running in
-                                          // process.
-    private boolean mUsingWrapper; // Set to true when process was launched with a wrapper attached
-    final ArraySet<BroadcastRecord> curReceivers = new ArraySet<BroadcastRecord>();// receivers currently running in the app
-    private long mWhenUnimportant; // When (uptime) the process last became unimportant
-    long lastCpuTime;           // How long proc has run CPU at last check
-    long curCpuTime;            // How long proc has run CPU most recently
-    long lastRequestedGc;       // When we last asked the app to do a gc
-    long lastLowMemory;         // When we last told the app that memory is low
-    long lastProviderTime;      // The last time someone else was using a provider in this process.
-    long lastTopTime;           // The last time the process was in the TOP state or greater.
-    boolean reportLowMemory;    // Set to true when waiting to report low mem
-    boolean empty;              // Is this an empty background process?
-    private boolean mCached;    // Is this a cached process?
-    String adjType;             // Debugging: primary thing impacting oom_adj.
-    int adjTypeCode;            // Debugging: adj code to report to app.
-    Object adjSource;           // Debugging: option dependent object.
-    int adjSourceProcState;     // Debugging: proc state of adjSource's process.
-    Object adjTarget;           // Debugging: target component impacting oom_adj.
-    Runnable crashHandler;      // Optional local handler to be invoked in the process crash.
-    boolean bindMountPending;   // True if Android/obb and Android/data need to be bind mount .
+    /**
+     * The actual proc...  may be null only if 'persistent' is true
+     * (in which case we are in the process of launching the app).
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private IApplicationThread mThread;
 
-    // Cache of last retrieve memory info and uptime, to throttle how frequently
-    // apps can requyest it.
-    Debug.MemoryInfo lastMemInfo;
-    long lastMemInfoTime;
+    /**
+     * Always keep this application running?
+     */
+    private volatile boolean mPersistent;
 
-    // Controller for error dialogs
-    private final ErrorDialogController mDialogController = new ErrorDialogController();
-    // Controller for driving the process state on the window manager side.
+    /**
+     * Caching of toShortString() result.
+     * <p>Note: No lock here, it doesn't matter in case of race condition</p>
+     */
+    private String mShortStringName;
+
+    /**
+     * Caching of toString() result.
+     * <p>Note: No lock here, it doesn't matter in case of race condition</p>
+     */
+    private String mStringName;
+
+    /**
+     * Process start is pending.
+     */
+    @GuardedBy("mService")
+    private boolean mPendingStart;
+
+    /**
+     * Seq no. Indicating the latest process start associated with this process record.
+     */
+    @GuardedBy("mService")
+    private long mStartSeq;
+
+    /**
+     * Params used in starting this process.
+     */
+    private volatile HostingRecord mHostingRecord;
+
+    /**
+     * Selinux info of this process.
+     */
+    private volatile String mSeInfo;
+
+    /**
+     * When the process is started.
+     */
+    private volatile long mStartTime;
+
+    /**
+     * This will be same as {@link #uid} usually except for some apps used during factory testing.
+     */
+    private volatile int mStartUid;
+
+    /**
+     * Indicates how the external storage was mounted for this process.
+     */
+    private volatile int mMountMode;
+
+    /**
+     * True if Android/obb and Android/data need to be bind mount.
+     */
+    private volatile boolean mBindMountPending;
+
+    /**
+     * True when proc was started in user unlocked state.
+     */
+    @GuardedBy("mProcLock")
+    private boolean mUnlocked;
+
+    /**
+     * TID for RenderThread.
+     */
+    @GuardedBy("mProcLock")
+    private int mRenderThreadTid;
+
+    /**
+     * Last used compatibility mode.
+     */
+    @GuardedBy("mService")
+    private CompatibilityInfo mCompat;
+
+    /**
+     * Set of disabled compat changes for the process (all others are enabled).
+     */
+    @GuardedBy("mService")
+    private long[] mDisabledCompatChanges;
+
+    /**
+     * Who is watching for the death.
+     */
+    @GuardedBy("mService")
+    private IBinder.DeathRecipient mDeathRecipient;
+
+    /**
+     * Set to currently active instrumentation running in process.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private ActiveInstrumentation mInstr;
+
+    /**
+     * True when proc has been killed by activity manager, not for RAM.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mKilledByAm;
+
+    /**
+     * True once we know the process has been killed.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mKilled;
+
+    /**
+     * The timestamp in uptime when this process was killed.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private long mKillTime;
+
+    /**
+     * Process is waiting to be killed when in the bg, and reason.
+     */
+    @GuardedBy("mService")
+    private String mWaitingToKill;
+
+    /**
+     * Whether this process should be killed and removed from process list.
+     * It is set when the package is force-stopped or the process has crashed too many times.
+     */
+    private volatile boolean mRemoved;
+
+    /**
+     * Was app launched for debugging?
+     */
+    @GuardedBy("mService")
+    private boolean mDebugging;
+
+    /**
+     * Has process show wait for debugger dialog?
+     */
+    @GuardedBy("mProcLock")
+    private boolean mWaitedForDebugger;
+
+    /**
+     * For managing the LRU list.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private long mLastActivityTime;
+
+    /**
+     * Set to true when process was launched with a wrapper attached.
+     */
+    @GuardedBy("mService")
+    private boolean mUsingWrapper;
+
+    /**
+     * Sequence id for identifying LRU update cycles.
+     */
+    @GuardedBy("mService")
+    private int mLruSeq;
+
+    /**
+     * Class to run on start if this is a special isolated process.
+     */
+    @GuardedBy("mService")
+    private String mIsolatedEntryPoint;
+
+    /**
+     * Arguments to pass to isolatedEntryPoint's main().
+     */
+    @GuardedBy("mService")
+    private String[] mIsolatedEntryPointArgs;
+
+    /**
+     * Process is currently hosting a backup agent for backup or restore.
+     */
+    @GuardedBy("mService")
+    private boolean mInFullBackup;
+
+    /**
+     * Controller for driving the process state on the window manager side.
+     */
     private final WindowProcessController mWindowProcessController;
-    // all ServiceRecord running in this process
-    private final ArraySet<ServiceRecord> mServices = new ArraySet<>();
-    // services that are currently executing code (need to remain foreground).
-    final ArraySet<ServiceRecord> executingServices = new ArraySet<>();
-    // All ConnectionRecord this process holds
-    final ArraySet<ConnectionRecord> connections = new ArraySet<>();
-    // all IIntentReceivers that are registered from this process.
-    final ArraySet<ReceiverList> receivers = new ArraySet<>();
-    // class (String) -> ContentProviderRecord
-    final ArrayMap<String, ContentProviderRecord> pubProviders = new ArrayMap<>();
-    // All ContentProviderRecord process is using
-    final ArrayList<ContentProviderConnection> conProviders = new ArrayList<>();
-    // a set of UIDs of all bound clients
-    private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
 
-    String isolatedEntryPoint;  // Class to run on start if this is a special isolated process.
-    String[] isolatedEntryPointArgs; // Arguments to pass to isolatedEntryPoint's main().
+    /**
+     * Profiling info of the process, such as PSS, cpu, etc.
+     */
+    final ProcessProfileRecord mProfile;
 
-    boolean execServicesFg;     // do we need to be executing services in the foreground?
-    private boolean mPersistent;// always keep this application running?
-    private boolean mCrashing;  // are we in the process of crashing?
-    boolean forceCrashReport;   // suppress normal auto-dismiss of crash dialog & report UI?
-    private boolean mNotResponding; // does the app have a not responding dialog?
-    volatile boolean removed;   // Whether this process should be killed and removed from process
-                                // list. It is set when the package is force-stopped or the process
-                                // has crashed too many times.
-    private boolean mDebugging; // was app launched for debugging?
-    boolean waitedForDebugger;  // has process show wait for debugger dialog?
+    /**
+     * All about the services in this process.
+     */
+    final ProcessServiceRecord mServices;
 
-    String shortStringName;     // caching of toShortString() result.
-    String stringName;          // caching of toString() result.
-    boolean pendingStart;       // Process start is pending.
-    long startSeq;              // Seq no. indicating the latest process start associated with
-                                // this process record.
-    int mountMode;              // Indicates how the external storage was mounted for this process.
+    /**
+     * All about the providers in this process.
+     */
+    final ProcessProviderRecord mProviders;
 
-    // These reports are generated & stored when an app gets into an error condition.
-    // They will be "null" when all is OK.
-    ActivityManager.ProcessErrorStateInfo crashingReport;
-    ActivityManager.ProcessErrorStateInfo notRespondingReport;
+    /**
+     * All about the receivers in this process.
+     */
+    final ProcessReceiverRecord mReceivers;
 
-    // Who will be notified of the error. This is usually an activity in the
-    // app that installed the package.
-    ComponentName errorReportReceiver;
+    /**
+     * All about the error state(crash, ANR) in this process.
+     */
+    final ProcessErrorStateRecord mErrorState;
 
-    // Process is currently hosting a backup agent for backup or restore
-    public boolean inFullBackup;
-    // App is allowed to manage whitelists such as temporary Power Save mode whitelist.
-    boolean whitelistManager;
+    /**
+     * All about the process state info (proc state, oom adj score) in this process.
+     */
+    final ProcessStateRecord mState;
 
-    // Params used in starting this process.
-    HostingRecord hostingRecord;
-    String seInfo;
-    long startTime;
-    // This will be same as {@link #uid} usually except for some apps used during factory testing.
-    int startUid;
-    // set of disabled compat changes for the process (all others are enabled)
-    long[] mDisabledCompatChanges;
+    /**
+     * All about the state info of the optimizer when the process is cached.
+     */
+    final ProcessCachedOptimizerRecord mOptRecord;
 
-    long mLastRss;               // Last computed memory rss.
+    /**
+     * The preceding instance of the process, which would exist when the previous process is killed
+     * but not fully dead yet; in this case, the new instance of the process should be held until
+     * this preceding instance is fully dead.
+     */
+    volatile ProcessRecord mPredecessor;
 
-    // The precede instance of the process, which would exist when the previous process is killed
-    // but not fully dead yet; in this case, the new instance of the process should be held until
-    // this precede instance is fully dead.
-    volatile ProcessRecord mPrecedence;
-    // The succeeding instance of the process, which is going to be started after this process
-    // is killed successfully.
+    /**
+     * The succeeding instance of the process, which is going to be started after this process
+     * is killed successfully.
+     */
     volatile ProcessRecord mSuccessor;
 
-    // Cached task info for OomAdjuster
-    private static final int VALUE_INVALID = -1;
-    private static final int VALUE_FALSE = 0;
-    private static final int VALUE_TRUE = 1;
-    private int mCachedHasActivities = VALUE_INVALID;
-    private int mCachedIsHeavyWeight = VALUE_INVALID;
-    private int mCachedHasVisibleActivities = VALUE_INVALID;
-    private int mCachedIsHomeProcess = VALUE_INVALID;
-    private int mCachedIsPreviousProcess = VALUE_INVALID;
-    private int mCachedHasRecentTasks = VALUE_INVALID;
-    private int mCachedIsReceivingBroadcast = VALUE_INVALID;
-    int mCachedAdj = ProcessList.INVALID_ADJ;
-    boolean mCachedForegroundActivities = false;
-    int mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-    int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-
-    // Approximates the usage count of the app, used for cache re-ranking by CacheOomRanker.
-    //
-    // Counts the number of times the process is re-added to the cache (i.e. setCached(false);
-    // setCached(true)). This over counts, as setCached is sometimes reset while remaining in the
-    // cache. However, this happens uniformly across processes, so ranking is not affected.
-    private int mCacheOomRankerUseCount;
-
-    boolean mReachable; // Whether or not this process is reachable from given process
-
-    long mKillTime; // The timestamp in uptime when this process was killed.
-
-    // If the proc state is PROCESS_STATE_BOUND_FOREGROUND_SERVICE or above, it can start FGS.
-    // It must obtain the proc state from a persistent/top process or FGS, not transitive.
-    int mAllowStartFgsState = PROCESS_STATE_NONEXISTENT;
-
-    private final ArraySet<Binder> mBackgroundFgsStartTokens = new ArraySet<>();
-
-    // The list of permissions that can start FGS from background.
-    private static String[] ALLOW_BG_START_FGS_PERMISSIONS =
-            {START_ACTIVITIES_FROM_BACKGROUND, START_FOREGROUND_SERVICES_FROM_BACKGROUND,
-                    SYSTEM_ALERT_WINDOW};
-    // Does the process has permission to start FGS from background.
-    boolean mAllowStartFgsByPermission;
-    // Can this process start FGS from background?
-    // If this process has the ability to start FGS from background, this ability can be passed to
-    // another process through service binding.
-    boolean mAllowStartFgs;
-
     void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
             long startTime) {
-        this.startUid = startUid;
-        this.hostingRecord = hostingRecord;
-        this.seInfo = seInfo;
-        this.startTime = startTime;
+        this.mStartUid = startUid;
+        this.mHostingRecord = hostingRecord;
+        this.mSeInfo = seInfo;
+        this.mStartTime = startTime;
     }
 
+    @GuardedBy({"mService", "mProcLock"})
     void dump(PrintWriter pw, String prefix) {
         final long nowUptime = SystemClock.uptimeMillis();
 
@@ -405,10 +375,10 @@
             pw.print(" ISOLATED uid="); pw.print(uid);
         }
         pw.print(" gids={");
-        if (gids != null) {
-            for (int gi=0; gi<gids.length; gi++) {
+        if (mGids != null) {
+            for (int gi = 0; gi < mGids.length; gi++) {
                 if (gi != 0) pw.print(", ");
-                pw.print(gids[gi]);
+                pw.print(mGids[gi]);
 
             }
         }
@@ -424,9 +394,12 @@
             if (processInfo.gwpAsanMode != ApplicationInfo.GWP_ASAN_DEFAULT) {
                 pw.print(prefix); pw.println("  gwpAsanMode=" + processInfo.gwpAsanMode);
             }
+            if (processInfo.memtagMode != ApplicationInfo.MEMTAG_DEFAULT) {
+                pw.print(prefix); pw.println("  memtagMode=" + processInfo.memtagMode);
+            }
         }
         pw.print(prefix); pw.print("mRequiredAbi="); pw.print(mRequiredAbi);
-                pw.print(" instructionSet="); pw.println(instructionSet);
+        pw.print(" instructionSet="); pw.println(mInstructionSet);
         if (info.className != null) {
             pw.print(prefix); pw.print("class="); pw.println(info.className);
         }
@@ -438,239 +411,63 @@
         pw.print(prefix); pw.print("dir="); pw.print(info.sourceDir);
                 pw.print(" publicDir="); pw.print(info.publicSourceDir);
                 pw.print(" data="); pw.println(info.dataDir);
-        pw.print(prefix); pw.print("packageList={");
-        for (int i=0; i<pkgList.size(); i++) {
-            if (i > 0) pw.print(", ");
-            pw.print(pkgList.keyAt(i));
-        }
-        pw.println("}");
-        if (pkgDeps != null) {
+        mPkgList.dump(pw, prefix);
+        if (mPkgDeps != null) {
             pw.print(prefix); pw.print("packageDependencies={");
-            for (int i=0; i<pkgDeps.size(); i++) {
+            for (int i = 0; i < mPkgDeps.size(); i++) {
                 if (i > 0) pw.print(", ");
-                pw.print(pkgDeps.valueAt(i));
+                pw.print(mPkgDeps.valueAt(i));
             }
             pw.println("}");
         }
-        pw.print(prefix); pw.print("compat="); pw.println(compat);
+        pw.print(prefix); pw.print("compat="); pw.println(mCompat);
         if (mInstr != null) {
             pw.print(prefix); pw.print("mInstr="); pw.println(mInstr);
         }
-        pw.print(prefix); pw.print("thread="); pw.println(thread);
-        pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting=");
-                pw.println(starting);
+        pw.print(prefix); pw.print("thread="); pw.println(mThread);
+        pw.print(prefix); pw.print("pid="); pw.print(mPid);
         pw.print(prefix); pw.print("lastActivityTime=");
-                TimeUtils.formatDuration(lastActivityTime, nowUptime, pw);
-                pw.print(" lastPssTime=");
-                TimeUtils.formatDuration(lastPssTime, nowUptime, pw);
-                pw.print(" pssStatType="); pw.print(pssStatType);
-                pw.print(" nextPssTime=");
-                TimeUtils.formatDuration(nextPssTime, nowUptime, pw);
-                pw.println();
-        pw.print(prefix); pw.print("lastPss="); DebugUtils.printSizeValue(pw, lastPss * 1024);
-                pw.print(" lastSwapPss="); DebugUtils.printSizeValue(pw, lastSwapPss * 1024);
-                pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss * 1024);
-                pw.print(" lastCachedSwapPss="); DebugUtils.printSizeValue(pw,
-                        lastCachedSwapPss * 1024);
-                pw.print(" lastRss="); DebugUtils.printSizeValue(pw, mLastRss * 1024);
-                pw.println();
-        pw.print(prefix); pw.print("procStateMemTracker: ");
-        procStateMemTracker.dumpLine(pw);
-        pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
-                pw.print(" lruSeq="); pw.println(lruSeq);
-        pw.print(prefix); pw.print("oom adj: max="); pw.print(maxAdj);
-                pw.print(" curRaw="); pw.print(mCurRawAdj);
-                pw.print(" setRaw="); pw.print(setRawAdj);
-                pw.print(" cur="); pw.print(curAdj);
-                pw.print(" set="); pw.println(setAdj);
-        pw.print(prefix); pw.print("lastCompactTime="); pw.print(lastCompactTime);
-                pw.print(" lastCompactAction="); pw.println(lastCompactAction);
-        pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
-                pw.print(" setSchedGroup="); pw.print(setSchedGroup);
-                pw.print(" systemNoUi="); pw.print(systemNoUi);
-                pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel);
-        pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
-                pw.print(" mRepProcState="); pw.print(mRepProcState);
-                pw.print(" pssProcState="); pw.print(pssProcState);
-                pw.print(" setProcState="); pw.print(setProcState);
-                pw.print(" lastStateTime=");
-                TimeUtils.formatDuration(lastStateTime, nowUptime, pw);
-                pw.println();
-        pw.print(prefix); pw.print("curCapability=");
-                ActivityManager.printCapabilitiesFull(pw, curCapability);
-                pw.print(" setCapability=");
-                ActivityManager.printCapabilitiesFull(pw, setCapability);
-                pw.println();
-        pw.print(prefix); pw.print("allowStartFgsState=");
-                pw.println(mAllowStartFgsState);
-        if (mAllowStartFgs) {
-            pw.print(prefix); pw.print("allowStartFgs="); pw.println(mAllowStartFgs);
-        }
-        if (hasShownUi || mPendingUiClean || hasAboveClient || treatLikeActivity) {
-            pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
-                    pw.print(" pendingUiClean="); pw.print(mPendingUiClean);
-                    pw.print(" hasAboveClient="); pw.print(hasAboveClient);
-                    pw.print(" treatLikeActivity="); pw.println(treatLikeActivity);
-        }
-        pw.print(prefix); pw.print("cached="); pw.print(mCached);
-                pw.print(" empty="); pw.println(empty);
-        if (serviceb) {
-            pw.print(prefix); pw.print("serviceb="); pw.print(serviceb);
-                    pw.print(" serviceHighRam="); pw.println(serviceHighRam);
-        }
-        if (notCachedSinceIdle) {
-            pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(notCachedSinceIdle);
-                    pw.print(" initialIdlePss="); pw.println(initialIdlePss);
-        }
-        if (connectionService != null || connectionGroup != 0) {
-            pw.print(prefix); pw.print("connectionGroup="); pw.print(connectionGroup);
-            pw.print(" Importance="); pw.print(connectionImportance);
-            pw.print(" Service="); pw.println(connectionService);
-        }
-        if (hasTopUi() || hasOverlayUi() || runningRemoteAnimation) {
-            pw.print(prefix); pw.print("hasTopUi="); pw.print(hasTopUi());
-                    pw.print(" hasOverlayUi="); pw.print(hasOverlayUi());
-                    pw.print(" runningRemoteAnimation="); pw.println(runningRemoteAnimation);
-        }
-        if (mHasForegroundServices || forcingToImportant != null) {
-            pw.print(prefix); pw.print("mHasForegroundServices="); pw.print(mHasForegroundServices);
-                    pw.print(" forcingToImportant="); pw.println(forcingToImportant);
-        }
-        if (reportedInteraction || mFgInteractionTime != 0) {
-            pw.print(prefix); pw.print("reportedInteraction=");
-            pw.print(reportedInteraction);
-            if (mInteractionEventTime != 0) {
-                pw.print(" time=");
-                TimeUtils.formatDuration(mInteractionEventTime, SystemClock.elapsedRealtime(), pw);
-            }
-            if (mFgInteractionTime != 0) {
-                pw.print(" fgInteractionTime=");
-                TimeUtils.formatDuration(mFgInteractionTime, SystemClock.elapsedRealtime(), pw);
-            }
-            pw.println();
-        }
-        if (mPersistent || removed) {
+        TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw);
+        if (mPersistent || mRemoved) {
             pw.print(prefix); pw.print("persistent="); pw.print(mPersistent);
-                    pw.print(" removed="); pw.println(removed);
+            pw.print(" removed="); pw.println(mRemoved);
         }
-        if (mHasClientActivities || mHasForegroundActivities || repForegroundActivities) {
-            pw.print(prefix); pw.print("hasClientActivities="); pw.print(mHasClientActivities);
-                    pw.print(" foregroundActivities="); pw.print(mHasForegroundActivities);
-                    pw.print(" (rep="); pw.print(repForegroundActivities); pw.println(")");
+        if (mDebugging) {
+            pw.print(prefix); pw.print("mDebugging="); pw.println(mDebugging);
         }
-        if (lastProviderTime > 0) {
-            pw.print(prefix); pw.print("lastProviderTime=");
-            TimeUtils.formatDuration(lastProviderTime, nowUptime, pw);
-            pw.println();
+        if (mPendingStart) {
+            pw.print(prefix); pw.print("pendingStart="); pw.println(mPendingStart);
         }
-        if (lastTopTime > 0) {
-            pw.print(prefix); pw.print("lastTopTime=");
-            TimeUtils.formatDuration(lastTopTime, nowUptime, pw);
-            pw.println();
-        }
-        if (hasStartedServices) {
-            pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
-        }
-        if (pendingStart) {
-            pw.print(prefix); pw.print("pendingStart="); pw.println(pendingStart);
-        }
-        pw.print(prefix); pw.print("startSeq="); pw.println(startSeq);
+        pw.print(prefix); pw.print("startSeq="); pw.println(mStartSeq);
         pw.print(prefix); pw.print("mountMode="); pw.println(
-                DebugUtils.valueToString(Zygote.class, "MOUNT_EXTERNAL_", mountMode));
-        if (setProcState > ActivityManager.PROCESS_STATE_SERVICE) {
-            pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime);
-                    if (lastCpuTime > 0) {
-                        pw.print(" timeUsed=");
-                        TimeUtils.formatDuration(curCpuTime - lastCpuTime, pw);
-                    }
-                    pw.print(" whenUnimportant=");
-                    TimeUtils.formatDuration(mWhenUnimportant - nowUptime, pw);
-                    pw.println();
+                DebugUtils.valueToString(Zygote.class, "MOUNT_EXTERNAL_", mMountMode));
+        if (mKilled || mKilledByAm || mWaitingToKill != null) {
+            pw.print(prefix); pw.print("killed="); pw.print(mKilled);
+            pw.print(" killedByAm="); pw.print(mKilledByAm);
+            pw.print(" waitingToKill="); pw.println(mWaitingToKill);
         }
-        pw.print(prefix); pw.print("lastRequestedGc=");
-                TimeUtils.formatDuration(lastRequestedGc, nowUptime, pw);
-                pw.print(" lastLowMemory=");
-                TimeUtils.formatDuration(lastLowMemory, nowUptime, pw);
-                pw.print(" reportLowMemory="); pw.println(reportLowMemory);
-        if (killed || killedByAm || waitingToKill != null) {
-            pw.print(prefix); pw.print("killed="); pw.print(killed);
-                    pw.print(" killedByAm="); pw.print(killedByAm);
-                    pw.print(" waitingToKill="); pw.println(waitingToKill);
-        }
-        if (mDebugging || mCrashing || mDialogController.hasCrashDialogs() || mNotResponding
-                || mDialogController.hasAnrDialogs() || bad) {
-            pw.print(prefix); pw.print("mDebugging="); pw.print(mDebugging);
-            pw.print(" mCrashing=" + mCrashing);
-            pw.print(" " + mDialogController.mCrashDialogs);
-            pw.print(" mNotResponding=" + mNotResponding);
-            pw.print(" " + mDialogController.mAnrDialogs);
-            pw.print(" bad=" + bad);
-
-            // mCrashing or mNotResponding is always set before errorReportReceiver
-            if (errorReportReceiver != null) {
-                pw.print(" errorReportReceiver=");
-                pw.print(errorReportReceiver.flattenToShortString());
-            }
-            pw.println();
-        }
-        if (whitelistManager) {
-            pw.print(prefix); pw.print("whitelistManager="); pw.println(whitelistManager);
-        }
-        if (isolatedEntryPoint != null || isolatedEntryPointArgs != null) {
-            pw.print(prefix); pw.print("isolatedEntryPoint="); pw.println(isolatedEntryPoint);
+        if (mIsolatedEntryPoint != null || mIsolatedEntryPointArgs != null) {
+            pw.print(prefix); pw.print("isolatedEntryPoint="); pw.println(mIsolatedEntryPoint);
             pw.print(prefix); pw.print("isolatedEntryPointArgs=");
-            pw.println(Arrays.toString(isolatedEntryPointArgs));
+            pw.println(Arrays.toString(mIsolatedEntryPointArgs));
         }
+        if (mState.getSetProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
+            mProfile.dumpCputime(pw, prefix);
+        }
+        mProfile.dumpPss(pw, prefix, nowUptime);
+        mState.dump(pw, prefix, nowUptime);
+        mErrorState.dump(pw, prefix, nowUptime);
+        mServices.dump(pw, prefix, nowUptime);
+        mProviders.dump(pw, prefix, nowUptime);
+        mReceivers.dump(pw, prefix, nowUptime);
+        mOptRecord.dump(pw, prefix, nowUptime);
         mWindowProcessController.dump(pw, prefix);
-        if (mServices.size() > 0) {
-            pw.print(prefix); pw.println("Services:");
-            for (int i = 0; i < mServices.size(); i++) {
-                pw.print(prefix); pw.print("  - "); pw.println(mServices.valueAt(i));
-            }
-        }
-        if (executingServices.size() > 0) {
-            pw.print(prefix); pw.print("Executing Services (fg=");
-            pw.print(execServicesFg); pw.println(")");
-            for (int i=0; i<executingServices.size(); i++) {
-                pw.print(prefix); pw.print("  - "); pw.println(executingServices.valueAt(i));
-            }
-        }
-        if (connections.size() > 0) {
-            pw.print(prefix); pw.println("Connections:");
-            for (int i=0; i<connections.size(); i++) {
-                pw.print(prefix); pw.print("  - "); pw.println(connections.valueAt(i));
-            }
-        }
-        if (pubProviders.size() > 0) {
-            pw.print(prefix); pw.println("Published Providers:");
-            for (int i=0; i<pubProviders.size(); i++) {
-                pw.print(prefix); pw.print("  - "); pw.println(pubProviders.keyAt(i));
-                pw.print(prefix); pw.print("    -> "); pw.println(pubProviders.valueAt(i));
-            }
-        }
-        if (conProviders.size() > 0) {
-            pw.print(prefix); pw.println("Connected Providers:");
-            for (int i=0; i<conProviders.size(); i++) {
-                pw.print(prefix); pw.print("  - "); pw.println(conProviders.get(i).toShortString());
-            }
-        }
-        if (!curReceivers.isEmpty()) {
-            pw.print(prefix); pw.println("Current Receivers:");
-            for (int i=0; i < curReceivers.size(); i++) {
-                pw.print(prefix); pw.print("  - "); pw.println(curReceivers.valueAt(i));
-            }
-        }
-        if (receivers.size() > 0) {
-            pw.print(prefix); pw.println("Receivers:");
-            for (int i=0; i<receivers.size(); i++) {
-                pw.print(prefix); pw.print("  - "); pw.println(receivers.valueAt(i));
-            }
-        }
     }
 
     ProcessRecord(ActivityManagerService _service, ApplicationInfo _info, String _processName,
             int _uid) {
         mService = _service;
+        mProcLock = _service.mProcLock;
         info = _info;
         ProcessInfo procInfo = null;
         if (_service.mPackageManagerInt != null) {
@@ -679,9 +476,12 @@
             if (processes != null) {
                 procInfo = processes.get(_processName);
                 if (procInfo != null && procInfo.deniedPermissions == null
-                        && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT) {
+                        && procInfo.gwpAsanMode == ApplicationInfo.GWP_ASAN_DEFAULT
+                        && procInfo.memtagMode == ApplicationInfo.MEMTAG_DEFAULT
+                        && procInfo.nativeHeapZeroInit == null) {
                     // If this process hasn't asked for permissions to be denied, or for a
-                    // non-default GwpAsan mode, then we don't care about it.
+                    // non-default GwpAsan mode, or any other non-default setting, then we don't
+                    // care about it.
                     procInfo = null;
                 }
             }
@@ -693,163 +493,393 @@
         uid = _uid;
         userId = UserHandle.getUserId(_uid);
         processName = _processName;
-        maxAdj = ProcessList.UNKNOWN_ADJ;
-        mCurRawAdj = setRawAdj = ProcessList.INVALID_ADJ;
-        curAdj = setAdj = verifiedAdj = ProcessList.INVALID_ADJ;
         mPersistent = false;
-        removed = false;
-        freezeUnfreezeTime = lastStateTime = lastPssTime = nextPssTime = SystemClock.uptimeMillis();
+        mRemoved = false;
+        mProfile = new ProcessProfileRecord(this);
+        mServices = new ProcessServiceRecord(this);
+        mProviders = new ProcessProviderRecord(this);
+        mReceivers = new ProcessReceiverRecord(this);
+        mErrorState = new ProcessErrorStateRecord(this);
+        mState = new ProcessStateRecord(this);
+        mOptRecord = new ProcessCachedOptimizerRecord(this);
+        final long now = SystemClock.uptimeMillis();
+        mProfile.init(now);
+        mOptRecord.init(now);
+        mState.init(now);
         mWindowProcessController = new WindowProcessController(
                 mService.mActivityTaskManager, info, processName, uid, userId, this, this);
-        pkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.longVersionCode));
-        setAllowStartFgsByPermission();
+        mPkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.longVersionCode));
     }
 
-    public void setPid(int _pid) {
-        pid = _pid;
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    UidRecord getUidRecord() {
+        return mUidRecord;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setUidRecord(UidRecord uidRecord) {
+        mUidRecord = uidRecord;
+    }
+
+    PackageList getPkgList() {
+        return mPkgList;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ArraySet<String> getPkgDeps() {
+        return mPkgDeps;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setPkgDeps(ArraySet<String> pkgDeps) {
+        mPkgDeps = pkgDeps;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getPid() {
+        return mPid;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setPid(int pid) {
+        mPid = pid;
         mWindowProcessController.setPid(pid);
-        procStatFile = null;
-        shortStringName = null;
-        stringName = null;
+        mShortStringName = null;
+        mStringName = null;
+        synchronized (mProfile.mProfilerLock) {
+            mProfile.setPid(pid);
+        }
     }
 
-    public void makeActive(IApplicationThread _thread, ProcessStatsService tracker) {
-        if (thread == null) {
-            synchronized (tracker.mLock) {
-                final ProcessState origBase = baseProcessTracker;
-                if (origBase != null) {
-                    origBase.setState(ProcessStats.STATE_NOTHING,
-                            tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
-                            pkgList.mPkgList);
-                    for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                        FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                                uid, processName, pkgList.keyAt(ipkg),
-                                ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
-                                pkgList.valueAt(ipkg).appVersion);
-                    }
-                    origBase.makeInactive();
-                }
-                baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
-                        info.longVersionCode, processName);
-                baseProcessTracker.makeActive();
-                for (int i = 0, ipkg = pkgList.size(); i < ipkg; i++) {
-                    ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
-                    if (holder.state != null && holder.state != origBase) {
-                        holder.state.makeInactive();
-                    }
-                    tracker.updateProcessStateHolderLocked(holder, pkgList.keyAt(i), info.uid,
-                            info.longVersionCode, processName);
-                    if (holder.state != baseProcessTracker) {
-                        holder.state.makeActive();
-                    }
-                }
-            }
-        }
-        thread = _thread;
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    IApplicationThread getThread() {
+        return mThread;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
+        mProfile.onProcessActive(thread, tracker);
+        mThread = thread;
         mWindowProcessController.setThread(thread);
     }
 
+    @GuardedBy({"mService", "mProcLock"})
     public void makeInactive(ProcessStatsService tracker) {
-        thread = null;
+        mThread = null;
         mWindowProcessController.setThread(null);
-        synchronized (tracker.mLock) {
-            final ProcessState origBase = baseProcessTracker;
-            if (origBase != null) {
-                if (origBase != null) {
-                    origBase.setState(ProcessStats.STATE_NOTHING,
-                            tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
-                            pkgList.mPkgList);
-                    for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                        FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                                uid, processName, pkgList.keyAt(ipkg),
-                                ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
-                                pkgList.valueAt(ipkg).appVersion);
-                    }
-                    origBase.makeInactive();
-                }
-                baseProcessTracker = null;
-                for (int i = 0, ipkg = pkgList.size(); i < ipkg; i++) {
-                    ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
-                    if (holder.state != null && holder.state != origBase) {
-                        holder.state.makeInactive();
-                    }
-                    holder.pkg = null;
-                    holder.state = null;
-                }
-            }
+        mProfile.onProcessInactive(tracker);
+    }
+
+    @GuardedBy("mService")
+    int[] getGids() {
+        return mGids;
+    }
+
+    @GuardedBy("mService")
+    void setGids(int[] gids) {
+        mGids = gids;
+    }
+
+    @GuardedBy("mService")
+    String getRequiredAbi() {
+        return mRequiredAbi;
+    }
+
+    @GuardedBy("mService")
+    void setRequiredAbi(String requiredAbi) {
+        mRequiredAbi = requiredAbi;
+        mWindowProcessController.setRequiredAbi(requiredAbi);
+    }
+
+    @GuardedBy("mService")
+    String getInstructionSet() {
+        return mInstructionSet;
+    }
+
+    @GuardedBy("mService")
+    void setInstructionSet(String instructionSet) {
+        mInstructionSet = instructionSet;
+    }
+
+    void setPersistent(boolean persistent) {
+        mPersistent = persistent;
+        mWindowProcessController.setPersistent(persistent);
+    }
+
+    boolean isPersistent() {
+        return mPersistent;
+    }
+
+    @GuardedBy("mService")
+    boolean isPendingStart() {
+        return mPendingStart;
+    }
+
+    @GuardedBy("mService")
+    void setPendingStart(boolean pendingStart) {
+        mPendingStart = pendingStart;
+    }
+
+    @GuardedBy("mService")
+    long getStartSeq() {
+        return mStartSeq;
+    }
+
+    @GuardedBy("mService")
+    void setStartSeq(long startSeq) {
+        mStartSeq = startSeq;
+    }
+
+    HostingRecord getHostingRecord() {
+        return mHostingRecord;
+    }
+
+    void setHostingRecord(HostingRecord hostingRecord) {
+        mHostingRecord = hostingRecord;
+    }
+
+    String getSeInfo() {
+        return mSeInfo;
+    }
+
+    void setSeInfo(String seInfo) {
+        mSeInfo = seInfo;
+    }
+
+    long getStartTime() {
+        return mStartTime;
+    }
+
+    void setStartTime(long startTime) {
+        mStartTime = startTime;
+    }
+
+    int getStartUid() {
+        return mStartUid;
+    }
+
+    void setStartUid(int startUid) {
+        mStartUid = startUid;
+    }
+
+    int getMountMode() {
+        return mMountMode;
+    }
+
+    void setMountMode(int mountMode) {
+        mMountMode = mountMode;
+    }
+
+    boolean isBindMountPending() {
+        return mBindMountPending;
+    }
+
+    void setBindMountPending(boolean bindMountPending) {
+        mBindMountPending = bindMountPending;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean isUnlocked() {
+        return mUnlocked;
+    }
+
+    @GuardedBy("mProcLock")
+    void setUnlocked(boolean unlocked) {
+        mUnlocked = unlocked;
+    }
+
+    @GuardedBy("mProcLock")
+    int getRenderThreadTid() {
+        return mRenderThreadTid;
+    }
+
+    @GuardedBy("mProcLock")
+    void setRenderThreadTid(int renderThreadTid) {
+        mRenderThreadTid = renderThreadTid;
+    }
+
+    @GuardedBy("mService")
+    CompatibilityInfo getCompat() {
+        return mCompat;
+    }
+
+    @GuardedBy("mService")
+    void setCompat(CompatibilityInfo compat) {
+        mCompat = compat;
+    }
+
+    @GuardedBy("mService")
+    long[] getDisabledCompatChanges() {
+        return mDisabledCompatChanges;
+    }
+
+    @GuardedBy("mService")
+    void setDisabledCompatChanges(long[] disabledCompatChanges) {
+        mDisabledCompatChanges = disabledCompatChanges;
+    }
+
+    @GuardedBy("mService")
+    void unlinkDeathRecipient() {
+        if (mDeathRecipient != null && mThread != null) {
+            mThread.asBinder().unlinkToDeath(mDeathRecipient, 0);
         }
+        mDeathRecipient = null;
     }
 
-    /**
-     * Records a service as running in the process. Note that this method does not actually start
-     * the service, but records the service as started for bookkeeping.
-     *
-     * @return true if the service was added, false otherwise.
-     */
-    boolean startService(ServiceRecord record) {
-        if (record == null) {
-            return false;
-        }
-        boolean added = mServices.add(record);
-        if (added && record.serviceInfo != null) {
-            mWindowProcessController.onServiceStarted(record.serviceInfo);
-        }
-        return added;
+    @GuardedBy("mService")
+    void setDeathRecipient(IBinder.DeathRecipient deathRecipient) {
+        mDeathRecipient = deathRecipient;
     }
 
-    /**
-     * Records a service as stopped. Note that like {@link #startService(ServiceRecord)} this method
-     * does not actually stop the service, but records the service as stopped for bookkeeping.
-     *
-     * @return true if the service was removed, false otherwise.
-     */
-    boolean stopService(ServiceRecord record) {
-        return mServices.remove(record);
+    @GuardedBy({"mService", "mProcLock"})
+    void setActiveInstrumentation(ActiveInstrumentation instr) {
+        mInstr = instr;
+        boolean isInstrumenting = instr != null;
+        mWindowProcessController.setInstrumenting(
+                isInstrumenting,
+                isInstrumenting ? instr.mSourceUid : -1,
+                isInstrumenting && instr.mHasBackgroundActivityStartsPermission);
     }
 
-    /**
-     * The same as calling {@link #stopService(ServiceRecord)} on all current running services.
-     */
-    void stopAllServices() {
-        mServices.clear();
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    ActiveInstrumentation getActiveInstrumentation() {
+        return mInstr;
     }
 
-    /**
-     * Returns the number of services added with {@link #startService(ServiceRecord)} and not yet
-     * removed by a call to {@link #stopService(ServiceRecord)} or {@link #stopAllServices()}.
-     *
-     * @see #startService(ServiceRecord)
-     * @see #stopService(ServiceRecord)
-     */
-    int numberOfRunningServices() {
-        return mServices.size();
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isKilledByAm() {
+        return mKilledByAm;
     }
 
-    /**
-     * Returns the service at the specified {@code index}.
-     *
-     * @see #numberOfRunningServices()
-     */
-    ServiceRecord getRunningServiceAt(int index) {
-        return mServices.valueAt(index);
+    @GuardedBy({"mService", "mProcLock"})
+    void setKilledByAm(boolean killedByAm) {
+        mKilledByAm = killedByAm;
     }
 
-    void setCached(boolean cached) {
-        if (mCached != cached) {
-            mCached = cached;
-            if (cached) {
-                ++mCacheOomRankerUseCount;
-            }
-        }
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isKilled() {
+        return mKilled;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setKilled(boolean killed) {
+        mKilled = killed;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getKillTime() {
+        return mKillTime;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setKillTime(long killTime) {
+        mKillTime = killTime;
+    }
+
+    @GuardedBy("mService")
+    String getWaitingToKill() {
+        return mWaitingToKill;
+    }
+
+    @GuardedBy("mService")
+    void setWaitingToKill(String waitingToKill) {
+        mWaitingToKill = waitingToKill;
+    }
+
+    @Override
+    public boolean isRemoved() {
+        return mRemoved;
+    }
+
+    void setRemoved(boolean removed) {
+        mRemoved = removed;
+    }
+
+    @GuardedBy("mService")
+    boolean isDebugging() {
+        return mDebugging;
+    }
+
+    @GuardedBy("mService")
+    void setDebugging(boolean debugging) {
+        mDebugging = debugging;
+        mWindowProcessController.setDebugging(debugging);
+    }
+
+    @GuardedBy("mProcLock")
+    boolean hasWaitedForDebugger() {
+        return mWaitedForDebugger;
+    }
+
+    @GuardedBy("mProcLock")
+    void setWaitedForDebugger(boolean waitedForDebugger) {
+        mWaitedForDebugger = waitedForDebugger;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getLastActivityTime() {
+        return mLastActivityTime;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setLastActivityTime(long lastActivityTime) {
+        mLastActivityTime = lastActivityTime;
+    }
+
+    @GuardedBy("mService")
+    boolean isUsingWrapper() {
+        return mUsingWrapper;
+    }
+
+    @GuardedBy("mService")
+    void setUsingWrapper(boolean usingWrapper) {
+        mUsingWrapper = usingWrapper;
+        mWindowProcessController.setUsingWrapper(usingWrapper);
+    }
+
+    @GuardedBy("mService")
+    int getLruSeq() {
+        return mLruSeq;
+    }
+
+    @GuardedBy("mService")
+    void setLruSeq(int lruSeq) {
+        mLruSeq = lruSeq;
+    }
+
+    @GuardedBy("mService")
+    String getIsolatedEntryPoint() {
+        return mIsolatedEntryPoint;
+    }
+
+    @GuardedBy("mService")
+    void setIsolatedEntryPoint(String isolatedEntryPoint) {
+        mIsolatedEntryPoint = isolatedEntryPoint;
+    }
+
+    @GuardedBy("mService")
+    String[] getIsolatedEntryPointArgs() {
+        return mIsolatedEntryPointArgs;
+    }
+
+    @GuardedBy("mService")
+    void setIsolatedEntryPointArgs(String[] isolatedEntryPointArgs) {
+        mIsolatedEntryPointArgs = isolatedEntryPointArgs;
+    }
+
+    @GuardedBy("mService")
+    boolean isInFullBackup() {
+        return mInFullBackup;
+    }
+
+    @GuardedBy("mService")
+    void setInFullBackup(boolean inFullBackup) {
+        mInFullBackup = inFullBackup;
     }
 
     @Override
     public boolean isCached() {
-        return mCached;
-    }
-
-    int getCacheOomRankerUseCount() {
-        return mCacheOomRankerUseCount;
+        return mState.isCached();
     }
 
     boolean hasActivities() {
@@ -864,6 +894,22 @@
         return mWindowProcessController.hasRecentTasks();
     }
 
+    @GuardedBy({"mService", "mProcLock"})
+    boolean onCleanupApplicationRecordLSP(ProcessStatsService processStats, boolean allowRestart) {
+        mErrorState.onCleanupApplicationRecordLSP();
+
+        resetPackageList(processStats);
+        unlinkDeathRecipient();
+        makeInactive(processStats);
+        setWaitingToKill(null);
+
+        mState.onCleanupApplicationRecordLSP();
+        mServices.onCleanupApplicationRecordLocked();
+        mReceivers.onCleanupApplicationRecordLocked();
+
+        return mProviders.onCleanupApplicationRecordLocked(allowRestart);
+    }
+
     /**
      * This method returns true if any of the activities within the process record are interesting
      * to the user. See HistoryRecord.isInterestingToUserLocked()
@@ -873,74 +919,26 @@
             return true;
         }
 
-        final int servicesSize = mServices.size();
-        for (int i = 0; i < servicesSize; i++) {
-            ServiceRecord r = mServices.valueAt(i);
-            if (r.isForeground) {
-                return true;
-            }
-        }
-        return false;
+        return mServices.hasForegroundServices();
     }
 
-    public void unlinkDeathRecipient() {
-        if (deathRecipient != null && thread != null) {
-            thread.asBinder().unlinkToDeath(deathRecipient, 0);
-        }
-        deathRecipient = null;
-    }
-
-    void updateHasAboveClientLocked() {
-        hasAboveClient = false;
-        for (int i=connections.size()-1; i>=0; i--) {
-            ConnectionRecord cr = connections.valueAt(i);
-            if ((cr.flags&Context.BIND_ABOVE_CLIENT) != 0) {
-                hasAboveClient = true;
-                break;
-            }
-        }
-    }
-
-    int modifyRawOomAdj(int adj) {
-        if (hasAboveClient) {
-            // If this process has bound to any services with BIND_ABOVE_CLIENT,
-            // then we need to drop its adjustment to be lower than the service's
-            // in order to honor the request.  We want to drop it by one adjustment
-            // level...  but there is special meaning applied to various levels so
-            // we will skip some of them.
-            if (adj < ProcessList.FOREGROUND_APP_ADJ) {
-                // System process will not get dropped, ever
-            } else if (adj < ProcessList.VISIBLE_APP_ADJ) {
-                adj = ProcessList.VISIBLE_APP_ADJ;
-            } else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) {
-                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-            } else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
-                adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
-            } else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
-                adj = ProcessList.CACHED_APP_MIN_ADJ;
-            } else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
-                adj++;
-            }
-        }
-        return adj;
-    }
-
-    void scheduleCrash(String message) {
+    @GuardedBy("mService")
+    void scheduleCrashLocked(String message) {
         // Checking killedbyAm should keep it from showing the crash dialog if the process
         // was already dead for a good / normal reason.
-        if (!killedByAm) {
-            if (thread != null) {
-                if (pid == Process.myPid()) {
+        if (!mKilledByAm) {
+            if (mThread != null) {
+                if (mPid == Process.myPid()) {
                     Slog.w(TAG, "scheduleCrash: trying to crash system process!");
                     return;
                 }
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    thread.scheduleCrash(message);
+                    mThread.scheduleCrash(message);
                 } catch (RemoteException e) {
                     // If it's already dead our work is done. If it's wedged just kill it.
                     // We won't get the crash dialog or the error reporting.
-                    kill("scheduleCrash for '" + message + "' failed",
+                    killLocked("scheduleCrash for '" + message + "' failed",
                             ApplicationExitInfo.REASON_CRASH, true);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -949,30 +947,36 @@
         }
     }
 
-    void kill(String reason, @Reason int reasonCode, boolean noisy) {
-        kill(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
+    @GuardedBy("mService")
+    void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
+        killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
     }
 
-    void kill(String reason, @Reason int reasonCode, @SubReason int subReason, boolean noisy) {
-        if (!killedByAm) {
+    @GuardedBy("mService")
+    void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
+            boolean noisy) {
+        if (!mKilledByAm) {
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
             if (mService != null && (noisy || info.uid == mService.mCurOomAdjUid)) {
                 mService.reportUidInfoMessageLocked(TAG,
-                        "Killing " + toShortString() + " (adj " + setAdj + "): " + reason,
-                        info.uid);
+                        "Killing " + toShortString() + " (adj " + mState.getSetAdj()
+                        + "): " + reason, info.uid);
             }
-            if (pid > 0) {
+            if (mPid > 0) {
                 mService.mProcessList.noteAppKill(this, reasonCode, subReason, reason);
-                EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
-                Process.killProcessQuiet(pid);
-                ProcessList.killProcessGroup(uid, pid);
+                EventLog.writeEvent(EventLogTags.AM_KILL,
+                        userId, mPid, processName, mState.getSetAdj(), reason);
+                Process.killProcessQuiet(mPid);
+                ProcessList.killProcessGroup(uid, mPid);
             } else {
-                pendingStart = false;
+                mPendingStart = false;
             }
             if (!mPersistent) {
-                killed = true;
-                killedByAm = true;
-                mKillTime = SystemClock.uptimeMillis();
+                synchronized (mProcLock) {
+                    mKilled = true;
+                    mKilledByAm = true;
+                    mKillTime = SystemClock.uptimeMillis();
+                }
             }
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         }
@@ -985,7 +989,7 @@
 
     public void dumpDebug(ProtoOutputStream proto, long fieldId, int lruIndex) {
         long token = proto.start(fieldId);
-        proto.write(ProcessRecordProto.PID, pid);
+        proto.write(ProcessRecordProto.PID, mPid);
         proto.write(ProcessRecordProto.PROCESS_NAME, processName);
         proto.write(ProcessRecordProto.UID, info.uid);
         if (UserHandle.getAppId(info.uid) >= Process.FIRST_APPLICATION_UID) {
@@ -1003,16 +1007,17 @@
     }
 
     public String toShortString() {
+        final String shortStringName = mShortStringName;
         if (shortStringName != null) {
             return shortStringName;
         }
         StringBuilder sb = new StringBuilder(128);
         toShortString(sb);
-        return shortStringName = sb.toString();
+        return mShortStringName = sb.toString();
     }
 
     void toShortString(StringBuilder sb) {
-        sb.append(pid);
+        sb.append(mPid);
         sb.append(':');
         sb.append(processName);
         sb.append('/');
@@ -1037,6 +1042,7 @@
     }
 
     public String toString() {
+        final String stringName = mStringName;
         if (stringName != null) {
             return stringName;
         }
@@ -1046,340 +1052,91 @@
         sb.append(' ');
         toShortString(sb);
         sb.append('}');
-        return stringName = sb.toString();
-    }
-
-    public String makeAdjReason() {
-        if (adjSource != null || adjTarget != null) {
-            StringBuilder sb = new StringBuilder(128);
-            sb.append(' ');
-            if (adjTarget instanceof ComponentName) {
-                sb.append(((ComponentName)adjTarget).flattenToShortString());
-            } else if (adjTarget != null) {
-                sb.append(adjTarget.toString());
-            } else {
-                sb.append("{null}");
-            }
-            sb.append("<=");
-            if (adjSource instanceof ProcessRecord) {
-                sb.append("Proc{");
-                sb.append(((ProcessRecord)adjSource).toShortString());
-                sb.append("}");
-            } else if (adjSource != null) {
-                sb.append(adjSource.toString());
-            } else {
-                sb.append("{null}");
-            }
-            return sb.toString();
-        }
-        return null;
+        return mStringName = sb.toString();
     }
 
     /*
      *  Return true if package has been added false if not
      */
     public boolean addPackage(String pkg, long versionCode, ProcessStatsService tracker) {
-        if (!pkgList.containsKey(pkg)) {
-            synchronized (tracker.mLock) {
-                ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
-                        versionCode);
-                if (baseProcessTracker != null) {
-                    tracker.updateProcessStateHolderLocked(holder, pkg, info.uid, versionCode,
-                            processName);
-                    pkgList.put(pkg, holder);
-                    if (holder.state != baseProcessTracker) {
-                        holder.state.makeActive();
+        synchronized (tracker.mLock) {
+            synchronized (mPkgList) {
+                if (!mPkgList.containsKey(pkg)) {
+                    ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                            versionCode);
+                    final ProcessState baseProcessTracker = mProfile.getBaseProcessTracker();
+                    if (baseProcessTracker != null) {
+                        tracker.updateProcessStateHolderLocked(holder, pkg, info.uid, versionCode,
+                                processName);
+                        mPkgList.put(pkg, holder);
+                        if (holder.state != baseProcessTracker) {
+                            holder.state.makeActive();
+                        }
+                    } else {
+                        mPkgList.put(pkg, holder);
                     }
-                } else {
-                    pkgList.put(pkg, holder);
+                    return true;
                 }
             }
-            return true;
         }
         return false;
     }
 
-    public int getSetAdjWithServices() {
-        if (setAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
-            if (hasStartedServices) {
-                return ProcessList.SERVICE_B_ADJ;
-            }
-        }
-        return setAdj;
-    }
-
-    public void forceProcessStateUpTo(int newState) {
-        if (mRepProcState > newState) {
-            mRepProcState = newState;
-            setCurProcState(newState);
-            setCurRawProcState(newState);
-            for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                        uid, processName, pkgList.keyAt(ipkg),
-                        ActivityManager.processStateAmToProto(mRepProcState),
-                        pkgList.valueAt(ipkg).appVersion);
-            }
-        }
-    }
-
     /*
      *  Delete all packages from list except the package indicated in info
      */
     public void resetPackageList(ProcessStatsService tracker) {
-        final int N = pkgList.size();
         synchronized (tracker.mLock) {
-            if (baseProcessTracker != null) {
-                long now = SystemClock.uptimeMillis();
-                baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
-                        tracker.getMemFactorLocked(), now, pkgList.mPkgList);
-                for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
-                    FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                            uid, processName, pkgList.keyAt(ipkg),
-                            ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
-                            pkgList.valueAt(ipkg).appVersion);
-                }
-                if (N != 1) {
-                    for (int i = 0; i < N; i++) {
-                        ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
-                        if (holder.state != null && holder.state != baseProcessTracker) {
-                            holder.state.makeInactive();
+            final ProcessState baseProcessTracker = mProfile.getBaseProcessTracker();
+            synchronized (mPkgList) {
+                final int numOfPkgs = mPkgList.size();
+                if (baseProcessTracker != null) {
+                    long now = SystemClock.uptimeMillis();
+                    baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
+                            tracker.getMemFactorLocked(), now, mPkgList.getPackageListLocked());
+                    mPkgList.forEachPackage((pkgName, holder) ->
+                            FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+                                uid, processName, pkgName,
+                                ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
+                                holder.appVersion)
+                    );
+                    if (numOfPkgs != 1) {
+                        mPkgList.forEachPackageProcessStats(holder -> {
+                            if (holder.state != null && holder.state != baseProcessTracker) {
+                                holder.state.makeInactive();
+                            }
+                        });
+                        mPkgList.clear();
+                        ProcessStats.ProcessStateHolder holder =
+                                new ProcessStats.ProcessStateHolder(info.longVersionCode);
+                        tracker.updateProcessStateHolderLocked(holder, info.packageName, info.uid,
+                                info.longVersionCode, processName);
+                        mPkgList.put(info.packageName, holder);
+                        if (holder.state != baseProcessTracker) {
+                            holder.state.makeActive();
                         }
-
                     }
-                    pkgList.clear();
-                    ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
-                            info.longVersionCode);
-                    tracker.updateProcessStateHolderLocked(holder, info.packageName, info.uid,
-                            info.longVersionCode, processName);
-                    pkgList.put(info.packageName, holder);
-                    if (holder.state != baseProcessTracker) {
-                        holder.state.makeActive();
-                    }
+                } else if (numOfPkgs != 1) {
+                    mPkgList.clear();
+                    mPkgList.put(info.packageName,
+                            new ProcessStats.ProcessStateHolder(info.longVersionCode));
                 }
-            } else if (N != 1) {
-                pkgList.clear();
-                pkgList.put(info.packageName,
-                        new ProcessStats.ProcessStateHolder(info.longVersionCode));
             }
         }
     }
 
-    public String[] getPackageList() {
-        int size = pkgList.size();
-        if (size == 0) {
-            return null;
-        }
-        String list[] = new String[size];
-        for (int i=0; i<pkgList.size(); i++) {
-            list[i] = pkgList.keyAt(i);
-        }
-        return list;
+    String[] getPackageList() {
+        return mPkgList.getPackageList();
     }
 
-    public List<VersionedPackage> getPackageListWithVersionCode() {
-        int size = pkgList.size();
-        if (size == 0) {
-            return null;
-        }
-        List<VersionedPackage> list = new ArrayList<>();
-        for (int i = 0; i < pkgList.size(); i++) {
-            list.add(new VersionedPackage(pkgList.keyAt(i), pkgList.valueAt(i).appVersion));
-        }
-        return list;
+    List<VersionedPackage> getPackageListWithVersionCode() {
+        return mPkgList.getPackageListWithVersionCode();
     }
 
     WindowProcessController getWindowProcessController() {
         return mWindowProcessController;
     }
 
-    void setCurrentSchedulingGroup(int curSchedGroup) {
-        mCurSchedGroup = curSchedGroup;
-        mWindowProcessController.setCurrentSchedulingGroup(curSchedGroup);
-    }
-
-    int getCurrentSchedulingGroup() {
-        return mCurSchedGroup;
-    }
-
-    void setCurProcState(int curProcState) {
-        mCurProcState = curProcState;
-        mWindowProcessController.setCurrentProcState(mCurProcState);
-    }
-
-    int getCurProcState() {
-        return mCurProcState;
-    }
-
-    void setCurRawProcState(int curRawProcState) {
-        mCurRawProcState = curRawProcState;
-    }
-
-    int getCurRawProcState() {
-        return mCurRawProcState;
-    }
-
-    void setReportedProcState(int repProcState) {
-        mRepProcState = repProcState;
-        for (int ipkg = pkgList.size() - 1; ipkg >= 0; ipkg--) {
-            FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
-                    uid, processName, pkgList.keyAt(ipkg),
-                    ActivityManager.processStateAmToProto(mRepProcState),
-                    pkgList.valueAt(ipkg).appVersion);
-        }
-        mWindowProcessController.setReportedProcState(repProcState);
-    }
-
-    int getReportedProcState() {
-        return mRepProcState;
-    }
-
-    void setCrashing(boolean crashing) {
-        mCrashing = crashing;
-        mWindowProcessController.setCrashing(crashing);
-    }
-
-    boolean isCrashing() {
-        return mCrashing;
-    }
-
-    void setNotResponding(boolean notResponding) {
-        mNotResponding = notResponding;
-        mWindowProcessController.setNotResponding(notResponding);
-    }
-
-    boolean isNotResponding() {
-        return mNotResponding;
-    }
-
-    void setPersistent(boolean persistent) {
-        mPersistent = persistent;
-        mWindowProcessController.setPersistent(persistent);
-    }
-
-    boolean isPersistent() {
-        return mPersistent;
-    }
-
-    public void setRequiredAbi(String requiredAbi) {
-        mRequiredAbi = requiredAbi;
-        mWindowProcessController.setRequiredAbi(requiredAbi);
-    }
-
-    String getRequiredAbi() {
-        return mRequiredAbi;
-    }
-
-    void setHasForegroundServices(boolean hasForegroundServices, int fgServiceTypes) {
-        mHasForegroundServices = hasForegroundServices;
-        mFgServiceTypes = fgServiceTypes;
-        mWindowProcessController.setHasForegroundServices(hasForegroundServices);
-    }
-
-    boolean hasForegroundServices() {
-        return mHasForegroundServices;
-    }
-
-    boolean hasLocationForegroundServices() {
-        return mHasForegroundServices
-                && (mFgServiceTypes & ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION) != 0;
-    }
-
-    boolean hasLocationCapability() {
-        return (setCapability & ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0;
-    }
-
-    int getForegroundServiceTypes() {
-        return mHasForegroundServices ? mFgServiceTypes : 0;
-    }
-
-    int getReportedForegroundServiceTypes() {
-        return mRepFgServiceTypes;
-    }
-
-    void setReportedForegroundServiceTypes(int foregroundServiceTypes) {
-        mRepFgServiceTypes = foregroundServiceTypes;
-    }
-
-    void setHasForegroundActivities(boolean hasForegroundActivities) {
-        mHasForegroundActivities = hasForegroundActivities;
-    }
-
-    boolean hasForegroundActivities() {
-        return mHasForegroundActivities;
-    }
-
-    void setHasClientActivities(boolean hasClientActivities) {
-        mHasClientActivities = hasClientActivities;
-        mWindowProcessController.setHasClientActivities(hasClientActivities);
-    }
-
-    boolean hasClientActivities() {
-        return mHasClientActivities;
-    }
-
-    void setHasTopUi(boolean hasTopUi) {
-        mHasTopUi = hasTopUi;
-        mWindowProcessController.setHasTopUi(hasTopUi);
-    }
-
-    boolean hasTopUi() {
-        return mHasTopUi;
-    }
-
-    void setHasOverlayUi(boolean hasOverlayUi) {
-        mHasOverlayUi = hasOverlayUi;
-        mWindowProcessController.setHasOverlayUi(hasOverlayUi);
-    }
-
-    boolean hasOverlayUi() {
-        return mHasOverlayUi;
-    }
-
-    void setInteractionEventTime(long interactionEventTime) {
-        mInteractionEventTime = interactionEventTime;
-        mWindowProcessController.setInteractionEventTime(interactionEventTime);
-    }
-
-    long getInteractionEventTime() {
-        return mInteractionEventTime;
-    }
-
-    void setFgInteractionTime(long fgInteractionTime) {
-        mFgInteractionTime = fgInteractionTime;
-        mWindowProcessController.setFgInteractionTime(fgInteractionTime);
-    }
-
-    long getFgInteractionTime() {
-        return mFgInteractionTime;
-    }
-
-    void setWhenUnimportant(long whenUnimportant) {
-        mWhenUnimportant = whenUnimportant;
-        mWindowProcessController.setWhenUnimportant(whenUnimportant);
-    }
-
-    long getWhenUnimportant() {
-        return mWhenUnimportant;
-    }
-
-    void setDebugging(boolean debugging) {
-        mDebugging = debugging;
-        mWindowProcessController.setDebugging(debugging);
-    }
-
-    boolean isDebugging() {
-        return mDebugging;
-    }
-
-    void setUsingWrapper(boolean usingWrapper) {
-        mUsingWrapper = usingWrapper;
-        mWindowProcessController.setUsingWrapper(usingWrapper);
-    }
-
-    boolean isUsingWrapper() {
-        return mUsingWrapper;
-    }
-
     /**
      * Allows background activity starts using token {@param entity}. Optionally, you can provide
      * {@param originatingToken} if you have one such originating token, this is useful for tracing
@@ -1397,106 +1154,32 @@
         mWindowProcessController.removeAllowBackgroundActivityStartsToken(entity);
     }
 
-    void addBoundClientUid(int clientUid) {
-        mBoundClientUids.add(clientUid);
-        mWindowProcessController.setBoundClientUids(mBoundClientUids);
-    }
-
-    void updateBoundClientUids() {
-        if (mServices.isEmpty()) {
-            clearBoundClientUids();
-            return;
-        }
-        // grab a set of clientUids of all connections of all services
-        ArraySet<Integer> boundClientUids = new ArraySet<>();
-        final int serviceCount = mServices.size();
-        for (int j = 0; j < serviceCount; j++) {
-            ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns =
-                    mServices.valueAt(j).getConnections();
-            final int N = conns.size();
-            for (int conni = 0; conni < N; conni++) {
-                ArrayList<ConnectionRecord> c = conns.valueAt(conni);
-                for (int i = 0; i < c.size(); i++) {
-                    boundClientUids.add(c.get(i).clientUid);
-                }
-            }
-        }
-        mBoundClientUids = boundClientUids;
-        mWindowProcessController.setBoundClientUids(mBoundClientUids);
-    }
-
-    void addBoundClientUidsOfNewService(ServiceRecord sr) {
-        if (sr == null) {
-            return;
-        }
-        ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns = sr.getConnections();
-        for (int conni = conns.size() - 1; conni >= 0; conni--) {
-            ArrayList<ConnectionRecord> c = conns.valueAt(conni);
-            for (int i = 0; i < c.size(); i++) {
-                mBoundClientUids.add(c.get(i).clientUid);
-            }
-        }
-        mWindowProcessController.setBoundClientUids(mBoundClientUids);
-    }
-
-    void clearBoundClientUids() {
-        mBoundClientUids.clear();
-        mWindowProcessController.setBoundClientUids(mBoundClientUids);
-    }
-
-    void setActiveInstrumentation(ActiveInstrumentation instr) {
-        mInstr = instr;
-        boolean isInstrumenting = instr != null;
-        mWindowProcessController.setInstrumenting(
-                isInstrumenting,
-                isInstrumenting ? instr.mSourceUid : -1,
-                isInstrumenting && instr.mHasBackgroundActivityStartsPermission);
-    }
-
-    ActiveInstrumentation getActiveInstrumentation() {
-        return mInstr;
-    }
-
-    void setCurRawAdj(int curRawAdj) {
-        mCurRawAdj = curRawAdj;
-        mWindowProcessController.setPerceptible(curRawAdj <= ProcessList.PERCEPTIBLE_APP_ADJ);
-    }
-
-    int getCurRawAdj() {
-        return mCurRawAdj;
-    }
-
     @Override
     public void clearProfilerIfNeeded() {
-        synchronized (mService) {
-            mService.mAppProfiler.clearProfilerLocked();
+        synchronized (mService.mAppProfiler.mProfilerLock) {
+            mService.mAppProfiler.clearProfilerLPf();
         }
     }
 
     @Override
     public void updateServiceConnectionActivities() {
         synchronized (mService) {
-            mService.mServices.updateServiceConnectionActivitiesLocked(this);
+            mService.mServices.updateServiceConnectionActivitiesLocked(mServices);
         }
     }
 
     @Override
     public void setPendingUiClean(boolean pendingUiClean) {
-        synchronized (mService) {
-            mPendingUiClean = pendingUiClean;
-            mWindowProcessController.setPendingUiClean(pendingUiClean);
+        synchronized (mProcLock) {
+            mProfile.setPendingUiClean(pendingUiClean);
         }
     }
 
-    boolean hasPendingUiClean() {
-        return mPendingUiClean;
-    }
-
     @Override
     public void setPendingUiCleanAndForceProcessStateUpTo(int newState) {
         synchronized (mService) {
             setPendingUiClean(true);
-            forceProcessStateUpTo(newState);
+            mState.forceProcessStateUpTo(newState);
         }
     }
 
@@ -1505,42 +1188,39 @@
             boolean updateOomAdj) {
         synchronized (mService) {
             if (updateServiceConnectionActivities) {
-                mService.mServices.updateServiceConnectionActivitiesLocked(this);
+                mService.mServices.updateServiceConnectionActivitiesLocked(mServices);
             }
-            if (thread == null) {
+            if (mThread == null) {
                 // Only update lru and oom-adj if the process is alive. Because it may be called
                 // when cleaning up the last activity from handling process died, the dead process
                 // should not be added to lru list again.
                 return;
             }
-            mService.mProcessList.updateLruProcessLocked(this, activityChange, null /* client */);
+            mService.updateLruProcessLocked(this, activityChange, null /* client */);
             if (updateOomAdj) {
                 mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
             }
         }
     }
 
-    @Override
-    public boolean isRemoved() {
-        return removed;
-    }
-
     /**
      * Returns the total time (in milliseconds) spent executing in both user and system code.
      * Safe to call without lock held.
      */
     @Override
     public long getCpuTime() {
-        return mService.mAppProfiler.getCpuTimeForPid(pid);
+        return mService.mAppProfiler.getCpuTimeForPid(mPid);
     }
 
     @Override
     public void onStartActivity(int topProcessState, boolean setProfileProc, String packageName,
             long versionCode) {
         synchronized (mService) {
-            waitingToKill = null;
+            mWaitingToKill = null;
             if (setProfileProc) {
-                mService.mAppProfiler.setProfileProcLocked(this);
+                synchronized (mService.mAppProfiler.mProfilerLock) {
+                    mService.mAppProfiler.setProfileProcLPf(this);
+                }
             }
             if (packageName != null) {
                 addPackage(packageName, versionCode, mService.mProcessStats);
@@ -1549,9 +1229,9 @@
             // Update oom adj first, we don't want the additional states are involved in this round.
             updateProcessInfo(false /* updateServiceConnectionActivities */,
                     true /* activityChange */, true /* updateOomAdj */);
-            hasShownUi = true;
             setPendingUiClean(true);
-            forceProcessStateUpTo(topProcessState);
+            mState.setHasShownUi(true);
+            mState.forceProcessStateUpTo(topProcessState);
         }
     }
 
@@ -1564,20 +1244,12 @@
 
     @Override
     public void setRunningRemoteAnimation(boolean runningRemoteAnimation) {
-        if (pid == Process.myPid()) {
+        if (mPid == Process.myPid()) {
             Slog.wtf(TAG, "system can't run remote animation");
             return;
         }
         synchronized (mService) {
-            if (this.runningRemoteAnimation == runningRemoteAnimation) {
-                return;
-            }
-            this.runningRemoteAnimation = runningRemoteAnimation;
-            if (DEBUG_OOM_ADJ) {
-                Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
-                        + " for pid=" + pid);
-            }
-            mService.updateOomAdjLocked(this, true, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+            mState.setRunningRemoteAnimation(runningRemoteAnimation);
         }
     }
 
@@ -1586,7 +1258,7 @@
     }
 
     public int getProcessClassEnum() {
-        if (pid == MY_PID) {
+        if (mPid == MY_PID) {
             return ServerProtoEnums.SYSTEM_SERVER;
         }
         if (info == null) {
@@ -1596,687 +1268,9 @@
             ServerProtoEnums.DATA_APP;
     }
 
-    /**
-     * Unless configured otherwise, swallow ANRs in background processes & kill the process.
-     * Non-private access is for tests only.
-     */
-    @VisibleForTesting
-    boolean isSilentAnr() {
-        return !getShowBackground() && !isInterestingForBackgroundTraces();
-    }
-
     /** Non-private access is for tests only. */
     @VisibleForTesting
     List<ProcessRecord> getLruProcessList() {
-        return mService.mProcessList.mLruProcesses;
-    }
-
-    /** Non-private access is for tests only. */
-    @VisibleForTesting
-    boolean isMonitorCpuUsage() {
-        return mService.mAppProfiler.MONITOR_CPU_USAGE;
-    }
-
-    void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
-            String parentShortComponentName, WindowProcessController parentProcess,
-            boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
-        ArrayList<Integer> firstPids = new ArrayList<>(5);
-        SparseArray<Boolean> lastPids = new SparseArray<>(20);
-
-        mWindowProcessController.appEarlyNotResponding(annotation, () -> kill("anr",
-                  ApplicationExitInfo.REASON_ANR, true));
-
-        long anrTime = SystemClock.uptimeMillis();
-        if (isMonitorCpuUsage()) {
-            mService.updateCpuStatsNow();
-        }
-
-        final boolean isSilentAnr;
-        synchronized (mService) {
-            // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
-            if (mService.mAtmInternal.isShuttingDown()) {
-                Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
-                return;
-            } else if (isNotResponding()) {
-                Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
-                return;
-            } else if (isCrashing()) {
-                Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
-                return;
-            } else if (killedByAm) {
-                Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
-                return;
-            } else if (killed) {
-                Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
-                return;
-            }
-
-            // In case we come through here for the same app before completing
-            // this one, mark as anring now so we will bail out.
-            setNotResponding(true);
-
-            // Log the ANR to the event log.
-            EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
-                    annotation);
-
-            // Dump thread traces as quickly as we can, starting with "interesting" processes.
-            firstPids.add(pid);
-
-            // Don't dump other PIDs if it's a background ANR or is requested to only dump self.
-            isSilentAnr = isSilentAnr();
-            if (!isSilentAnr && !onlyDumpSelf) {
-                int parentPid = pid;
-                if (parentProcess != null && parentProcess.getPid() > 0) {
-                    parentPid = parentProcess.getPid();
-                }
-                if (parentPid != pid) firstPids.add(parentPid);
-
-                if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);
-
-                for (int i = getLruProcessList().size() - 1; i >= 0; i--) {
-                    ProcessRecord r = getLruProcessList().get(i);
-                    if (r != null && r.thread != null) {
-                        int myPid = r.pid;
-                        if (myPid > 0 && myPid != pid && myPid != parentPid && myPid != MY_PID) {
-                            if (r.isPersistent()) {
-                                firstPids.add(myPid);
-                                if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
-                            } else if (r.treatLikeActivity) {
-                                firstPids.add(myPid);
-                                if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
-                            } else {
-                                lastPids.put(myPid, Boolean.TRUE);
-                                if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        // Check if package is still being loaded
-        boolean isPackageLoading = false;
-        final PackageManagerInternal packageManagerInternal =
-                mService.getPackageManagerInternalLocked();
-        if (aInfo != null && aInfo.packageName != null) {
-            IncrementalStatesInfo incrementalStatesInfo =
-                    packageManagerInternal.getIncrementalStatesInfo(
-                            aInfo.packageName, uid, userId);
-            if (incrementalStatesInfo != null) {
-                isPackageLoading = incrementalStatesInfo.isLoading();
-            }
-        }
-
-        // Log the ANR to the main log.
-        StringBuilder info = new StringBuilder();
-        info.setLength(0);
-        info.append("ANR in ").append(processName);
-        if (activityShortComponentName != null) {
-            info.append(" (").append(activityShortComponentName).append(")");
-        }
-        info.append("\n");
-        info.append("PID: ").append(pid).append("\n");
-        if (annotation != null) {
-            info.append("Reason: ").append(annotation).append("\n");
-        }
-        if (parentShortComponentName != null
-                && parentShortComponentName.equals(activityShortComponentName)) {
-            info.append("Parent: ").append(parentShortComponentName).append("\n");
-        }
-
-        if (isPackageLoading) {
-            // Report in the main log that the package is still loading
-            final float loadingProgress = packageManagerInternal.getIncrementalStatesInfo(
-                    aInfo.packageName, uid, userId).getProgress();
-            info.append("Package is ").append((int) (loadingProgress * 100)).append("% loaded.\n");
-        }
-
-        StringBuilder report = new StringBuilder();
-        report.append(MemoryPressureUtil.currentPsiState());
-        ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
-
-        // don't dump native PIDs for background ANRs unless it is the process of interest
-        String[] nativeProcs = null;
-        if (isSilentAnr || onlyDumpSelf) {
-            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
-                if (NATIVE_STACKS_OF_INTEREST[i].equals(processName)) {
-                    nativeProcs = new String[] { processName };
-                    break;
-                }
-            }
-        } else {
-            nativeProcs = NATIVE_STACKS_OF_INTEREST;
-        }
-
-        int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
-        ArrayList<Integer> nativePids = null;
-
-        if (pids != null) {
-            nativePids = new ArrayList<>(pids.length);
-            for (int i : pids) {
-                nativePids.add(i);
-            }
-        }
-
-        // For background ANRs, don't pass the ProcessCpuTracker to
-        // avoid spending 1/2 second collecting stats to rank lastPids.
-        StringWriter tracesFileException = new StringWriter();
-        // To hold the start and end offset to the ANR trace file respectively.
-        final long[] offsets = new long[2];
-        File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
-                isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
-                nativePids, tracesFileException, offsets);
-
-        if (isMonitorCpuUsage()) {
-            mService.updateCpuStatsNow();
-            mService.mAppProfiler.printCurrentCpuState(report, anrTime);
-            info.append(processCpuTracker.printCurrentLoad());
-            info.append(report);
-        }
-        report.append(tracesFileException.getBuffer());
-
-        info.append(processCpuTracker.printCurrentState(anrTime));
-
-        Slog.e(TAG, info.toString());
-        if (tracesFile == null) {
-            // There is no trace file, so dump (only) the alleged culprit's threads to the log
-            Process.sendSignal(pid, Process.SIGNAL_QUIT);
-        } else if (offsets[1] > 0) {
-            // We've dumped into the trace file successfully
-            mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace(
-                    pid, uid, getPackageList(), tracesFile, offsets[0], offsets[1]);
-        }
-
-        FrameworkStatsLog.write(FrameworkStatsLog.ANR_OCCURRED, uid, processName,
-                activityShortComponentName == null ? "unknown": activityShortComponentName,
-                annotation,
-                (this.info != null) ? (this.info.isInstantApp()
-                        ? FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__TRUE
-                        : FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__FALSE)
-                        : FrameworkStatsLog.ANROCCURRED__IS_INSTANT_APP__UNAVAILABLE,
-                isInterestingToUserLocked()
-                        ? FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__FOREGROUND
-                        : FrameworkStatsLog.ANROCCURRED__FOREGROUND_STATE__BACKGROUND,
-                getProcessClassEnum(),
-                (this.info != null) ? this.info.packageName : "", isPackageLoading);
-        final ProcessRecord parentPr = parentProcess != null
-                ? (ProcessRecord) parentProcess.mOwner : null;
-        mService.addErrorToDropBox("anr", this, processName, activityShortComponentName,
-                parentShortComponentName, parentPr, annotation, report.toString(), tracesFile,
-                null);
-
-        if (mWindowProcessController.appNotResponding(info.toString(), () -> kill("anr",
-                ApplicationExitInfo.REASON_ANR, true),
-                () -> {
-                    synchronized (mService) {
-                        mService.mServices.scheduleServiceTimeoutLocked(this);
-                    }
-                })) {
-            return;
-        }
-
-        synchronized (mService) {
-            // mBatteryStatsService can be null if the AMS is constructed with injector only. This
-            // will only happen in tests.
-            if (mService.mBatteryStatsService != null) {
-                mService.mBatteryStatsService.noteProcessAnr(processName, uid);
-            }
-
-            if (isSilentAnr() && !isDebugging()) {
-                kill("bg anr", ApplicationExitInfo.REASON_ANR, true);
-                return;
-            }
-
-            // Set the app's notResponding state, and look up the errorReportReceiver
-            makeAppNotRespondingLocked(activityShortComponentName,
-                    annotation != null ? "ANR " + annotation : "ANR", info.toString());
-
-            // Notify package manager service to possibly update package state
-            if (aInfo != null && aInfo.packageName != null) {
-                packageManagerInternal.notifyPackageCrashOrAnr(aInfo.packageName);
-            }
-
-            // mUiHandler can be null if the AMS is constructed with injector only. This will only
-            // happen in tests.
-            if (mService.mUiHandler != null) {
-                // Bring up the infamous App Not Responding dialog
-                Message msg = Message.obtain();
-                msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
-                msg.obj = new AppNotRespondingDialog.Data(this, aInfo, aboveSystem);
-
-                mService.mUiHandler.sendMessage(msg);
-            }
-        }
-    }
-
-    private void makeAppNotRespondingLocked(String activity, String shortMsg, String longMsg) {
-        setNotResponding(true);
-        // mAppErrors can be null if the AMS is constructed with injector only. This will only
-        // happen in tests.
-        if (mService.mAppErrors != null) {
-            notRespondingReport = mService.mAppErrors.generateProcessError(this,
-                    ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
-                    activity, shortMsg, longMsg, null);
-        }
-        startAppProblemLocked();
-        getWindowProcessController().stopFreezingActivities();
-    }
-
-    void startAppProblemLocked() {
-        // If this app is not running under the current user, then we can't give it a report button
-        // because that would require launching the report UI under a different user.
-        errorReportReceiver = null;
-
-        for (int userId : mService.mUserController.getCurrentProfileIds()) {
-            if (this.userId == userId) {
-                errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
-                        mService.mContext, info.packageName, info.flags);
-            }
-        }
-        mService.skipCurrentReceiverLocked(this);
-    }
-
-    private boolean isInterestingForBackgroundTraces() {
-        // The system_server is always considered interesting.
-        if (pid == MY_PID) {
-            return true;
-        }
-
-        // A package is considered interesting if any of the following is true :
-        //
-        // - It's displaying an activity.
-        // - It's the SystemUI.
-        // - It has an overlay or a top UI visible.
-        //
-        // NOTE: The check whether a given ProcessRecord belongs to the systemui
-        // process is a bit of a kludge, but the same pattern seems repeated at
-        // several places in the system server.
-        return isInterestingToUserLocked() ||
-                (info != null && "com.android.systemui".equals(info.packageName))
-                || (hasTopUi() || hasOverlayUi());
-    }
-
-    private boolean getShowBackground() {
-        return Settings.Secure.getInt(mService.mContext.getContentResolver(),
-                Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
-    }
-
-    void resetCachedInfo() {
-        mCachedHasActivities = VALUE_INVALID;
-        mCachedIsHeavyWeight = VALUE_INVALID;
-        mCachedHasVisibleActivities = VALUE_INVALID;
-        mCachedIsHomeProcess = VALUE_INVALID;
-        mCachedIsPreviousProcess = VALUE_INVALID;
-        mCachedHasRecentTasks = VALUE_INVALID;
-        mCachedIsReceivingBroadcast = VALUE_INVALID;
-        mCachedAdj = ProcessList.INVALID_ADJ;
-        mCachedForegroundActivities = false;
-        mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-        mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-    }
-
-    boolean getCachedHasActivities() {
-        if (mCachedHasActivities == VALUE_INVALID) {
-            mCachedHasActivities = getWindowProcessController().hasActivities() ? VALUE_TRUE
-                    : VALUE_FALSE;
-        }
-        return mCachedHasActivities == VALUE_TRUE;
-    }
-
-    boolean getCachedIsHeavyWeight() {
-        if (mCachedIsHeavyWeight == VALUE_INVALID) {
-            mCachedIsHeavyWeight = getWindowProcessController().isHeavyWeightProcess()
-                    ? VALUE_TRUE : VALUE_FALSE;
-        }
-        return mCachedIsHeavyWeight == VALUE_TRUE;
-    }
-
-    boolean getCachedHasVisibleActivities() {
-        if (mCachedHasVisibleActivities == VALUE_INVALID) {
-            mCachedHasVisibleActivities = getWindowProcessController().hasVisibleActivities()
-                    ? VALUE_TRUE : VALUE_FALSE;
-        }
-        return mCachedHasVisibleActivities == VALUE_TRUE;
-    }
-
-    boolean getCachedIsHomeProcess() {
-        if (mCachedIsHomeProcess == VALUE_INVALID) {
-            if (getWindowProcessController().isHomeProcess()) {
-                mCachedIsHomeProcess = VALUE_TRUE;
-                mService.mAppProfiler.mHasHomeProcess = true;
-            } else {
-                mCachedIsHomeProcess = VALUE_FALSE;
-            }
-        }
-        return mCachedIsHomeProcess == VALUE_TRUE;
-    }
-
-    boolean getCachedIsPreviousProcess() {
-        if (mCachedIsPreviousProcess == VALUE_INVALID) {
-            if (getWindowProcessController().isPreviousProcess()) {
-                mCachedIsPreviousProcess = VALUE_TRUE;
-                mService.mAppProfiler.mHasPreviousProcess = true;
-            } else {
-                mCachedIsPreviousProcess = VALUE_FALSE;
-            }
-        }
-        return mCachedIsPreviousProcess == VALUE_TRUE;
-    }
-
-    boolean getCachedHasRecentTasks() {
-        if (mCachedHasRecentTasks == VALUE_INVALID) {
-            mCachedHasRecentTasks = getWindowProcessController().hasRecentTasks()
-                    ? VALUE_TRUE : VALUE_FALSE;
-        }
-        return mCachedHasRecentTasks == VALUE_TRUE;
-    }
-
-    boolean getCachedIsReceivingBroadcast(ArraySet<BroadcastQueue> tmpQueue) {
-        if (mCachedIsReceivingBroadcast == VALUE_INVALID) {
-            tmpQueue.clear();
-            mCachedIsReceivingBroadcast = mService.isReceivingBroadcastLocked(this, tmpQueue)
-                    ? VALUE_TRUE : VALUE_FALSE;
-            if (mCachedIsReceivingBroadcast == VALUE_TRUE) {
-                mCachedSchedGroup = tmpQueue.contains(mService.mFgBroadcastQueue)
-                        ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
-            }
-        }
-        return mCachedIsReceivingBroadcast == VALUE_TRUE;
-    }
-
-    void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
-            int adj, boolean foregroundActivities, int procState, int schedGroup, int appUid,
-            int logUid, int processCurTop) {
-        if (mCachedAdj != ProcessList.INVALID_ADJ) {
-            return;
-        }
-        callback.initialize(this, adj, foregroundActivities, procState, schedGroup, appUid, logUid,
-                processCurTop);
-        final int minLayer = Math.min(ProcessList.VISIBLE_APP_LAYER_MAX,
-                getWindowProcessController().computeOomAdjFromActivities(callback));
-
-        mCachedAdj = callback.adj;
-        mCachedForegroundActivities = callback.foregroundActivities;
-        mCachedProcState = callback.procState;
-        mCachedSchedGroup = callback.schedGroup;
-
-        if (mCachedAdj == ProcessList.VISIBLE_APP_ADJ) {
-            mCachedAdj += minLayer;
-        }
-    }
-
-    public void addAllowBackgroundFgsStartsToken(Binder entity) {
-        mBackgroundFgsStartTokens.add(entity);
-    }
-
-    public void removeAllowBackgroundFgsStartsToken(Binder entity) {
-        mBackgroundFgsStartTokens.remove(entity);
-    }
-
-    public boolean areBackgroundFgsStartsAllowedByToken() {
-        return !mBackgroundFgsStartTokens.isEmpty();
-    }
-
-    ErrorDialogController getDialogController() {
-        return mDialogController;
-    }
-
-    void resetAllowStartFgs() {
-        mAllowStartFgsState = PROCESS_STATE_NONEXISTENT;
-        mAllowStartFgs = mAllowStartFgsByPermission;
-    }
-
-    void bumpAllowStartFgsState(int newProcState) {
-        if (newProcState < mAllowStartFgsState) {
-            mAllowStartFgsState = newProcState;
-        }
-    }
-
-    void setAllowStartFgsByPermission() {
-        boolean ret = false;
-        if (!ret) {
-            boolean isSystem = false;
-            final int uid = UserHandle.getAppId(info.uid);
-            switch (uid) {
-                case ROOT_UID:
-                case SYSTEM_UID:
-                case NFC_UID:
-                case SHELL_UID:
-                    isSystem = true;
-                    break;
-                default:
-                    isSystem = false;
-                    break;
-            }
-
-            if (isSystem) {
-                ret = true;
-            }
-        }
-
-        if (!ret) {
-            for (int i = 0; i < ALLOW_BG_START_FGS_PERMISSIONS.length; ++i) {
-                if (ActivityManager.checkComponentPermission(ALLOW_BG_START_FGS_PERMISSIONS[i],
-                        info.uid, -1, true)
-                        == PERMISSION_GRANTED) {
-                    ret = true;
-                    break;
-                }
-            }
-        }
-        mAllowStartFgs = mAllowStartFgsByPermission = ret;
-    }
-
-    boolean isAllowedStartFgsState() {
-        return mAllowStartFgsState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-    }
-
-    void setAllowStartFgs() {
-        if (mAllowStartFgs) {
-            return;
-        }
-        if (!mAllowStartFgs) {
-            mAllowStartFgs = isAllowedStartFgsState();
-        }
-
-        if (!mAllowStartFgs) {
-            // Is the calling UID a device owner app?
-            if (mService.mInternal != null) {
-                mAllowStartFgs = mService.mInternal.isDeviceOwner(info.uid);
-            }
-        }
-
-        if (!mAllowStartFgs) {
-            if (mService.mInternal != null) {
-                mAllowStartFgs = mService.mInternal.isAssociatedCompanionApp(
-                        UserHandle.getUserId(info.uid), info.uid);
-            }
-        }
-
-        if (!mAllowStartFgs) {
-            // Is the calling UID a profile owner app?
-            if (mService.mInternal != null) {
-                mAllowStartFgs = mService.mInternal.isProfileOwner(info.uid);
-            }
-        }
-
-        if (!mAllowStartFgs) {
-            // uid is on DeviceIdleController's user/system allowlist
-            // or AMS's FgsStartTempAllowList.
-            mAllowStartFgs = mService.isWhitelistedForFgsStartLocked(info.uid);
-        }
-    }
-
-    /** A controller to generate error dialogs in {@link ProcessRecord} */
-    class ErrorDialogController {
-        /** dialogs being displayed due to crash */
-        private List<AppErrorDialog> mCrashDialogs;
-        /** dialogs being displayed due to app not responding */
-        private List<AppNotRespondingDialog> mAnrDialogs;
-        /** dialogs displayed due to strict mode violation */
-        private List<StrictModeViolationDialog> mViolationDialogs;
-        /** current wait for debugger dialog */
-        private AppWaitingForDebuggerDialog mWaitDialog;
-
-        boolean hasCrashDialogs() {
-            return mCrashDialogs != null;
-        }
-
-        boolean hasAnrDialogs() {
-            return mAnrDialogs != null;
-        }
-
-        boolean hasViolationDialogs() {
-            return mViolationDialogs != null;
-        }
-
-        boolean hasDebugWaitingDialog() {
-            return mWaitDialog != null;
-        }
-
-        void clearAllErrorDialogs() {
-            clearCrashDialogs();
-            clearAnrDialogs();
-            clearViolationDialogs();
-            clearWaitingDialog();
-        }
-
-        void clearCrashDialogs() {
-            clearCrashDialogs(true /* needDismiss */);
-        }
-
-        void clearCrashDialogs(boolean needDismiss) {
-            if (mCrashDialogs == null) {
-                return;
-            }
-            if (needDismiss) {
-                forAllDialogs(mCrashDialogs, Dialog::dismiss);
-            }
-            mCrashDialogs = null;
-        }
-
-        void clearAnrDialogs() {
-            if (mAnrDialogs == null) {
-                return;
-            }
-            forAllDialogs(mAnrDialogs, Dialog::dismiss);
-            mAnrDialogs = null;
-        }
-
-        void clearViolationDialogs() {
-            if (mViolationDialogs == null) {
-                return;
-            }
-            forAllDialogs(mViolationDialogs, Dialog::dismiss);
-            mViolationDialogs = null;
-        }
-
-        void clearWaitingDialog() {
-            if (mWaitDialog == null) {
-                return;
-            }
-            mWaitDialog.dismiss();
-            mWaitDialog = null;
-        }
-
-        void forAllDialogs(List<? extends BaseErrorDialog> dialogs, Consumer<BaseErrorDialog> c) {
-            for (int i = dialogs.size() - 1; i >= 0; i--) {
-                c.accept(dialogs.get(i));
-            }
-        }
-
-        void showCrashDialogs(AppErrorDialog.Data data) {
-            List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
-            mCrashDialogs = new ArrayList<>();
-            for (int i = contexts.size() - 1; i >= 0; i--) {
-                final Context c = contexts.get(i);
-                mCrashDialogs.add(new AppErrorDialog(c, mService, data));
-            }
-            mService.mUiHandler.post(() -> {
-                List<AppErrorDialog> dialogs;
-                synchronized (mService) {
-                    dialogs = mCrashDialogs;
-                }
-                if (dialogs != null) {
-                    forAllDialogs(dialogs, Dialog::show);
-                }
-            });
-        }
-
-        void showAnrDialogs(AppNotRespondingDialog.Data data) {
-            List<Context> contexts = getDisplayContexts(isSilentAnr() /* lastUsedOnly */);
-            mAnrDialogs = new ArrayList<>();
-            for (int i = contexts.size() - 1; i >= 0; i--) {
-                final Context c = contexts.get(i);
-                mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data));
-            }
-            mService.mUiHandler.post(() -> {
-                List<AppNotRespondingDialog> dialogs;
-                synchronized (mService) {
-                    dialogs = mAnrDialogs;
-                }
-                if (dialogs != null) {
-                    forAllDialogs(dialogs, Dialog::show);
-                }
-            });
-        }
-
-        void showViolationDialogs(AppErrorResult res) {
-            List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */);
-            mViolationDialogs = new ArrayList<>();
-            for (int i = contexts.size() - 1; i >= 0; i--) {
-                final Context c = contexts.get(i);
-                mViolationDialogs.add(
-                        new StrictModeViolationDialog(c, mService, res, ProcessRecord.this));
-            }
-            mService.mUiHandler.post(() -> {
-                List<StrictModeViolationDialog> dialogs;
-                synchronized (mService) {
-                    dialogs = mViolationDialogs;
-                }
-                if (dialogs != null) {
-                    forAllDialogs(dialogs, Dialog::show);
-                }
-            });
-        }
-
-        void showDebugWaitingDialogs() {
-            List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */);
-            final Context c = contexts.get(0);
-            mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, ProcessRecord.this);
-
-            mService.mUiHandler.post(() -> {
-                Dialog dialog;
-                synchronized (mService) {
-                    dialog = mWaitDialog;
-                }
-                if (dialog != null) {
-                    dialog.show();
-                }
-            });
-        }
-
-        /**
-         * Helper function to collect contexts from crashed app located displays
-         *
-         * @param lastUsedOnly Sets to {@code true} to indicate to only get last used context.
-         *                     Sets to {@code false} to collect contexts from crashed app located
-         *                     displays.
-         *
-         * @return display context list
-         */
-        private List<Context> getDisplayContexts(boolean lastUsedOnly) {
-            List<Context> displayContexts = new ArrayList<>();
-            if (!lastUsedOnly) {
-                mWindowProcessController.getDisplayContextsWithErrorDialogs(displayContexts);
-            }
-            // If there is no foreground window display, fallback to last used display.
-            if (displayContexts.isEmpty() || lastUsedOnly) {
-                displayContexts.add(mService.mWmInternal != null
-                        ? mService.mWmInternal.getTopFocusedDisplayUiContext()
-                        : mService.mUiContext);
-            }
-            return displayContexts;
-        }
+        return mService.mProcessList.getLruProcessesLOSP();
     }
 }
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
new file mode 100644
index 0000000..5c3bf60
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * The state info of all services in the process.
+ */
+final class ProcessServiceRecord {
+    /**
+     * Are there any client services with activities?
+     */
+    private boolean mHasClientActivities;
+
+    /**
+     * Running any services that are foreground?
+     */
+    private boolean mHasForegroundServices;
+
+    /**
+     * Service that applied current connectionGroup/Importance.
+     */
+    private ServiceRecord mConnectionService;
+
+    /**
+     * Last group set by a connection.
+     */
+    private int mConnectionGroup;
+
+    /**
+     * Last importance set by a connection.
+     */
+    private int mConnectionImportance;
+
+    /**
+     * Type of foreground service, if there is a foreground service.
+     */
+    private int mFgServiceTypes;
+
+    /**
+     * Last reported foreground service types.
+     */
+    private int mRepFgServiceTypes;
+
+    /**
+     * Bound using BIND_ABOVE_CLIENT, so want to be lower.
+     */
+    private boolean mHasAboveClient;
+
+    /**
+     * Bound using BIND_TREAT_LIKE_ACTIVITY.
+     */
+    private boolean mTreatLikeActivity;
+
+    /**
+     * Do we need to be executing services in the foreground?
+     */
+    private boolean mExecServicesFg;
+
+    /**
+     * App is allowed to manage allowlists such as temporary Power Save mode allowlist.
+     */
+    boolean mAllowlistManager;
+
+    /**
+     * All ServiceRecord running in this process.
+     */
+    private final ArraySet<ServiceRecord> mServices = new ArraySet<>();
+
+    /**
+     * Services that are currently executing code (need to remain foreground).
+     */
+    private final ArraySet<ServiceRecord> mExecutingServices = new ArraySet<>();
+
+    /**
+     * All ConnectionRecord this process holds.
+     */
+    private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>();
+
+    /**
+     * A set of UIDs of all bound clients.
+     */
+    private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
+
+    final ProcessRecord mApp;
+
+    private final ActivityManagerService mService;
+
+    ProcessServiceRecord(ProcessRecord app) {
+        mApp = app;
+        mService = app.mService;
+    }
+
+    void setHasClientActivities(boolean hasClientActivities) {
+        mHasClientActivities = hasClientActivities;
+        mApp.getWindowProcessController().setHasClientActivities(hasClientActivities);
+    }
+
+    boolean hasClientActivities() {
+        return mHasClientActivities;
+    }
+
+    void setHasForegroundServices(boolean hasForegroundServices, int fgServiceTypes) {
+        mHasForegroundServices = hasForegroundServices;
+        mFgServiceTypes = fgServiceTypes;
+        mApp.getWindowProcessController().setHasForegroundServices(hasForegroundServices);
+    }
+
+    boolean hasForegroundServices() {
+        return mHasForegroundServices;
+    }
+
+    int getForegroundServiceTypes() {
+        return mHasForegroundServices ? mFgServiceTypes : 0;
+    }
+
+    int getReportedForegroundServiceTypes() {
+        return mRepFgServiceTypes;
+    }
+
+    void setReportedForegroundServiceTypes(int foregroundServiceTypes) {
+        mRepFgServiceTypes = foregroundServiceTypes;
+    }
+
+    ServiceRecord getConnectionService() {
+        return mConnectionService;
+    }
+
+    void setConnectionService(ServiceRecord connectionService) {
+        mConnectionService = connectionService;
+    }
+
+    int getConnectionGroup() {
+        return mConnectionGroup;
+    }
+
+    void setConnectionGroup(int connectionGroup) {
+        mConnectionGroup = connectionGroup;
+    }
+
+    int getConnectionImportance() {
+        return mConnectionImportance;
+    }
+
+    void setConnectionImportance(int connectionImportance) {
+        mConnectionImportance = connectionImportance;
+    }
+
+    void updateHasAboveClientLocked() {
+        mHasAboveClient = false;
+        for (int i = mConnections.size() - 1; i >= 0; i--) {
+            ConnectionRecord cr = mConnections.valueAt(i);
+            if ((cr.flags & Context.BIND_ABOVE_CLIENT) != 0) {
+                mHasAboveClient = true;
+                break;
+            }
+        }
+    }
+
+    void setHasAboveClient(boolean hasAboveClient) {
+        mHasAboveClient = hasAboveClient;
+    }
+
+    boolean hasAboveClient() {
+        return mHasAboveClient;
+    }
+
+    int modifyRawOomAdj(int adj) {
+        if (mHasAboveClient) {
+            // If this process has bound to any services with BIND_ABOVE_CLIENT,
+            // then we need to drop its adjustment to be lower than the service's
+            // in order to honor the request.  We want to drop it by one adjustment
+            // level...  but there is special meaning applied to various levels so
+            // we will skip some of them.
+            if (adj < ProcessList.FOREGROUND_APP_ADJ) {
+                // System process will not get dropped, ever
+            } else if (adj < ProcessList.VISIBLE_APP_ADJ) {
+                adj = ProcessList.VISIBLE_APP_ADJ;
+            } else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) {
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+            } else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+                adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+            } else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
+                adj = ProcessList.CACHED_APP_MIN_ADJ;
+            } else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
+                adj++;
+            }
+        }
+        return adj;
+    }
+
+    boolean isTreatedLikeActivity() {
+        return mTreatLikeActivity;
+    }
+
+    void setTreatLikeActivity(boolean treatLikeActivity) {
+        mTreatLikeActivity = treatLikeActivity;
+    }
+
+    boolean shouldExecServicesFg() {
+        return mExecServicesFg;
+    }
+
+    void setExecServicesFg(boolean execServicesFg) {
+        mExecServicesFg = execServicesFg;
+    }
+
+    /**
+     * Records a service as running in the process. Note that this method does not actually start
+     * the service, but records the service as started for bookkeeping.
+     *
+     * @return true if the service was added, false otherwise.
+     */
+    boolean startService(ServiceRecord record) {
+        if (record == null) {
+            return false;
+        }
+        boolean added = mServices.add(record);
+        if (added && record.serviceInfo != null) {
+            mApp.getWindowProcessController().onServiceStarted(record.serviceInfo);
+        }
+        return added;
+    }
+
+    /**
+     * Records a service as stopped. Note that like {@link #startService(ServiceRecord)} this method
+     * does not actually stop the service, but records the service as stopped for bookkeeping.
+     *
+     * @return true if the service was removed, false otherwise.
+     */
+    boolean stopService(ServiceRecord record) {
+        return mServices.remove(record);
+    }
+
+    /**
+     * The same as calling {@link #stopService(ServiceRecord)} on all current running services.
+     */
+    void stopAllServices() {
+        mServices.clear();
+    }
+
+    /**
+     * Returns the number of services added with {@link #startService(ServiceRecord)} and not yet
+     * removed by a call to {@link #stopService(ServiceRecord)} or {@link #stopAllServices()}.
+     *
+     * @see #startService(ServiceRecord)
+     * @see #stopService(ServiceRecord)
+     */
+    int numberOfRunningServices() {
+        return mServices.size();
+    }
+
+    /**
+     * Returns the service at the specified {@code index}.
+     *
+     * @see #numberOfRunningServices()
+     */
+    ServiceRecord getRunningServiceAt(int index) {
+        return mServices.valueAt(index);
+    }
+
+    void startExecutingService(ServiceRecord service) {
+        mExecutingServices.add(service);
+    }
+
+    void stopExecutingService(ServiceRecord service) {
+        mExecutingServices.remove(service);
+    }
+
+    void stopAllExecutingServices() {
+        mExecutingServices.clear();
+    }
+
+    ServiceRecord getExecutingServiceAt(int index) {
+        return mExecutingServices.valueAt(index);
+    }
+
+    int numberOfExecutingServices() {
+        return mExecutingServices.size();
+    }
+
+    void addConnection(ConnectionRecord connection) {
+        mConnections.add(connection);
+    }
+
+    void removeConnection(ConnectionRecord connection) {
+        mConnections.remove(connection);
+    }
+
+    void removeAllConnections() {
+        mConnections.clear();
+    }
+
+    ConnectionRecord getConnectionAt(int index) {
+        return mConnections.valueAt(index);
+    }
+
+    int numberOfConnections() {
+        return mConnections.size();
+    }
+
+    void addBoundClientUid(int clientUid) {
+        mBoundClientUids.add(clientUid);
+        mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids);
+    }
+
+    void updateBoundClientUids() {
+        if (mServices.isEmpty()) {
+            clearBoundClientUids();
+            return;
+        }
+        // grab a set of clientUids of all mConnections of all services
+        final ArraySet<Integer> boundClientUids = new ArraySet<>();
+        final int serviceCount = mServices.size();
+        for (int j = 0; j < serviceCount; j++) {
+            final ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns =
+                    mServices.valueAt(j).getConnections();
+            final int size = conns.size();
+            for (int conni = 0; conni < size; conni++) {
+                ArrayList<ConnectionRecord> c = conns.valueAt(conni);
+                for (int i = 0; i < c.size(); i++) {
+                    boundClientUids.add(c.get(i).clientUid);
+                }
+            }
+        }
+        mBoundClientUids = boundClientUids;
+        mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids);
+    }
+
+    void addBoundClientUidsOfNewService(ServiceRecord sr) {
+        if (sr == null) {
+            return;
+        }
+        ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns = sr.getConnections();
+        for (int conni = conns.size() - 1; conni >= 0; conni--) {
+            ArrayList<ConnectionRecord> c = conns.valueAt(conni);
+            for (int i = 0; i < c.size(); i++) {
+                mBoundClientUids.add(c.get(i).clientUid);
+            }
+        }
+        mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids);
+    }
+
+    void clearBoundClientUids() {
+        mBoundClientUids.clear();
+        mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids);
+    }
+
+    @GuardedBy("mService")
+    boolean incServiceCrashCountLocked(long now) {
+        final boolean procIsBoundForeground = mApp.mState.getCurProcState()
+                == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+        boolean tryAgain = false;
+        // Bump up the crash count of any services currently running in the proc.
+        for (int i = numberOfRunningServices() - 1; i >= 0; i--) {
+            // Any services running in the application need to be placed
+            // back in the pending list.
+            ServiceRecord sr = getRunningServiceAt(i);
+            // If the service was restarted a while ago, then reset crash count, else increment it.
+            if (now > sr.restartTime + ActivityManagerConstants.MIN_CRASH_INTERVAL) {
+                sr.crashCount = 1;
+            } else {
+                sr.crashCount++;
+            }
+            // Allow restarting for started or bound foreground services that are crashing.
+            // This includes wallpapers.
+            if (sr.crashCount < mService.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY
+                    && (sr.isForeground || procIsBoundForeground)) {
+                tryAgain = true;
+            }
+        }
+        return tryAgain;
+    }
+
+    @GuardedBy("mService")
+    void onCleanupApplicationRecordLocked() {
+        mTreatLikeActivity = false;
+        mHasAboveClient = false;
+        setHasClientActivities(false);
+    }
+
+    void dump(PrintWriter pw, String prefix, long nowUptime) {
+        if (mHasForegroundServices || mApp.mState.getForcingToImportant() != null) {
+            pw.print(prefix); pw.print("mHasForegroundServices="); pw.print(mHasForegroundServices);
+            pw.print(" forcingToImportant="); pw.println(mApp.mState.getForcingToImportant());
+        }
+        if (mHasClientActivities || mHasAboveClient || mTreatLikeActivity) {
+            pw.print(prefix); pw.print("hasClientActivities="); pw.print(mHasClientActivities);
+            pw.print(" hasAboveClient="); pw.print(mHasAboveClient);
+            pw.print(" treatLikeActivity="); pw.println(mTreatLikeActivity);
+        }
+        if (mConnectionService != null || mConnectionGroup != 0) {
+            pw.print(prefix); pw.print("connectionGroup="); pw.print(mConnectionGroup);
+            pw.print(" Importance="); pw.print(mConnectionImportance);
+            pw.print(" Service="); pw.println(mConnectionService);
+        }
+        if (mAllowlistManager) {
+            pw.print(prefix); pw.print("allowlistManager="); pw.println(mAllowlistManager);
+        }
+        if (mServices.size() > 0) {
+            pw.print(prefix); pw.println("Services:");
+            for (int i = 0, size = mServices.size(); i < size; i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(mServices.valueAt(i));
+            }
+        }
+        if (mExecutingServices.size() > 0) {
+            pw.print(prefix); pw.print("Executing Services (fg=");
+            pw.print(mExecServicesFg); pw.println(")");
+            for (int i = 0, size = mExecutingServices.size(); i < size; i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(mExecutingServices.valueAt(i));
+            }
+        }
+        if (mConnections.size() > 0) {
+            pw.print(prefix); pw.println("mConnections:");
+            for (int i = 0, size = mConnections.size(); i < size; i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(mConnections.valueAt(i));
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
new file mode 100644
index 0000000..e1a153d
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -0,0 +1,1337 @@
+/*
+ * Copyright (C) 2020 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.am;
+
+import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
+import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.NFC_UID;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SHELL_UID;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_COMPANION_APP;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_PROC_STATE;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_SYSTEM_UID;
+import static com.android.server.am.ActiveServices.FGS_FEATURE_DENIED;
+import static com.android.server.am.ActiveServices.fgsCodeToString;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ProcessRecord.TAG;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.CompositeRWLock;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
+
+
+import java.io.PrintWriter;
+
+/**
+ * The state info of the process, including proc state, oom adj score, et al.
+ */
+final class ProcessStateRecord {
+    private final ProcessRecord mApp;
+    private final ActivityManagerService mService;
+    private final ActivityManagerGlobalLock mProcLock;
+
+    /**
+     * Maximum OOM adjustment for this process.
+     */
+    @GuardedBy("mService")
+    private int mMaxAdj = ProcessList.UNKNOWN_ADJ;
+
+    /**
+     *  Current OOM unlimited adjustment for this process.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurRawAdj = ProcessList.INVALID_ADJ;
+
+    /**
+     * Last set OOM unlimited adjustment for this process.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetRawAdj = ProcessList.INVALID_ADJ;
+
+    /**
+     * Current OOM adjustment for this process.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurAdj = ProcessList.INVALID_ADJ;
+
+    /**
+     * Last set OOM adjustment for this process.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetAdj = ProcessList.INVALID_ADJ;
+
+    /**
+     * The last adjustment that was verified as actually being set.
+     */
+    @GuardedBy("mService")
+    private int mVerifiedAdj = ProcessList.INVALID_ADJ;
+
+    /**
+     * Current capability flags of this process.
+     * For example, PROCESS_CAPABILITY_FOREGROUND_LOCATION is one capability.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurCapability = PROCESS_CAPABILITY_NONE;
+
+    /**
+     * Last set capability flags.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetCapability = PROCESS_CAPABILITY_NONE;
+
+    /**
+     * Currently desired scheduling class.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+
+    /**
+     * Last set to background scheduling class.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+
+    /**
+     * Currently computed process state.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurProcState = PROCESS_STATE_NONEXISTENT;
+
+    /**
+     * Last reported process state.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mRepProcState = PROCESS_STATE_NONEXISTENT;
+
+    /**
+     * Temp state during computation.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurRawProcState = PROCESS_STATE_NONEXISTENT;
+
+    /**
+     * Last set process state in process tracker.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetProcState = PROCESS_STATE_NONEXISTENT;
+
+    /**
+     * Last time mSetProcState changed.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private long mLastStateTime;
+
+    /**
+     * Previous priority value if we're switching to non-SCHED_OTHER.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSavedPriority;
+
+    /**
+     * Process currently is on the service B list.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mServiceB;
+
+    /**
+     * We are forcing to service B list due to its RAM use.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mServiceHighRam;
+
+    /**
+     * Has this process not been in a cached state since last idle?
+     */
+    @GuardedBy("mProcLock")
+    private boolean mNotCachedSinceIdle;
+
+    /**
+     * Are there any started services running in this process?
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mHasStartedServices;
+
+    /**
+     * Running any activities that are foreground?
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mHasForegroundActivities;
+
+    /**
+     * Last reported foreground activities.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mRepForegroundActivities;
+
+    /**
+     * Has UI been shown in this process since it was started?
+     */
+    @GuardedBy("mService")
+    private boolean mHasShownUi;
+
+    /**
+     * Is this process currently showing a non-activity UI that the user
+     * is interacting with? E.g. The status bar when it is expanded, but
+     * not when it is minimized. When true the
+     * process will be set to use the ProcessList#SCHED_GROUP_TOP_APP
+     * scheduling group to boost performance.
+     */
+    @GuardedBy("mService")
+    private boolean mHasTopUi;
+
+    /**
+     * Is the process currently showing a non-activity UI that
+     * overlays on-top of activity UIs on screen. E.g. display a window
+     * of type android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
+     * When true the process will oom adj score will be set to
+     * ProcessList#PERCEPTIBLE_APP_ADJ at minimum to reduce the chance
+     * of the process getting killed.
+     */
+    @GuardedBy("mService")
+    private boolean mHasOverlayUi;
+
+    /**
+     * Is the process currently running a RemoteAnimation? When true
+     * the process will be set to use the
+     * ProcessList#SCHED_GROUP_TOP_APP scheduling group to boost
+     * performance, as well as oom adj score will be set to
+     * ProcessList#VISIBLE_APP_ADJ at minimum to reduce the chance
+     * of the process getting killed.
+     */
+    @GuardedBy("mService")
+    private boolean mRunningRemoteAnimation;
+
+    /**
+     * Keep track of whether we changed 'mSetAdj'.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mProcStateChanged;
+
+    /**
+     * Whether we have told usage stats about it being an interaction.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mReportedInteraction;
+
+    /**
+     * The time we sent the last interaction event.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private long mInteractionEventTime;
+
+    /**
+     * When we became foreground for interaction purposes.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private long mFgInteractionTime;
+
+    /**
+     * Token that is forcing this process to be important.
+     */
+    @GuardedBy("mService")
+    private Object mForcingToImportant;
+
+    /**
+     * Sequence id for identifying oom_adj assignment cycles.
+     */
+    @GuardedBy("mService")
+    private int mAdjSeq;
+
+    /**
+     * Sequence id for identifying oom_adj assignment cycles.
+     */
+    @GuardedBy("mService")
+    private int mCompletedAdjSeq;
+
+    /**
+     * Whether this app has encountered a cycle in the most recent update.
+     */
+    @GuardedBy("mService")
+    private boolean mContainsCycle;
+
+    /**
+     * When (uptime) the process last became unimportant.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private long mWhenUnimportant;
+
+    /**
+     * The last time the process was in the TOP state or greater.
+     */
+    @GuardedBy("mService")
+    private long mLastTopTime;
+
+    /**
+     * Is this an empty background process?
+     */
+    @GuardedBy("mService")
+    private boolean mEmpty;
+
+    /**
+     * Is this a cached process?
+     */
+    @GuardedBy("mService")
+    private boolean mCached;
+
+    /**
+     * This is a system process, but not currently showing UI.
+     */
+    @GuardedBy("mService")
+    private boolean mSystemNoUi;
+
+    /**
+     * If the proc state is PROCESS_STATE_BOUND_FOREGROUND_SERVICE or above, it can start FGS.
+     * It must obtain the proc state from a persistent/top process or FGS, not transitive.
+     */
+    @GuardedBy("mService")
+    private int mAllowStartFgsState = PROCESS_STATE_NONEXISTENT;
+
+    @GuardedBy("mService")
+    private final ArraySet<Binder> mBackgroundFgsStartTokens = new ArraySet<>();
+
+    /**
+     * Does the process has permission to start FGS from background.
+     */
+    @GuardedBy("mService")
+    private @ActiveServices.FgsFeatureRetCode int mAllowStartFgsByPermission;
+
+    /**
+     * Can this process start FGS from background?
+     * If this process has the ability to start FGS from background, this ability can be passed to
+     * another process through service binding.
+     */
+    @GuardedBy("mService")
+    private @ActiveServices.FgsFeatureRetCode int mAllowStartFgs;
+
+    /**
+     * Debugging: primary thing impacting oom_adj.
+     */
+    @GuardedBy("mService")
+    private String mAdjType;
+
+    /**
+     * Debugging: adj code to report to app.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mAdjTypeCode;
+
+    /**
+     * Debugging: option dependent object.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private Object mAdjSource;
+
+    /**
+     * Debugging: proc state of mAdjSource's process.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mAdjSourceProcState;
+
+    /**
+     * Debugging: target component impacting oom_adj.
+     */
+    @CompositeRWLock({"mService", "mProcLock"})
+    private Object mAdjTarget;
+
+    /**
+     * Approximates the usage count of the app, used for cache re-ranking by CacheOomRanker.
+     *
+     * Counts the number of times the process is re-added to the cache (i.e. setCached(false);
+     * setCached(true)). This over counts, as setCached is sometimes reset while remaining in the
+     * cache. However, this happens uniformly across processes, so ranking is not affected.
+     */
+    @GuardedBy("mService")
+    private int mCacheOomRankerUseCount;
+
+    /**
+     * Whether or not this process is reachable from given process.
+     */
+    @GuardedBy("mService")
+    private boolean mReachable;
+
+    // Below are the cached task info for OomAdjuster only
+    private static final int VALUE_INVALID = -1;
+    private static final int VALUE_FALSE = 0;
+    private static final int VALUE_TRUE = 1;
+
+    @GuardedBy("mService")
+    private int mCachedHasActivities = VALUE_INVALID;
+    @GuardedBy("mService")
+    private int mCachedIsHeavyWeight = VALUE_INVALID;
+    @GuardedBy("mService")
+    private int mCachedHasVisibleActivities = VALUE_INVALID;
+    @GuardedBy("mService")
+    private int mCachedIsHomeProcess = VALUE_INVALID;
+    @GuardedBy("mService")
+    private int mCachedIsPreviousProcess = VALUE_INVALID;
+    @GuardedBy("mService")
+    private int mCachedHasRecentTasks = VALUE_INVALID;
+    @GuardedBy("mService")
+    private int mCachedIsReceivingBroadcast = VALUE_INVALID;
+
+    @GuardedBy("mService")
+    private int mCachedAdj = ProcessList.INVALID_ADJ;
+    @GuardedBy("mService")
+    private boolean mCachedForegroundActivities = false;
+    @GuardedBy("mService")
+    private int mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+    @GuardedBy("mService")
+    private int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+
+    ProcessStateRecord(ProcessRecord app) {
+        mApp = app;
+        mService = app.mService;
+        mProcLock = mService.mProcLock;
+        setAllowStartFgsByPermission();
+    }
+
+    void init(long now) {
+        mLastStateTime = now;
+    }
+
+    @GuardedBy("mService")
+    void setMaxAdj(int maxAdj) {
+        mMaxAdj = maxAdj;
+    }
+
+    @GuardedBy("mService")
+    int getMaxAdj() {
+        return mMaxAdj;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurRawAdj(int curRawAdj) {
+        mCurRawAdj = curRawAdj;
+        mApp.getWindowProcessController().setPerceptible(
+                curRawAdj <= ProcessList.PERCEPTIBLE_APP_ADJ);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurRawAdj() {
+        return mCurRawAdj;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetRawAdj(int setRawAdj) {
+        mSetRawAdj = setRawAdj;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetRawAdj() {
+        return mSetRawAdj;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurAdj(int curAdj) {
+        mCurAdj = curAdj;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurAdj() {
+        return mCurAdj;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetAdj(int setAdj) {
+        mSetAdj = setAdj;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetAdj() {
+        return mSetAdj;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetAdjWithServices() {
+        if (mSetAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+            if (mHasStartedServices) {
+                return ProcessList.SERVICE_B_ADJ;
+            }
+        }
+        return mSetAdj;
+    }
+
+    @GuardedBy("mService")
+    void setVerifiedAdj(int verifiedAdj) {
+        mVerifiedAdj = verifiedAdj;
+    }
+
+    @GuardedBy("mService")
+    int getVerifiedAdj() {
+        return mVerifiedAdj;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurCapability(int curCapability) {
+        mCurCapability = curCapability;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurCapability() {
+        return mCurCapability;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetCapability(int setCapability) {
+        mSetCapability = setCapability;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetCapability() {
+        return mSetCapability;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurrentSchedulingGroup(int curSchedGroup) {
+        mCurSchedGroup = curSchedGroup;
+        mApp.getWindowProcessController().setCurrentSchedulingGroup(curSchedGroup);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurrentSchedulingGroup() {
+        return mCurSchedGroup;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetSchedGroup(int setSchedGroup) {
+        mSetSchedGroup = setSchedGroup;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetSchedGroup() {
+        return mSetSchedGroup;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurProcState(int curProcState) {
+        mCurProcState = curProcState;
+        mApp.getWindowProcessController().setCurrentProcState(mCurProcState);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurProcState() {
+        return mCurProcState;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurRawProcState(int curRawProcState) {
+        mCurRawProcState = curRawProcState;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurRawProcState() {
+        return mCurRawProcState;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setReportedProcState(int repProcState) {
+        mRepProcState = repProcState;
+        mApp.getPkgList().forEachPackage((pkgName, holder) ->
+                FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+                    mApp.uid, mApp.processName, pkgName,
+                    ActivityManager.processStateAmToProto(mRepProcState),
+                    holder.appVersion)
+        );
+        mApp.getWindowProcessController().setReportedProcState(repProcState);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getReportedProcState() {
+        return mRepProcState;
+    }
+
+    @GuardedBy("mService")
+    void forceProcessStateUpTo(int newState) {
+        if (mRepProcState > newState) {
+            synchronized (mProcLock) {
+                mRepProcState = newState;
+                setCurProcState(newState);
+                setCurRawProcState(newState);
+                mApp.getPkgList().forEachPackage((pkgName, holder) ->
+                        FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
+                            mApp.uid, mApp.processName, pkgName,
+                            ActivityManager.processStateAmToProto(mRepProcState),
+                            holder.appVersion)
+                );
+            }
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetProcState(int setProcState) {
+        mSetProcState = setProcState;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetProcState() {
+        return mSetProcState;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setLastStateTime(long lastStateTime) {
+        mLastStateTime = lastStateTime;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getLastStateTime() {
+        return mLastStateTime;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSavedPriority(int savedPriority) {
+        mSavedPriority = savedPriority;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSavedPriority() {
+        return mSavedPriority;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setServiceB(boolean serviceb) {
+        mServiceB = serviceb;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isServiceB() {
+        return mServiceB;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setServiceHighRam(boolean serviceHighRam) {
+        mServiceHighRam = serviceHighRam;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isServiceHighRam() {
+        return mServiceHighRam;
+    }
+
+    @GuardedBy("mProcLock")
+    void setNotCachedSinceIdle(boolean notCachedSinceIdle) {
+        mNotCachedSinceIdle = notCachedSinceIdle;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean isNotCachedSinceIdle() {
+        return mNotCachedSinceIdle;
+    }
+
+    @GuardedBy("mProcLock")
+    void setHasStartedServices(boolean hasStartedServices) {
+        mHasStartedServices = hasStartedServices;
+    }
+
+    @GuardedBy("mProcLock")
+    boolean hasStartedServices() {
+        return mHasStartedServices;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setHasForegroundActivities(boolean hasForegroundActivities) {
+        mHasForegroundActivities = hasForegroundActivities;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean hasForegroundActivities() {
+        return mHasForegroundActivities;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setRepForegroundActivities(boolean repForegroundActivities) {
+        mRepForegroundActivities = repForegroundActivities;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean hasRepForegroundActivities() {
+        return mRepForegroundActivities;
+    }
+
+    @GuardedBy("mService")
+    void setHasShownUi(boolean hasShownUi) {
+        mHasShownUi = hasShownUi;
+    }
+
+    @GuardedBy("mService")
+    boolean hasShownUi() {
+        return mHasShownUi;
+    }
+
+    @GuardedBy("mService")
+    void setHasTopUi(boolean hasTopUi) {
+        mHasTopUi = hasTopUi;
+        mApp.getWindowProcessController().setHasTopUi(hasTopUi);
+    }
+
+    @GuardedBy("mService")
+    boolean hasTopUi() {
+        return mHasTopUi;
+    }
+
+    @GuardedBy("mService")
+    void setHasOverlayUi(boolean hasOverlayUi) {
+        mHasOverlayUi = hasOverlayUi;
+        mApp.getWindowProcessController().setHasOverlayUi(hasOverlayUi);
+    }
+
+    @GuardedBy("mService")
+    boolean hasOverlayUi() {
+        return mHasOverlayUi;
+    }
+
+    @GuardedBy("mService")
+    boolean isRunningRemoteAnimation() {
+        return mRunningRemoteAnimation;
+    }
+
+    @GuardedBy("mService")
+    void setRunningRemoteAnimation(boolean runningRemoteAnimation) {
+        if (mRunningRemoteAnimation == runningRemoteAnimation) {
+            return;
+        }
+        mRunningRemoteAnimation = runningRemoteAnimation;
+        if (DEBUG_OOM_ADJ) {
+            Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
+                    + " for pid=" + mApp.getPid());
+        }
+        mService.updateOomAdjLocked(mApp, true, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setProcStateChanged(boolean procStateChanged) {
+        mProcStateChanged = procStateChanged;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean hasProcStateChanged() {
+        return mProcStateChanged;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setReportedInteraction(boolean reportedInteraction) {
+        mReportedInteraction = reportedInteraction;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean hasReportedInteraction() {
+        return mReportedInteraction;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setInteractionEventTime(long interactionEventTime) {
+        mInteractionEventTime = interactionEventTime;
+        mApp.getWindowProcessController().setInteractionEventTime(interactionEventTime);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getInteractionEventTime() {
+        return mInteractionEventTime;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setFgInteractionTime(long fgInteractionTime) {
+        mFgInteractionTime = fgInteractionTime;
+        mApp.getWindowProcessController().setFgInteractionTime(fgInteractionTime);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getFgInteractionTime() {
+        return mFgInteractionTime;
+    }
+
+    @GuardedBy("mService")
+    void setForcingToImportant(Object forcingToImportant) {
+        mForcingToImportant = forcingToImportant;
+    }
+
+    @GuardedBy("mService")
+    Object getForcingToImportant() {
+        return mForcingToImportant;
+    }
+
+    @GuardedBy("mService")
+    void setAdjSeq(int adjSeq) {
+        mAdjSeq = adjSeq;
+    }
+
+    @GuardedBy("mService")
+    void decAdjSeq() {
+        mAdjSeq--;
+    }
+
+    @GuardedBy("mService")
+    int getAdjSeq() {
+        return mAdjSeq;
+    }
+
+    @GuardedBy("mService")
+    void setCompletedAdjSeq(int completedAdjSeq) {
+        mCompletedAdjSeq = completedAdjSeq;
+    }
+
+    @GuardedBy("mService")
+    void decCompletedAdjSeq() {
+        mCompletedAdjSeq--;
+    }
+
+    @GuardedBy("mService")
+    int getCompletedAdjSeq() {
+        return mCompletedAdjSeq;
+    }
+
+    @GuardedBy("mService")
+    void setContainsCycle(boolean containsCycle) {
+        mContainsCycle = containsCycle;
+    }
+
+    @GuardedBy("mService")
+    boolean containsCycle() {
+        return mContainsCycle;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setWhenUnimportant(long whenUnimportant) {
+        mWhenUnimportant = whenUnimportant;
+        mApp.getWindowProcessController().setWhenUnimportant(whenUnimportant);
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getWhenUnimportant() {
+        return mWhenUnimportant;
+    }
+
+    @GuardedBy("mService")
+    void setLastTopTime(long lastTopTime) {
+        mLastTopTime = lastTopTime;
+    }
+
+    @GuardedBy("mService")
+    long getLastTopTime() {
+        return mLastTopTime;
+    }
+
+    @GuardedBy("mService")
+    void setEmpty(boolean empty) {
+        mEmpty = empty;
+    }
+
+    @GuardedBy("mService")
+    boolean isEmpty() {
+        return mEmpty;
+    }
+
+    @GuardedBy("mService")
+    void setCached(boolean cached) {
+        if (mCached != cached) {
+            mCached = cached;
+            if (cached) {
+                ++mCacheOomRankerUseCount;
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    boolean isCached() {
+        return mCached;
+    }
+
+    @GuardedBy("mService")
+    int getCacheOomRankerUseCount() {
+        return mCacheOomRankerUseCount;
+    }
+
+    @GuardedBy("mService")
+    void setSystemNoUi(boolean systemNoUi) {
+        mSystemNoUi = systemNoUi;
+    }
+
+    @GuardedBy("mService")
+    boolean isSystemNoUi() {
+        return mSystemNoUi;
+    }
+
+    @GuardedBy("mService")
+    void setAdjType(String adjType) {
+        mAdjType = adjType;
+    }
+
+    @GuardedBy("mService")
+    String getAdjType() {
+        return mAdjType;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setAdjTypeCode(int adjTypeCode) {
+        mAdjTypeCode = adjTypeCode;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getAdjTypeCode() {
+        return mAdjTypeCode;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setAdjSource(Object adjSource) {
+        mAdjSource = adjSource;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    Object getAdjSource() {
+        return mAdjSource;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setAdjSourceProcState(int adjSourceProcState) {
+        mAdjSourceProcState = adjSourceProcState;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getAdjSourceProcState() {
+        return mAdjSourceProcState;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setAdjTarget(Object adjTarget) {
+        mAdjTarget = adjTarget;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    Object getAdjTarget() {
+        return mAdjTarget;
+    }
+
+    @GuardedBy("mService")
+    boolean isReachable() {
+        return mReachable;
+    }
+
+    @GuardedBy("mService")
+    void setReachable(boolean reachable) {
+        mReachable = reachable;
+    }
+
+    @GuardedBy("mService")
+    void resetCachedInfo() {
+        mCachedHasActivities = VALUE_INVALID;
+        mCachedIsHeavyWeight = VALUE_INVALID;
+        mCachedHasVisibleActivities = VALUE_INVALID;
+        mCachedIsHomeProcess = VALUE_INVALID;
+        mCachedIsPreviousProcess = VALUE_INVALID;
+        mCachedHasRecentTasks = VALUE_INVALID;
+        mCachedIsReceivingBroadcast = VALUE_INVALID;
+        mCachedAdj = ProcessList.INVALID_ADJ;
+        mCachedForegroundActivities = false;
+        mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+        mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+    }
+
+    @GuardedBy("mService")
+    boolean getCachedHasActivities() {
+        if (mCachedHasActivities == VALUE_INVALID) {
+            mCachedHasActivities = mApp.getWindowProcessController().hasActivities() ? VALUE_TRUE
+                    : VALUE_FALSE;
+        }
+        return mCachedHasActivities == VALUE_TRUE;
+    }
+
+    @GuardedBy("mService")
+    boolean getCachedIsHeavyWeight() {
+        if (mCachedIsHeavyWeight == VALUE_INVALID) {
+            mCachedIsHeavyWeight = mApp.getWindowProcessController().isHeavyWeightProcess()
+                    ? VALUE_TRUE : VALUE_FALSE;
+        }
+        return mCachedIsHeavyWeight == VALUE_TRUE;
+    }
+
+    @GuardedBy("mService")
+    boolean getCachedHasVisibleActivities() {
+        if (mCachedHasVisibleActivities == VALUE_INVALID) {
+            mCachedHasVisibleActivities = mApp.getWindowProcessController().hasVisibleActivities()
+                    ? VALUE_TRUE : VALUE_FALSE;
+        }
+        return mCachedHasVisibleActivities == VALUE_TRUE;
+    }
+
+    @GuardedBy("mService")
+    boolean getCachedIsHomeProcess() {
+        if (mCachedIsHomeProcess == VALUE_INVALID) {
+            if (mApp.getWindowProcessController().isHomeProcess()) {
+                mCachedIsHomeProcess = VALUE_TRUE;
+                mService.mAppProfiler.mHasHomeProcess = true;
+            } else {
+                mCachedIsHomeProcess = VALUE_FALSE;
+            }
+        }
+        return mCachedIsHomeProcess == VALUE_TRUE;
+    }
+
+    @GuardedBy("mService")
+    boolean getCachedIsPreviousProcess() {
+        if (mCachedIsPreviousProcess == VALUE_INVALID) {
+            if (mApp.getWindowProcessController().isPreviousProcess()) {
+                mCachedIsPreviousProcess = VALUE_TRUE;
+                mService.mAppProfiler.mHasPreviousProcess = true;
+            } else {
+                mCachedIsPreviousProcess = VALUE_FALSE;
+            }
+        }
+        return mCachedIsPreviousProcess == VALUE_TRUE;
+    }
+
+    @GuardedBy("mService")
+    boolean getCachedHasRecentTasks() {
+        if (mCachedHasRecentTasks == VALUE_INVALID) {
+            mCachedHasRecentTasks = mApp.getWindowProcessController().hasRecentTasks()
+                    ? VALUE_TRUE : VALUE_FALSE;
+        }
+        return mCachedHasRecentTasks == VALUE_TRUE;
+    }
+
+    @GuardedBy("mService")
+    boolean getCachedIsReceivingBroadcast(ArraySet<BroadcastQueue> tmpQueue) {
+        if (mCachedIsReceivingBroadcast == VALUE_INVALID) {
+            tmpQueue.clear();
+            mCachedIsReceivingBroadcast = mService.isReceivingBroadcastLocked(mApp, tmpQueue)
+                    ? VALUE_TRUE : VALUE_FALSE;
+            if (mCachedIsReceivingBroadcast == VALUE_TRUE) {
+                mCachedSchedGroup = tmpQueue.contains(mService.mFgBroadcastQueue)
+                        ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+            }
+        }
+        return mCachedIsReceivingBroadcast == VALUE_TRUE;
+    }
+
+    @GuardedBy("mService")
+    void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
+            int adj, boolean foregroundActivities, int procState, int schedGroup, int appUid,
+            int logUid, int processCurTop) {
+        if (mCachedAdj != ProcessList.INVALID_ADJ) {
+            return;
+        }
+        callback.initialize(mApp, adj, foregroundActivities, procState, schedGroup, appUid, logUid,
+                processCurTop);
+        final int minLayer = Math.min(ProcessList.VISIBLE_APP_LAYER_MAX,
+                mApp.getWindowProcessController().computeOomAdjFromActivities(callback));
+
+        mCachedAdj = callback.adj;
+        mCachedForegroundActivities = callback.foregroundActivities;
+        mCachedProcState = callback.procState;
+        mCachedSchedGroup = callback.schedGroup;
+
+        if (mCachedAdj == ProcessList.VISIBLE_APP_ADJ) {
+            mCachedAdj += minLayer;
+        }
+    }
+
+    @GuardedBy("mService")
+    int getCachedAdj() {
+        return mCachedAdj;
+    }
+
+    @GuardedBy("mService")
+    boolean getCachedForegroundActivities() {
+        return mCachedForegroundActivities;
+    }
+
+    @GuardedBy("mService")
+    int getCachedProcState() {
+        return mCachedProcState;
+    }
+
+    @GuardedBy("mService")
+    int getCachedSchedGroup() {
+        return mCachedSchedGroup;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    public String makeAdjReason() {
+        if (mAdjSource != null || mAdjTarget != null) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append(' ');
+            if (mAdjTarget instanceof ComponentName) {
+                sb.append(((ComponentName) mAdjTarget).flattenToShortString());
+            } else if (mAdjTarget != null) {
+                sb.append(mAdjTarget.toString());
+            } else {
+                sb.append("{null}");
+            }
+            sb.append("<=");
+            if (mAdjSource instanceof ProcessRecord) {
+                sb.append("Proc{");
+                sb.append(((ProcessRecord) mAdjSource).toShortString());
+                sb.append("}");
+            } else if (mAdjSource != null) {
+                sb.append(mAdjSource.toString());
+            } else {
+                sb.append("{null}");
+            }
+            return sb.toString();
+        }
+        return null;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void onCleanupApplicationRecordLSP() {
+        setHasForegroundActivities(false);
+        mHasShownUi = false;
+        mForcingToImportant = null;
+        mCurRawAdj = mSetRawAdj = mCurAdj = mSetAdj = mVerifiedAdj = ProcessList.INVALID_ADJ;
+        mCurCapability = mSetCapability = PROCESS_CAPABILITY_NONE;
+        mCurSchedGroup = mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+        mCurProcState = mRepProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
+                PROCESS_STATE_NONEXISTENT;
+    }
+
+    @GuardedBy("mService")
+    void addAllowBackgroundFgsStartsToken(Binder entity) {
+        mBackgroundFgsStartTokens.add(entity);
+    }
+
+    @GuardedBy("mService")
+    void removeAllowBackgroundFgsStartsToken(Binder entity) {
+        mBackgroundFgsStartTokens.remove(entity);
+    }
+
+    @GuardedBy("mService")
+    boolean areBackgroundFgsStartsAllowedByToken() {
+        return !mBackgroundFgsStartTokens.isEmpty();
+    }
+
+    @GuardedBy("mService")
+    void resetAllowStartFgs() {
+        mAllowStartFgsState = PROCESS_STATE_NONEXISTENT;
+        mAllowStartFgs = mAllowStartFgsByPermission;
+    }
+
+    @GuardedBy("mService")
+    void bumpAllowStartFgsState(int newProcState) {
+        if (newProcState < mAllowStartFgsState) {
+            mAllowStartFgsState = newProcState;
+        }
+    }
+
+    @GuardedBy("mService")
+    void setAllowStartFgsState(int allowStartFgsState) {
+        mAllowStartFgsState = allowStartFgsState;
+    }
+
+    @GuardedBy("mService")
+    boolean isAllowedStartFgsState() {
+        return mAllowStartFgsState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+    }
+
+    @GuardedBy("mService")
+    void setAllowStartFgsByPermission() {
+        int ret = FGS_FEATURE_DENIED;
+        if (ret == FGS_FEATURE_DENIED) {
+            boolean isSystem = false;
+            final int uid = UserHandle.getAppId(mApp.info.uid);
+            switch (uid) {
+                case ROOT_UID:
+                case SYSTEM_UID:
+                case NFC_UID:
+                case SHELL_UID:
+                    isSystem = true;
+                    break;
+                default:
+                    isSystem = false;
+                    break;
+            }
+
+            if (isSystem) {
+                ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_UID;
+            }
+        }
+
+        if (ret == FGS_FEATURE_DENIED) {
+            if (ActivityManager.checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND,
+                    mApp.info.uid, -1, true) == PERMISSION_GRANTED) {
+                ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION;
+            } else if (ActivityManager.checkComponentPermission(
+                    START_FOREGROUND_SERVICES_FROM_BACKGROUND,
+                    mApp.info.uid, -1, true) == PERMISSION_GRANTED) {
+                ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION;
+            } else if (ActivityManager.checkComponentPermission(SYSTEM_ALERT_WINDOW,
+                    mApp.info.uid, -1, true) == PERMISSION_GRANTED) {
+                ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION;
+            }
+        }
+        mAllowStartFgs = mAllowStartFgsByPermission = ret;
+    }
+
+    @GuardedBy("mService")
+    void setAllowStartFgs() {
+        if (mAllowStartFgs != FGS_FEATURE_DENIED) {
+            return;
+        }
+        if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+            if (isAllowedStartFgsState()) {
+                mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_PROC_STATE;
+            }
+        }
+
+        if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+            // Is the calling UID a device owner app?
+            if (mService.mInternal != null) {
+                if (mService.mInternal.isDeviceOwner(mApp.info.uid)) {
+                    mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER;
+                }
+            }
+        }
+
+        if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+            if (mService.mInternal != null) {
+                final boolean isCompanionApp = mService.mInternal.isAssociatedCompanionApp(
+                        UserHandle.getUserId(mApp.info.uid), mApp.info.uid);
+                if (isCompanionApp) {
+                    mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_COMPANION_APP;
+                }
+            }
+        }
+
+        if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+            // Is the calling UID a profile owner app?
+            if (mService.mInternal != null) {
+                if (mService.mInternal.isProfileOwner(mApp.info.uid)) {
+                    mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER;
+                }
+            }
+        }
+
+        if (mAllowStartFgs == FGS_FEATURE_DENIED) {
+            // uid is on DeviceIdleController's user/system allowlist
+            // or AMS's FgsStartTempAllowList.
+            if (mService.isAllowlistedForFgsStartLOSP(mApp.info.uid)) {
+                mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST;
+            }
+        }
+    }
+
+    @GuardedBy("mService")
+    void setAllowStartFgs(@ActiveServices.FgsFeatureRetCode int allowStartFgs) {
+        mAllowStartFgs = allowStartFgs;
+    }
+
+    @GuardedBy("mService")
+    @ActiveServices.FgsFeatureRetCode int getAllowedStartFgs() {
+        return mAllowStartFgs;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void dump(PrintWriter pw, String prefix, long nowUptime) {
+        if (mReportedInteraction || mFgInteractionTime != 0) {
+            pw.print(prefix); pw.print("reportedInteraction=");
+            pw.print(mReportedInteraction);
+            if (mInteractionEventTime != 0) {
+                pw.print(" time=");
+                TimeUtils.formatDuration(mInteractionEventTime, SystemClock.elapsedRealtime(), pw);
+            }
+            if (mFgInteractionTime != 0) {
+                pw.print(" fgInteractionTime=");
+                TimeUtils.formatDuration(mFgInteractionTime, SystemClock.elapsedRealtime(), pw);
+            }
+            pw.println();
+        }
+        pw.print(prefix); pw.print("adjSeq="); pw.print(mAdjSeq);
+        pw.print(" lruSeq="); pw.println(mApp.getLruSeq());
+        pw.print(prefix); pw.print("oom adj: max="); pw.print(mMaxAdj);
+        pw.print(" curRaw="); pw.print(mCurRawAdj);
+        pw.print(" setRaw="); pw.print(mSetRawAdj);
+        pw.print(" cur="); pw.print(mCurAdj);
+        pw.print(" set="); pw.println(mSetAdj);
+        pw.print(prefix); pw.print("mCurSchedGroup="); pw.print(mCurSchedGroup);
+        pw.print(" setSchedGroup="); pw.print(mSetSchedGroup);
+        pw.print(" systemNoUi="); pw.print(mSystemNoUi);
+        pw.print(prefix); pw.print("curProcState="); pw.print(getCurProcState());
+        pw.print(" mRepProcState="); pw.print(mRepProcState);
+        pw.print(" setProcState="); pw.print(mSetProcState);
+        pw.print(" lastStateTime=");
+        TimeUtils.formatDuration(getLastStateTime(), nowUptime, pw);
+        pw.println();
+        pw.print(prefix); pw.print("curCapability=");
+        ActivityManager.printCapabilitiesFull(pw, mCurCapability);
+        pw.print(" setCapability=");
+        ActivityManager.printCapabilitiesFull(pw, mSetCapability);
+        pw.println();
+        pw.print(prefix); pw.print("allowStartFgsState=");
+        pw.println(mAllowStartFgsState);
+        if (mAllowStartFgs != FGS_FEATURE_DENIED) {
+            pw.print(prefix); pw.print("allowStartFgs=");
+            pw.println(fgsCodeToString(mAllowStartFgs));
+        }
+        if (mHasShownUi || mApp.mProfile.hasPendingUiClean()) {
+            pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi);
+            pw.print(" pendingUiClean="); pw.print(mApp.mProfile.hasPendingUiClean());
+        }
+        pw.print(prefix); pw.print("cached="); pw.print(mCached);
+        pw.print(" empty="); pw.println(mEmpty);
+        if (mServiceB) {
+            pw.print(prefix); pw.print("serviceb="); pw.print(mServiceB);
+            pw.print(" serviceHighRam="); pw.println(mServiceHighRam);
+        }
+        if (mNotCachedSinceIdle) {
+            pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(mNotCachedSinceIdle);
+            pw.print(" initialIdlePss="); pw.println(mApp.mProfile.getInitialIdlePss());
+        }
+        if (hasTopUi() || hasOverlayUi() || mRunningRemoteAnimation) {
+            pw.print(prefix); pw.print("hasTopUi="); pw.print(hasTopUi());
+            pw.print(" hasOverlayUi="); pw.print(hasOverlayUi());
+            pw.print(" runningRemoteAnimation="); pw.println(mRunningRemoteAnimation);
+        }
+        if (mHasForegroundActivities || mRepForegroundActivities) {
+            pw.print(prefix);
+            pw.print(" foregroundActivities="); pw.print(mHasForegroundActivities);
+            pw.print(" (rep="); pw.print(mRepForegroundActivities); pw.println(")");
+        }
+        if (mSetProcState > ActivityManager.PROCESS_STATE_SERVICE) {
+            pw.print(prefix);
+            pw.print(" whenUnimportant=");
+            TimeUtils.formatDuration(mWhenUnimportant - nowUptime, pw);
+            pw.println();
+        }
+        if (mLastTopTime > 0) {
+            pw.print(prefix); pw.print("lastTopTime=");
+            TimeUtils.formatDuration(mLastTopTime, nowUptime, pw);
+            pw.println();
+        }
+        if (mHasStartedServices) {
+            pw.print(prefix); pw.print("hasStartedServices="); pw.println(mHasStartedServices);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index c10a078..34a0c1b 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -313,8 +313,8 @@
 
     private void scheduleRequestPssAllProcs(boolean always, boolean memLowered) {
         mAm.mHandler.post(() -> {
-            synchronized (mAm) {
-                mAm.mAppProfiler.requestPssAllProcsLocked(
+            synchronized (mAm.mProcLock) {
+                mAm.mAppProfiler.requestPssAllProcsLPr(
                         SystemClock.uptimeMillis(), always, memLowered);
             }
         });
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
index 29c1657..072eba5 100644
--- a/services/core/java/com/android/server/am/ProviderMap.java
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.app.IApplicationThread;
 import android.content.ComponentName;
 import android.content.ComponentName.WithComponentName;
 import android.os.Binder;
@@ -23,6 +24,7 @@
 import android.os.UserHandle;
 import android.util.Slog;
 import android.util.SparseArray;
+
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
@@ -372,10 +374,11 @@
      */
     private void dumpProvider(String prefix, FileDescriptor fd, PrintWriter pw,
             final ContentProviderRecord r, String[] args, boolean dumpAll) {
+        final IApplicationThread thread = r.proc != null ? r.proc.getThread() : null;
         for (String s: args) {
             if (!dumpAll && s.contains("--proto")) {
-                if (r.proc != null && r.proc.thread != null) {
-                    dumpToTransferPipe(null , fd, pw, r, args);
+                if (thread != null) {
+                    dumpToTransferPipe(null , fd, pw, r, thread, args);
                 }
                 return;
             }
@@ -386,7 +389,7 @@
             pw.print(r);
             pw.print(" pid=");
             if (r.proc != null) {
-                pw.println(r.proc.pid);
+                pw.println(r.proc.getPid());
             } else {
                 pw.println("(not running)");
             }
@@ -394,10 +397,10 @@
                 r.dump(pw, innerPrefix, true);
             }
         }
-        if (r.proc != null && r.proc.thread != null) {
+        if (thread != null) {
             pw.println("    Client:");
             pw.flush();
-            dumpToTransferPipe("      ", fd, pw, r, args);
+            dumpToTransferPipe("      ", fd, pw, r, thread, args);
         }
     }
 
@@ -420,8 +423,9 @@
         // Only dump the first provider, since we are dumping in proto format
         for (int i = 0; i < providers.size(); i++) {
             final ContentProviderRecord r = providers.get(i);
-            if (r.proc != null && r.proc.thread != null) {
-                dumpToTransferPipe(null, fd, pw, r, newArgs);
+            IApplicationThread thread;
+            if (r.proc != null && (thread = r.proc.getThread()) != null) {
+                dumpToTransferPipe(null, fd, pw, r, thread, newArgs);
                 return true;
             }
         }
@@ -433,11 +437,11 @@
      * any meta string (e.g., provider info, indentation) written to the file descriptor.
      */
     private void dumpToTransferPipe(String prefix, FileDescriptor fd, PrintWriter pw,
-            final ContentProviderRecord r, String[] args) {
+            final ContentProviderRecord r, final IApplicationThread thread, String[] args) {
         try {
             TransferPipe tp = new TransferPipe();
             try {
-                r.proc.thread.dumpProvider(
+                thread.dumpProvider(
                     tp.getWriteFd(), r.provider.asBinder(), args);
                 tp.setBufferPrefix(prefix);
                 // Short timeout, since blocking here can
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8a1b4e3..485087d 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -23,6 +23,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.annotation.Nullable;
+import android.app.IApplicationThread;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -281,7 +282,7 @@
         proto.write(ServiceRecordProto.SHORT_NAME, this.shortInstanceName);
         proto.write(ServiceRecordProto.IS_RUNNING, app != null);
         if (app != null) {
-            proto.write(ServiceRecordProto.PID, app.pid);
+            proto.write(ServiceRecordProto.PID, app.getPid());
         }
         if (intent != null) {
             intent.getIntent().dumpDebug(proto, ServiceRecordProto.INTENT, false, true, false,
@@ -582,13 +583,14 @@
         restartTracker.setRestarting(true, memFactor, now);
     }
 
-    public void setProcess(ProcessRecord _proc) {
-        if (_proc != null) {
+    public void setProcess(ProcessRecord proc, IApplicationThread thread, int pid,
+            UidRecord uidRecord) {
+        if (proc != null) {
             // We're starting a new process for this service, but a previous one is allowed to start
             // background activities. Remove that ability now (unless the new process is the same as
             // the previous one, which is a common case).
             if (mAppForAllowingBgActivityStartsByStart != null) {
-                if (mAppForAllowingBgActivityStartsByStart != _proc) {
+                if (mAppForAllowingBgActivityStartsByStart != proc) {
                     mAppForAllowingBgActivityStartsByStart
                             .removeAllowBackgroundActivityStartsToken(this);
                     ams.mHandler.removeCallbacks(mCleanUpAllowBgActivityStartsByStartCallback);
@@ -596,34 +598,35 @@
             }
             // Make sure the cleanup callback knows about the new process.
             mAppForAllowingBgActivityStartsByStart = mIsAllowedBgActivityStartsByStart
-                    ? _proc : null;
+                    ? proc : null;
             if (mIsAllowedBgActivityStartsByStart
                     || mIsAllowedBgActivityStartsByBinding) {
-                _proc.addOrUpdateAllowBackgroundActivityStartsToken(this,
+                proc.addOrUpdateAllowBackgroundActivityStartsToken(this,
                         getExclusiveOriginatingToken());
             } else {
-                _proc.removeAllowBackgroundActivityStartsToken(this);
+                proc.removeAllowBackgroundActivityStartsToken(this);
             }
             if (mIsAllowedBgFgsStartsByBinding) {
-                _proc.addAllowBackgroundFgsStartsToken(this);
+                proc.mState.addAllowBackgroundFgsStartsToken(this);
             } else {
-                _proc.removeAllowBackgroundFgsStartsToken(this);
+                proc.mState.removeAllowBackgroundFgsStartsToken(this);
             }
         }
-        if (app != null && app != _proc) {
+        if (app != null && app != proc) {
             // If the old app is allowed to start bg activities because of a service start, leave it
             // that way until the cleanup callback runs. Otherwise we can remove its bg activity
             // start ability immediately (it can't be bound now).
             if (!mIsAllowedBgActivityStartsByStart) {
                 app.removeAllowBackgroundActivityStartsToken(this);
             }
-            app.updateBoundClientUids();
+            app.mServices.updateBoundClientUids();
         }
-        app = _proc;
-        if (pendingConnectionGroup > 0 && _proc != null) {
-            _proc.connectionService = this;
-            _proc.connectionGroup = pendingConnectionGroup;
-            _proc.connectionImportance = pendingConnectionImportance;
+        app = proc;
+        if (pendingConnectionGroup > 0 && proc != null) {
+            final ProcessServiceRecord psr = proc.mServices;
+            psr.setConnectionService(this);
+            psr.setConnectionGroup(pendingConnectionGroup);
+            psr.setConnectionImportance(pendingConnectionImportance);
             pendingConnectionGroup = pendingConnectionImportance = 0;
         }
         if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS) {
@@ -631,7 +634,7 @@
                 ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
                 for (int i = 0; i < cr.size(); i++) {
                     final ConnectionRecord conn = cr.get(i);
-                    if (_proc != null) {
+                    if (proc != null) {
                         conn.startAssociationIfNeeded();
                     } else {
                         conn.stopAssociation();
@@ -639,8 +642,8 @@
                 }
             }
         }
-        if (_proc != null) {
-            _proc.updateBoundClientUids();
+        if (proc != null) {
+            proc.mServices.updateBoundClientUids();
         }
     }
 
@@ -658,7 +661,7 @@
 
         // if we have a process attached, add bound client uid of this connection to it
         if (app != null) {
-            app.addBoundClientUid(c.clientUid);
+            app.mServices.addBoundClientUid(c.clientUid);
         }
     }
 
@@ -666,7 +669,7 @@
         connections.remove(binder);
         // if we have a process attached, tell it to update the state of bound clients
         if (app != null) {
-            app.updateBoundClientUids();
+            app.mServices.updateBoundClientUids();
         }
     }
 
@@ -820,9 +823,9 @@
             return;
         }
         if (mIsAllowedBgFgsStartsByBinding) {
-            app.addAllowBackgroundFgsStartsToken(this);
+            app.mState.addAllowBackgroundFgsStartsToken(this);
         } else {
-            app.removeAllowBackgroundFgsStartsToken(this);
+            app.mState.removeAllowBackgroundFgsStartsToken(this);
         }
     }
 
@@ -937,7 +940,7 @@
 
     public void postNotification() {
         final int appUid = appInfo.uid;
-        final int appPid = app.pid;
+        final int appPid = app.getPid();
         if (foregroundId != 0 && foregroundNoti != null) {
             // Do asynchronous communication with notification manager to
             // avoid deadlocks.
@@ -1057,7 +1060,7 @@
         final String localPackageName = packageName;
         final int localForegroundId = foregroundId;
         final int appUid = appInfo.uid;
-        final int appPid = app != null ? app.pid : 0;
+        final int appPid = app != null ? app.getPid() : 0;
         ams.mHandler.post(new Runnable() {
             public void run() {
                 NotificationManagerInternal nm = LocalServices.getService(
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7299e81..e022e97 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -81,6 +81,7 @@
     static final String[] sDeviceConfigScopes = new String[] {
         DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_CONFIGURATION,
+        DeviceConfig.NAMESPACE_CONNECTIVITY,
         DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
         DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS,
         DeviceConfig.NAMESPACE_MEDIA_NATIVE,
diff --git a/services/core/java/com/android/server/am/StrictModeViolationDialog.java b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
index 783f150..4cc1fc1 100644
--- a/services/core/java/com/android/server/am/StrictModeViolationDialog.java
+++ b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
@@ -49,8 +49,8 @@
         mProc = app;
         mResult = result;
         CharSequence name;
-        if ((app.pkgList.size() == 1) &&
-                (name=context.getPackageManager().getApplicationLabel(app.info)) != null) {
+        if (app.getPkgList().size() == 1
+                && (name = context.getPackageManager().getApplicationLabel(app.info)) != null) {
             setMessage(res.getString(
                     com.android.internal.R.string.smv_application,
                     name.toString(), app.info.processName));
@@ -67,7 +67,7 @@
                   res.getText(com.android.internal.R.string.dlg_ok),
                   mHandler.obtainMessage(ACTION_OK));
 
-        if (app.errorReportReceiver != null) {
+        if (app.mErrorState.getErrorReportReceiver() != null) {
             setButton(DialogInterface.BUTTON_NEGATIVE,
                       res.getText(com.android.internal.R.string.report),
                       mHandler.obtainMessage(ACTION_OK_AND_REPORT));
@@ -84,9 +84,9 @@
 
     private final Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
-            synchronized (mService) {
+            synchronized (mService.mProcLock) {
                 if (mProc != null) {
-                    mProc.getDialogController().clearViolationDialogs();
+                    mProc.mErrorState.getDialogController().clearViolationDialogs();
                 }
             }
             mResult.set(msg.what);
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index b3150d1..df65ee6 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -204,16 +204,18 @@
                 } else {
                     UidRecord validateUid = mValidateUids.get(item.uid);
                     if (validateUid == null) {
-                        validateUid = new UidRecord(item.uid);
+                        validateUid = new UidRecord(item.uid, null);
                         mValidateUids.put(item.uid, validateUid);
                     }
                     if ((item.change & UidRecord.CHANGE_IDLE) != 0) {
-                        validateUid.idle = true;
+                        validateUid.setIdle(true);
                     } else if ((item.change & UidRecord.CHANGE_ACTIVE) != 0) {
-                        validateUid.idle = false;
+                        validateUid.setIdle(false);
                     }
-                    validateUid.setCurProcState(validateUid.setProcState = item.procState);
-                    validateUid.curCapability = validateUid.setCapability = item.capability;
+                    validateUid.setSetProcState(item.procState);
+                    validateUid.setCurProcState(item.procState);
+                    validateUid.setSetCapability(item.capability);
+                    validateUid.setCurCapability(item.capability);
                     validateUid.lastDispatchedProcStateSeq = item.procStateSeq;
                 }
             }
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index f1945ed..2fb662f 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -26,27 +26,58 @@
 import android.util.proto.ProtoOutputStream;
 import android.util.proto.ProtoUtils;
 
+import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.am.UidObserverController.ChangeRecord;
 
+import java.util.function.Consumer;
+
 /**
  * Overall information about a uid that has actively running processes.
  */
 public final class UidRecord {
-    final int uid;
-    int mCurProcState;
-    int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
-    int curCapability;
-    int setCapability;
-    long lastBackgroundTime;
-    boolean ephemeral;
-    boolean foregroundServices;
-    boolean curWhitelist;
-    boolean setWhitelist;
-    boolean idle;
-    boolean setIdle;
-    int numProcs;
-    ArraySet<ProcessRecord> procRecords = new ArraySet<>();
+    private final ActivityManagerService mService;
+    private final ActivityManagerGlobalLock mProcLock;
+    private final int mUid;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurProcState;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mCurCapability;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mSetCapability;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private long mLastBackgroundTime;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mEphemeral;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mForegroundServices;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mCurAllowList;;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mSetAllowList;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mIdle;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private boolean mSetIdle;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private int mNumProcs;
+
+    @CompositeRWLock({"mService", "mProcLock"})
+    private ArraySet<ProcessRecord> mProcRecords = new ArraySet<>();
 
     /**
      * Sequence number associated with the {@link #mCurProcState}. This is incremented using
@@ -111,32 +142,168 @@
     // UidObserverController is the only thing that should modify this.
     final ChangeRecord pendingChange = new ChangeRecord();
 
-    int lastReportedChange;
+    @GuardedBy("mService")
+    private int mLastReportedChange;
 
-    public UidRecord(int _uid) {
-        uid = _uid;
-        idle = true;
+    public UidRecord(int uid, ActivityManagerService service) {
+        mUid = uid;
+        mService = service;
+        mProcLock = service != null ? service.mProcLock : null;
+        mIdle = true;
         reset();
     }
 
-    public int getCurProcState() {
+    int getUid() {
+        return mUid;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurProcState() {
         return mCurProcState;
     }
 
-    public void setCurProcState(int curProcState) {
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurProcState(int curProcState) {
         mCurProcState = curProcState;
     }
 
-    public void reset() {
-        setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
-        foregroundServices = false;
-        curCapability = 0;
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetProcState() {
+        return mSetProcState;
+    }
 
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetProcState(int setProcState) {
+        mSetProcState = setProcState;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getCurCapability() {
+        return mCurCapability;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurCapability(int curCapability) {
+        mCurCapability = curCapability;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getSetCapability() {
+        return mSetCapability;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetCapability(int setCapability) {
+        mSetCapability = setCapability;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    long getLastBackgroundTime() {
+        return mLastBackgroundTime;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setLastBackgroundTime(long lastBackgroundTime) {
+        mLastBackgroundTime = lastBackgroundTime;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isEphemeral() {
+        return mEphemeral;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setEphemeral(boolean ephemeral) {
+        mEphemeral = ephemeral;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean hasForegroundServices() {
+        return mForegroundServices;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setForegroundServices(boolean foregroundServices) {
+        mForegroundServices = foregroundServices;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isCurAllowListed() {
+        return mCurAllowList;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setCurAllowListed(boolean curAllowList) {
+        mCurAllowList = curAllowList;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isSetAllowListed() {
+        return mSetAllowList;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetAllowListed(boolean setAllowlist) {
+        mSetAllowList = setAllowlist;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isIdle() {
+        return mIdle;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setIdle(boolean idle) {
+        mIdle = idle;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    boolean isSetIdle() {
+        return mSetIdle;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void setSetIdle(boolean setIdle) {
+        mSetIdle = setIdle;
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    int getNumOfProcs() {
+        return mProcRecords.size();
+    }
+
+    @GuardedBy(anyOf = {"mService", "mProcLock"})
+    void forEachProcess(Consumer<ProcessRecord> callback) {
+        for (int i = mProcRecords.size() - 1; i >= 0; i--) {
+            callback.accept(mProcRecords.valueAt(i));
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void addProcess(ProcessRecord app) {
+        mProcRecords.add(app);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void removeProcess(ProcessRecord app) {
+        mProcRecords.remove(app);
+    }
+
+    @GuardedBy("mService")
+    void setLastReportedChange(int lastReportedChange) {
+        mLastReportedChange = lastReportedChange;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    void reset() {
+        setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+        mForegroundServices = false;
+        mCurCapability = 0;
     }
 
     public void updateHasInternetPermission() {
         hasInternetPermission = ActivityManager.checkUidPermission(Manifest.permission.INTERNET,
-                uid) == PackageManager.PERMISSION_GRANTED;
+                mUid) == PackageManager.PERMISSION_GRANTED;
     }
 
     /**
@@ -153,19 +320,19 @@
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         long token = proto.start(fieldId);
-        proto.write(UidRecordProto.UID, uid);
+        proto.write(UidRecordProto.UID, mUid);
         proto.write(UidRecordProto.CURRENT, ProcessList.makeProcStateProtoEnum(mCurProcState));
-        proto.write(UidRecordProto.EPHEMERAL, ephemeral);
-        proto.write(UidRecordProto.FG_SERVICES, foregroundServices);
-        proto.write(UidRecordProto.WHILELIST, curWhitelist);
+        proto.write(UidRecordProto.EPHEMERAL, mEphemeral);
+        proto.write(UidRecordProto.FG_SERVICES, mForegroundServices);
+        proto.write(UidRecordProto.WHILELIST, mCurAllowList);
         ProtoUtils.toDuration(proto, UidRecordProto.LAST_BACKGROUND_TIME,
-                lastBackgroundTime, SystemClock.elapsedRealtime());
-        proto.write(UidRecordProto.IDLE, idle);
-        if (lastReportedChange != 0) {
+                mLastBackgroundTime, SystemClock.elapsedRealtime());
+        proto.write(UidRecordProto.IDLE, mIdle);
+        if (mLastReportedChange != 0) {
             ProtoUtils.writeBitWiseFlagsToProtoEnum(proto, UidRecordProto.LAST_REPORTED_CHANGES,
-                    lastReportedChange, ORIG_ENUMS, PROTO_ENUMS);
+                    mLastReportedChange, ORIG_ENUMS, PROTO_ENUMS);
         }
-        proto.write(UidRecordProto.NUM_PROCS, numProcs);
+        proto.write(UidRecordProto.NUM_PROCS, mNumProcs);
 
         long seqToken = proto.start(UidRecordProto.NETWORK_STATE_UPDATE);
         proto.write(UidRecordProto.ProcStateSequence.CURURENT, curProcStateSeq);
@@ -182,54 +349,54 @@
         sb.append("UidRecord{");
         sb.append(Integer.toHexString(System.identityHashCode(this)));
         sb.append(' ');
-        UserHandle.formatUid(sb, uid);
+        UserHandle.formatUid(sb, mUid);
         sb.append(' ');
         sb.append(ProcessList.makeProcStateString(mCurProcState));
-        if (ephemeral) {
+        if (mEphemeral) {
             sb.append(" ephemeral");
         }
-        if (foregroundServices) {
+        if (mForegroundServices) {
             sb.append(" fgServices");
         }
-        if (curWhitelist) {
-            sb.append(" whitelist");
+        if (mCurAllowList) {
+            sb.append(" allowlist");
         }
-        if (lastBackgroundTime > 0) {
+        if (mLastBackgroundTime > 0) {
             sb.append(" bg:");
-            TimeUtils.formatDuration(SystemClock.elapsedRealtime()-lastBackgroundTime, sb);
+            TimeUtils.formatDuration(SystemClock.elapsedRealtime() - mLastBackgroundTime, sb);
         }
-        if (idle) {
+        if (mIdle) {
             sb.append(" idle");
         }
-        if (lastReportedChange != 0) {
+        if (mLastReportedChange != 0) {
             sb.append(" change:");
             boolean printed = false;
-            if ((lastReportedChange & CHANGE_GONE) != 0) {
+            if ((mLastReportedChange & CHANGE_GONE) != 0) {
                 printed = true;
                 sb.append("gone");
             }
-            if ((lastReportedChange & CHANGE_IDLE) != 0) {
+            if ((mLastReportedChange & CHANGE_IDLE) != 0) {
                 if (printed) {
                     sb.append("|");
                 }
                 printed = true;
                 sb.append("idle");
             }
-            if ((lastReportedChange & CHANGE_ACTIVE) != 0) {
+            if ((mLastReportedChange & CHANGE_ACTIVE) != 0) {
                 if (printed) {
                     sb.append("|");
                 }
                 printed = true;
                 sb.append("active");
             }
-            if ((lastReportedChange & CHANGE_CACHED) != 0) {
+            if ((mLastReportedChange & CHANGE_CACHED) != 0) {
                 if (printed) {
                     sb.append("|");
                 }
                 printed = true;
                 sb.append("cached");
             }
-            if ((lastReportedChange & CHANGE_UNCACHED) != 0) {
+            if ((mLastReportedChange & CHANGE_UNCACHED) != 0) {
                 if (printed) {
                     sb.append("|");
                 }
@@ -237,7 +404,7 @@
             }
         }
         sb.append(" procs:");
-        sb.append(numProcs);
+        sb.append(mNumProcs);
         sb.append(" seq(");
         sb.append(curProcStateSeq);
         sb.append(",");
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3bbc837..6dd78e7 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -118,6 +118,7 @@
 import java.util.Objects;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 /**
  * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -1458,7 +1459,7 @@
             t.traceBegin("updateConfigurationAndProfileIds");
             if (foreground) {
                 // Make sure the old user is no longer considering the display to be on.
-                mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
+                mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
                 boolean userSwitchUiEnabled;
                 synchronized (mLock) {
                     mCurrentUserId = userId;
@@ -3045,7 +3046,7 @@
             d.show();
         }
 
-        void reportGlobalUsageEventLocked(int event) {
+        void reportGlobalUsageEvent(int event) {
             mService.reportGlobalUsageEvent(event);
         }
 
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
new file mode 100644
index 0000000..3acad49
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2021 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.app;
+
+import android.annotation.NonNull;
+import android.app.GameManager;
+import android.app.GameManager.GameMode;
+import android.app.IGameManagerService;
+import android.content.Context;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+
+/**
+ * Service to manage game related features.
+ *
+ * <p>Game service is a core service that monitors, coordinates game related features,
+ * as well as collect metrics.</p>
+ *
+ * @hide
+ */
+public final class GameManagerService extends IGameManagerService.Stub {
+    public static final String TAG = "GameManagerService";
+
+    private static final boolean DEBUG = false;
+
+    static final int WRITE_SETTINGS = 1;
+    static final int REMOVE_SETTINGS = 2;
+    static final int WRITE_SETTINGS_DELAY = 10 * 1000;  // 10 seconds
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+    private final Handler mHandler;
+    @GuardedBy("mLock")
+    private final ArrayMap<Integer, Settings> mSettings = new ArrayMap<>();
+
+    public GameManagerService(Context context) {
+        this(context, createServiceThread().getLooper());
+    }
+
+    GameManagerService(Context context, Looper looper) {
+        mContext = context;
+        mHandler = new SettingsHandler(looper);
+    }
+
+    class SettingsHandler extends Handler {
+
+        SettingsHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            doHandleMessage(msg);
+        }
+
+        void doHandleMessage(Message msg) {
+            switch (msg.what) {
+                case WRITE_SETTINGS: {
+                    final int userId = (int) msg.obj;
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                    synchronized (mLock) {
+                        removeMessages(WRITE_SETTINGS, msg.obj);
+                        if (mSettings.containsKey(userId)) {
+                            Settings userSettings = mSettings.get(userId);
+                            userSettings.writePersistentDataLocked();
+                        }
+                    }
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                    break;
+                }
+                case REMOVE_SETTINGS: {
+                    final int userId = (int) msg.obj;
+                    synchronized (mLock) {
+                        // Since the user was removed, ignore previous write message
+                        // and do write here.
+                        removeMessages(WRITE_SETTINGS, msg.obj);
+                        removeMessages(REMOVE_SETTINGS, msg.obj);
+                        if (mSettings.containsKey(userId)) {
+                            final Settings userSettings = mSettings.get(userId);
+                            mSettings.remove(userId);
+                            userSettings.writePersistentDataLocked();
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * SystemService lifecycle for GameService.
+     * @hide
+     */
+    public static class Lifecycle extends SystemService {
+        private GameManagerService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mService = new GameManagerService(getContext());
+            publishBinderService(Context.GAME_SERVICE, mService);
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (phase == PHASE_BOOT_COMPLETED) {
+                mService.onBootCompleted();
+            }
+        }
+
+        @Override
+        public void onUserStarting(@NonNull TargetUser user) {
+            mService.onUserStarting(user.getUserIdentifier());
+        }
+
+        @Override
+        public void onUserStopping(@NonNull TargetUser user) {
+            mService.onUserStopping(user.getUserIdentifier());
+        }
+    }
+
+    //TODO(b/178111358) Add proper permission check and multi-user handling
+    @Override
+    public @GameMode int getGameMode(String packageName, int userId) {
+        synchronized (mLock) {
+            if (!mSettings.containsKey(userId)) {
+                return GameManager.GAME_MODE_UNSUPPORTED;
+            }
+            Settings userSettings = mSettings.get(userId);
+            return userSettings.getGameModeLocked(packageName);
+        }
+    }
+
+    //TODO(b/178111358) Add proper permission check and multi-user handling
+    @Override
+    public void setGameMode(String packageName, @GameMode int gameMode, int userId) {
+        synchronized (mLock) {
+            if (!mSettings.containsKey(userId)) {
+                return;
+            }
+            Settings userSettings = mSettings.get(userId);
+            userSettings.setGameModeLocked(packageName, gameMode);
+            final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
+            msg.obj = userId;
+            if (!mHandler.hasEqualMessages(WRITE_SETTINGS, userId)) {
+                mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
+            }
+        }
+    }
+
+    /**
+     * Notified when boot is completed.
+     */
+    @VisibleForTesting
+    void onBootCompleted() {
+        Slog.d(TAG, "onBootCompleted");
+    }
+
+    void onUserStarting(int userId) {
+        synchronized (mLock) {
+            if (mSettings.containsKey(userId)) {
+                return;
+            }
+
+            Settings userSettings = new Settings(Environment.getDataSystemDeDirectory(userId));
+            mSettings.put(userId, userSettings);
+            userSettings.readPersistentDataLocked();
+        }
+    }
+
+    void onUserStopping(int userId) {
+        synchronized (mLock) {
+            if (!mSettings.containsKey(userId)) {
+                return;
+            }
+            final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
+            msg.obj = userId;
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    private static ServiceThread createServiceThread() {
+        ServiceThread handlerThread = new ServiceThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+        handlerThread.start();
+        return handlerThread;
+    }
+}
diff --git a/services/core/java/com/android/server/app/Settings.java b/services/core/java/com/android/server/app/Settings.java
new file mode 100644
index 0000000..ab367fb
--- /dev/null
+++ b/services/core/java/com/android/server/app/Settings.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2021 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.app;
+
+import android.app.GameManager;
+import android.os.FileUtils;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Persists all GameService related settings.
+ * @hide
+ */
+public class Settings {
+
+    // The XML file follows the below format:
+    // <?xml>
+    // <packages>
+    //     <package></package>
+    //     ...
+    // </packages>
+    private static final String GAME_SERVICE_FILE_NAME = "game-manager-service.xml";
+
+    private static final String TAG_PACKAGE = "package";
+    private static final String TAG_PACKAGES = "packages";
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_GAME_MODE = "gameMode";
+
+    private final File mSystemDir;
+    @VisibleForTesting
+    final AtomicFile mSettingsFile;
+
+    // PackageName -> GameMode
+    private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
+
+    Settings(File dataDir) {
+        mSystemDir = new File(dataDir, "system");
+        mSystemDir.mkdirs();
+        FileUtils.setPermissions(mSystemDir.toString(),
+                FileUtils.S_IRWXU | FileUtils.S_IRWXG
+                        | FileUtils.S_IROTH | FileUtils.S_IXOTH,
+                -1, -1);
+        mSettingsFile = new AtomicFile(new File(mSystemDir, GAME_SERVICE_FILE_NAME));
+    }
+
+    /**
+     * Return the game mode of a given package.
+     * This operation must be synced with an external lock.
+     */
+    int getGameModeLocked(String packageName) {
+        if (mGameModes.containsKey(packageName)) {
+            return mGameModes.get(packageName);
+        }
+        return GameManager.GAME_MODE_UNSUPPORTED;
+    }
+
+    /**
+     * Set the game mode of a given package.
+     * This operation must be synced with an external lock.
+     */
+    void setGameModeLocked(String packageName, int gameMode) {
+        mGameModes.put(packageName, gameMode);
+    }
+
+    /**
+     * Write all current game service settings into disk.
+     * This operation must be synced with an external lock.
+     */
+    void writePersistentDataLocked() {
+        FileOutputStream fstr = null;
+        try {
+            fstr = mSettingsFile.startWrite();
+
+            final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_PACKAGES);
+            for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
+                serializer.startTag(null, TAG_PACKAGE);
+                serializer.attribute(null, ATTR_NAME, entry.getKey());
+                serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
+                serializer.endTag(null, TAG_PACKAGE);
+            }
+            serializer.endTag(null, TAG_PACKAGES);
+
+            serializer.endDocument();
+
+            mSettingsFile.finishWrite(fstr);
+
+            FileUtils.setPermissions(mSettingsFile.toString(),
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR
+                            | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                    -1, -1);
+            return;
+        } catch (java.io.IOException e) {
+            mSettingsFile.failWrite(fstr);
+            Slog.wtf(GameManagerService.TAG, "Unable to write game manager service settings, "
+                    + "current changes will be lost at reboot", e);
+        }
+    }
+
+    /**
+     * Read game service settings from the disk.
+     * This operation must be synced with an external lock.
+     */
+    boolean readPersistentDataLocked() {
+        mGameModes.clear();
+
+        try {
+            final FileInputStream str = mSettingsFile.openRead();
+
+            final TypedXmlPullParser parser = Xml.resolvePullParser(str);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+            }
+            if (type != XmlPullParser.START_TAG) {
+                Slog.wtf(GameManagerService.TAG,
+                        "No start tag found in package manager settings");
+                return false;
+            }
+
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals(TAG_PACKAGE)) {
+                    readPackage(parser);
+                } else {
+                    Slog.w(GameManagerService.TAG, "Unknown element: " + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+        } catch (XmlPullParserException | java.io.IOException e) {
+            Slog.wtf(GameManagerService.TAG, "Error reading package manager settings", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void readPackage(TypedXmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        String name = null;
+        int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
+        try {
+            name = parser.getAttributeValue(null, ATTR_NAME);
+            gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
+        } catch (XmlPullParserException e) {
+            Slog.wtf(GameManagerService.TAG, "Error reading game mode", e);
+        }
+        if (name != null) {
+            mGameModes.put(name, gameMode);
+        } else {
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
new file mode 100644
index 0000000..e97f0b4
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2021 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.apphibernation;
+
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REMOVED_FOR_ALL_USERS;
+import static android.content.Intent.EXTRA_REPLACING;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.apphibernation.IAppHibernationService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * System service that manages app hibernation state, a state apps can enter that means they are
+ * not being actively used and can be optimized for storage. The actual policy for determining
+ * if an app should hibernate is managed by PermissionController code.
+ */
+public final class AppHibernationService extends SystemService {
+    private static final String TAG = "AppHibernationService";
+    private static final int PACKAGE_MATCH_FLAGS =
+            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                    | PackageManager.MATCH_UNINSTALLED_PACKAGES
+                    | PackageManager.MATCH_DISABLED_COMPONENTS;
+
+    /**
+     * Lock for accessing any in-memory hibernation state
+     */
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final IPackageManager mIPackageManager;
+    private final IActivityManager mIActivityManager;
+    private final UserManager mUserManager;
+    @GuardedBy("mLock")
+    private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>();
+    private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores =
+            new SparseArray<>();
+    @GuardedBy("mLock")
+    private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>();
+    private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore;
+    private final Injector mInjector;
+
+    /**
+     * Initializes the system service.
+     * <p>
+     * Subclasses must define a single argument constructor that accepts the context
+     * and passes it to super.
+     * </p>
+     *
+     * @param context The system server context.
+     */
+    public AppHibernationService(@NonNull Context context) {
+        this(new InjectorImpl(context));
+    }
+
+    @VisibleForTesting
+    AppHibernationService(@NonNull Injector injector) {
+        super(injector.getContext());
+        mContext = injector.getContext();
+        mIPackageManager = injector.getPackageManager();
+        mIActivityManager = injector.getActivityManager();
+        mUserManager = injector.getUserManager();
+        mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore();
+        mInjector = injector;
+
+        final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(ACTION_PACKAGE_REMOVED);
+        intentFilter.addDataScheme("package");
+        userAllContext.registerReceiver(mBroadcastReceiver, intentFilter);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.APP_HIBERNATION_SERVICE, mServiceStub);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_BOOT_COMPLETED) {
+            List<GlobalLevelState> states =
+                    mGlobalLevelHibernationDiskStore.readHibernationStates();
+            synchronized (mLock) {
+                initializeGlobalHibernationStates(states);
+            }
+        }
+    }
+
+    /**
+     * Whether a package is hibernating for a given user.
+     *
+     * @param packageName the package to check
+     * @param userId the user to check
+     * @return true if package is hibernating for the user
+     */
+    boolean isHibernatingForUser(String packageName, int userId) {
+        userId = handleIncomingUser(userId, "isHibernating");
+        if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+            Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user "
+                    + userId);
+            return false;
+        }
+        synchronized (mLock) {
+            final Map<String, UserLevelState> packageStates = mUserStates.get(userId);
+            final UserLevelState pkgState = packageStates.get(packageName);
+            if (pkgState == null) {
+                throw new IllegalArgumentException(
+                        String.format("Package %s is not installed for user %s",
+                                packageName, userId));
+            }
+            return pkgState.hibernated;
+        }
+    }
+
+    /**
+     * Whether a package is hibernated globally. This only occurs when a package is hibernating for
+     * all users and allows us to make optimizations at the package or APK level.
+     *
+     * @param packageName package to check
+     */
+    boolean isHibernatingGlobally(String packageName) {
+        synchronized (mLock) {
+            GlobalLevelState state = mGlobalHibernationStates.get(packageName);
+            if (state == null) {
+                throw new IllegalArgumentException(
+                        String.format("Package %s is not installed", packageName));
+            }
+            return state.hibernated;
+        }
+    }
+
+    /**
+     * Set whether the package is hibernating for the given user.
+     *
+     * @param packageName package to modify state
+     * @param userId user
+     * @param isHibernating new hibernation state
+     */
+    void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
+        userId = handleIncomingUser(userId, "setHibernating");
+        if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
+            Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user "
+                    + userId);
+            return;
+        }
+        synchronized (mLock) {
+            final Map<String, UserLevelState> packageStates = mUserStates.get(userId);
+            final UserLevelState pkgState = packageStates.get(packageName);
+            if (pkgState == null) {
+                throw new IllegalArgumentException(
+                        String.format("Package %s is not installed for user %s",
+                                packageName, userId));
+            }
+
+            if (pkgState.hibernated == isHibernating) {
+                return;
+            }
+
+            if (isHibernating) {
+                hibernatePackageForUser(packageName, userId, pkgState);
+            } else {
+                unhibernatePackageForUser(packageName, userId, pkgState);
+            }
+            List<UserLevelState> states = new ArrayList<>(mUserStates.get(userId).values());
+            mUserDiskStores.get(userId).scheduleWriteHibernationStates(states);
+        }
+    }
+
+    /**
+     * Set whether the package should be hibernated globally at a package level, allowing the
+     * the system to make optimizations at the package or APK level.
+     *
+     * @param packageName package to hibernate globally
+     * @param isHibernating new hibernation state
+     */
+    void setHibernatingGlobally(String packageName, boolean isHibernating) {
+        synchronized (mLock) {
+            GlobalLevelState state = mGlobalHibernationStates.get(packageName);
+            if (state == null) {
+                throw new IllegalArgumentException(
+                        String.format("Package %s is not installed for any user", packageName));
+            }
+            if (state.hibernated != isHibernating) {
+                if (isHibernating) {
+                    hibernatePackageGlobally(packageName, state);
+                } else {
+                    unhibernatePackageGlobally(packageName, state);
+                }
+                List<GlobalLevelState> states = new ArrayList<>(mGlobalHibernationStates.values());
+                mGlobalLevelHibernationDiskStore.scheduleWriteHibernationStates(states);
+            }
+        }
+    }
+
+    /**
+     * Put an app into hibernation for a given user, allowing user-level optimizations to occur.
+     *
+     * @param pkgState package hibernation state
+     */
+    @GuardedBy("mLock")
+    private void hibernatePackageForUser(@NonNull String packageName, int userId,
+            @NonNull UserLevelState pkgState) {
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage");
+        final long caller = Binder.clearCallingIdentity();
+        try {
+            mIActivityManager.forceStopPackage(packageName, userId);
+            mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
+                    null /* observer */);
+            pkgState.hibernated = true;
+        } catch (RemoteException e) {
+            throw new IllegalStateException(
+                    "Failed to hibernate due to manager not being available", e);
+        } finally {
+            Binder.restoreCallingIdentity(caller);
+            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+        }
+    }
+
+    /**
+     * Remove a package from hibernation for a given user.
+     *
+     * @param pkgState package hibernation state
+     */
+    @GuardedBy("mLock")
+    private void unhibernatePackageForUser(@NonNull String packageName, int userId,
+            UserLevelState pkgState) {
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage");
+        final long caller = Binder.clearCallingIdentity();
+        try {
+            mIPackageManager.setPackageStoppedState(packageName, false, userId);
+            pkgState.hibernated = false;
+        } catch (RemoteException e) {
+            throw new IllegalStateException(
+                    "Failed to unhibernate due to manager not being available", e);
+        } finally {
+            Binder.restoreCallingIdentity(caller);
+            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+        }
+    }
+
+    /**
+     * Put a package into global hibernation, optimizing its storage at a package / APK level.
+     */
+    @GuardedBy("mLock")
+    private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally");
+        // TODO(175830194): Delete vdex/odex when DexManager API is built out
+        state.hibernated = true;
+        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+    }
+
+    /**
+     * Unhibernate a package from global hibernation.
+     */
+    @GuardedBy("mLock")
+    private void unhibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackageGlobally");
+        state.hibernated = false;
+        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+    }
+
+    /**
+     * Initializes in-memory store of user-level hibernation states for the given user
+     *
+     * @param userId user id to add installed packages for
+     * @param diskStates states pulled from disk, if available
+     */
+    @GuardedBy("mLock")
+    private void initializeUserHibernationStates(int userId,
+            @Nullable List<UserLevelState> diskStates) {
+        List<PackageInfo> packages;
+        try {
+            packages = mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId).getList();
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Package manager not available", e);
+        }
+
+        Map<String, UserLevelState> userLevelStates = new ArrayMap<>();
+
+        for (int i = 0, size = packages.size(); i < size; i++) {
+            String packageName = packages.get(i).packageName;
+            UserLevelState state = new UserLevelState();
+            state.packageName = packageName;
+            userLevelStates.put(packageName, state);
+        }
+
+        if (diskStates != null) {
+            Set<String> installedPackages = new ArraySet<>();
+            for (int i = 0, size = packages.size(); i < size; i++) {
+                installedPackages.add(packages.get(i).packageName);
+            }
+            for (int i = 0, size = diskStates.size(); i < size; i++) {
+                String packageName = diskStates.get(i).packageName;
+                if (!installedPackages.contains(packageName)) {
+                    Slog.w(TAG, String.format(
+                            "No hibernation state associated with package %s user %d. Maybe"
+                                    + "the package was uninstalled? ", packageName, userId));
+                    continue;
+                }
+                userLevelStates.put(packageName, diskStates.get(i));
+            }
+        }
+        mUserStates.put(userId, userLevelStates);
+    }
+
+    /**
+     * Initialize in-memory store of global level hibernation states.
+     *
+     * @param diskStates global level hibernation states pulled from disk, if available
+     */
+    @GuardedBy("mLock")
+    private void initializeGlobalHibernationStates(@Nullable List<GlobalLevelState> diskStates) {
+        List<PackageInfo> packages;
+        try {
+            packages = mIPackageManager.getInstalledPackages(
+                    PACKAGE_MATCH_FLAGS | MATCH_ANY_USER, 0 /* userId */).getList();
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Package manager not available", e);
+        }
+
+        for (int i = 0, size = packages.size(); i < size; i++) {
+            String packageName = packages.get(i).packageName;
+            GlobalLevelState state = new GlobalLevelState();
+            state.packageName = packageName;
+            mGlobalHibernationStates.put(packageName, state);
+        }
+        if (diskStates != null) {
+            Set<String> installedPackages = new ArraySet<>();
+            for (int i = 0, size = packages.size(); i < size; i++) {
+                installedPackages.add(packages.get(i).packageName);
+            }
+            for (int i = 0, size = diskStates.size(); i < size; i++) {
+                GlobalLevelState state = diskStates.get(i);
+                if (!installedPackages.contains(state.packageName)) {
+                    Slog.w(TAG, String.format(
+                            "No hibernation state associated with package %s. Maybe the "
+                                    + "package was uninstalled? ", state.packageName));
+                    continue;
+                }
+                mGlobalHibernationStates.put(state.packageName, state);
+            }
+        }
+    }
+
+    @Override
+    public void onUserUnlocking(@NonNull TargetUser user) {
+        int userId = user.getUserIdentifier();
+        HibernationStateDiskStore<UserLevelState> diskStore =
+                mInjector.getUserLevelDiskStore(userId);
+        mUserDiskStores.put(userId, diskStore);
+        List<UserLevelState> storedStates = diskStore.readHibernationStates();
+        synchronized (mLock) {
+            initializeUserHibernationStates(userId, storedStates);
+        }
+    }
+
+    @Override
+    public void onUserStopping(@NonNull TargetUser user) {
+        int userId = user.getUserIdentifier();
+        // TODO: Flush any scheduled writes to disk immediately on user stopping / power off.
+        synchronized (mLock) {
+            mUserDiskStores.remove(userId);
+            mUserStates.remove(userId);
+        }
+    }
+
+    private void onPackageAdded(@NonNull String packageName, int userId) {
+        synchronized (mLock) {
+            UserLevelState userState = new UserLevelState();
+            userState.packageName = packageName;
+            mUserStates.get(userId).put(packageName, userState);
+            if (!mGlobalHibernationStates.containsKey(packageName)) {
+                GlobalLevelState globalState = new GlobalLevelState();
+                globalState.packageName = packageName;
+                mGlobalHibernationStates.put(packageName, globalState);
+            }
+        }
+    }
+
+    private void onPackageRemoved(@NonNull String packageName, int userId) {
+        synchronized (mLock) {
+            mUserStates.get(userId).remove(packageName);
+        }
+    }
+
+    private void onPackageRemovedForAllUsers(@NonNull String packageName) {
+        synchronized (mLock) {
+            mGlobalHibernationStates.remove(packageName);
+        }
+    }
+
+    /**
+     * Private helper method to get the real user id and enforce permission checks.
+     *
+     * @param userId user id to handle
+     * @param name name to use for exceptions
+     * @return real user id
+     */
+    private int handleIncomingUser(int userId, @NonNull String name) {
+        int callingUid = Binder.getCallingUid();
+        try {
+            return mIActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+                    false /* allowAll */, true /* requireFull */, name, null);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this);
+
+    static final class AppHibernationServiceStub extends IAppHibernationService.Stub {
+        final AppHibernationService mService;
+
+        AppHibernationServiceStub(AppHibernationService service) {
+            mService = service;
+        }
+
+        @Override
+        public boolean isHibernatingForUser(String packageName, int userId) {
+            return mService.isHibernatingForUser(packageName, userId);
+        }
+
+        @Override
+        public void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
+            mService.setHibernatingForUser(packageName, userId, isHibernating);
+        }
+
+        @Override
+        public void setHibernatingGlobally(String packageName, boolean isHibernating) {
+            mService.setHibernatingGlobally(packageName, isHibernating);
+        }
+
+        @Override
+        public boolean isHibernatingGlobally(String packageName) {
+            return mService.isHibernatingGlobally(packageName);
+        }
+
+        @Override
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err, @NonNull String[] args,
+                @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
+            new AppHibernationShellCommand(mService).exec(this, in, out, err, args, callback,
+                    resultReceiver);
+        }
+    }
+
+    // Broadcast receiver for package add/removal events
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            if (userId == UserHandle.USER_NULL) {
+                return;
+            }
+
+            final String action = intent.getAction();
+            if (ACTION_PACKAGE_ADDED.equals(action) || ACTION_PACKAGE_REMOVED.equals(action)) {
+                final String packageName = intent.getData().getSchemeSpecificPart();
+                if (intent.getBooleanExtra(EXTRA_REPLACING, false)) {
+                    // Package removal/add is part of an update, so no need to modify package state.
+                    return;
+                }
+
+                if (ACTION_PACKAGE_ADDED.equals(action)) {
+                    onPackageAdded(packageName, userId);
+                } else if (ACTION_PACKAGE_REMOVED.equals(action)) {
+                    onPackageRemoved(packageName, userId);
+                    if (intent.getBooleanExtra(EXTRA_REMOVED_FOR_ALL_USERS, false)) {
+                        onPackageRemovedForAllUsers(packageName);
+                    }
+                }
+            }
+        }
+    };
+
+    /**
+     * Whether app hibernation is enabled on this device.
+     *
+     * @return true if enabled, false otherwise
+     */
+    public static boolean isAppHibernationEnabled() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE_APP_HIBERNATION,
+                AppHibernationConstants.KEY_APP_HIBERNATION_ENABLED,
+                false /* defaultValue */);
+    }
+
+    /**
+     * Dependency injector for {@link #AppHibernationService)}.
+     */
+    interface Injector {
+        Context getContext();
+
+        IPackageManager getPackageManager();
+
+        IActivityManager getActivityManager();
+
+        UserManager getUserManager();
+
+        HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore();
+
+        HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId);
+    }
+
+    private static final class InjectorImpl implements Injector {
+        private static final String HIBERNATION_DIR_NAME = "hibernation";
+        private final Context mContext;
+        private final ScheduledExecutorService mScheduledExecutorService;
+        private final UserLevelHibernationProto mUserLevelHibernationProto;
+
+        InjectorImpl(Context context) {
+            mContext = context;
+            mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
+            mUserLevelHibernationProto = new UserLevelHibernationProto();
+        }
+
+        @Override
+        public Context getContext() {
+            return mContext;
+        }
+
+        @Override
+        public IPackageManager getPackageManager() {
+            return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+        }
+
+        @Override
+        public IActivityManager getActivityManager() {
+            return ActivityManager.getService();
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mContext.getSystemService(UserManager.class);
+        }
+
+        @Override
+        public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
+            File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME);
+            return new HibernationStateDiskStore<>(
+                    dir, new GlobalLevelHibernationProto(), mScheduledExecutorService);
+        }
+
+        @Override
+        public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
+            File dir = new File(Environment.getDataSystemCeDirectory(userId), HIBERNATION_DIR_NAME);
+            return new HibernationStateDiskStore<>(
+                    dir, mUserLevelHibernationProto, mScheduledExecutorService);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java b/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java
new file mode 100644
index 0000000..7d6eea2
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 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.apphibernation;
+
+import android.os.ShellCommand;
+import android.os.UserHandle;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command implementation for {@link AppHibernationService}.
+ */
+final class AppHibernationShellCommand extends ShellCommand {
+    private static final String USER_OPT = "--user";
+    private static final String GLOBAL_OPT = "--global";
+    private static final int SUCCESS = 0;
+    private static final int ERROR = -1;
+    private final AppHibernationService mService;
+
+    AppHibernationShellCommand(AppHibernationService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        switch (cmd) {
+            case "set-state":
+                return runSetState();
+            case "get-state":
+                return runGetState();
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    private int runSetState() {
+        String opt;
+        boolean setsGlobal = false;
+        int userId = UserHandle.USER_CURRENT;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case USER_OPT:
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case GLOBAL_OPT:
+                    setsGlobal = true;
+                    break;
+                default:
+                    getErrPrintWriter().println("Error: Unknown option: " + opt);
+            }
+        }
+
+        String pkg = getNextArgRequired();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return ERROR;
+        }
+
+        String newStateRaw = getNextArgRequired();
+        if (newStateRaw == null) {
+            getErrPrintWriter().println("Error: No state to set specified");
+            return ERROR;
+        }
+        boolean newState = Boolean.parseBoolean(newStateRaw);
+
+        if (setsGlobal) {
+            mService.setHibernatingGlobally(pkg, newState);
+        } else {
+            mService.setHibernatingForUser(pkg, userId, newState);
+        }
+        return SUCCESS;
+    }
+
+    private int runGetState() {
+        String opt;
+        boolean requestsGlobal = false;
+        int userId = UserHandle.USER_CURRENT;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case USER_OPT:
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case GLOBAL_OPT:
+                    requestsGlobal = true;
+                    break;
+                default:
+                    getErrPrintWriter().println("Error: Unknown option: " + opt);
+            }
+        }
+
+        String pkg = getNextArgRequired();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: No package specified");
+            return ERROR;
+        }
+        boolean isHibernating = requestsGlobal
+                ? mService.isHibernatingGlobally(pkg) : mService.isHibernatingForUser(pkg, userId);
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println(isHibernating);
+        return SUCCESS;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("App hibernation (app_hibernation) commands: ");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("");
+        pw.println("  set-state [--user USER_ID] [--global] PACKAGE true|false");
+        pw.println("    Sets the hibernation state of the package to value specified. Optionally");
+        pw.println("    may specify a user id or set global hibernation state.");
+        pw.println("");
+        pw.println("  get-state [--user USER_ID] [--global] PACKAGE");
+        pw.println("    Gets the hibernation state of the package. Optionally may specify a user");
+        pw.println("    id or request global hibernation state.");
+        pw.println("");
+    }
+}
diff --git a/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java b/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java
new file mode 100644
index 0000000..79e995b
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.apphibernation;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads and writes protos for {@link GlobalLevelState} hiberation states.
+ */
+final class GlobalLevelHibernationProto implements ProtoReadWriter<List<GlobalLevelState>> {
+    private static final String TAG = "GlobalLevelHibernationProtoReadWriter";
+
+    @Override
+    public void writeToProto(@NonNull ProtoOutputStream stream,
+            @NonNull List<GlobalLevelState> data) {
+        for (int i = 0, size = data.size(); i < size; i++) {
+            long token = stream.start(GlobalLevelHibernationStatesProto.HIBERNATION_STATE);
+            GlobalLevelState state = data.get(i);
+            stream.write(GlobalLevelHibernationStateProto.PACKAGE_NAME, state.packageName);
+            stream.write(GlobalLevelHibernationStateProto.HIBERNATED, state.hibernated);
+            stream.end(token);
+        }
+    }
+
+    @Override
+    public @Nullable List<GlobalLevelState> readFromProto(@NonNull ProtoInputStream stream)
+            throws IOException {
+        List<GlobalLevelState> list = new ArrayList<>();
+        while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            if (stream.getFieldNumber()
+                    != (int) GlobalLevelHibernationStatesProto.HIBERNATION_STATE) {
+                continue;
+            }
+            GlobalLevelState state = new GlobalLevelState();
+            long token = stream.start(GlobalLevelHibernationStatesProto.HIBERNATION_STATE);
+            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                switch (stream.getFieldNumber()) {
+                    case (int) GlobalLevelHibernationStateProto.PACKAGE_NAME:
+                        state.packageName =
+                                stream.readString(GlobalLevelHibernationStateProto.PACKAGE_NAME);
+                        break;
+                    case (int) GlobalLevelHibernationStateProto.HIBERNATED:
+                        state.hibernated =
+                                stream.readBoolean(GlobalLevelHibernationStateProto.HIBERNATED);
+                        break;
+                    default:
+                        Slog.w(TAG, "Undefined field in proto: " + stream.getFieldNumber());
+                }
+            }
+            stream.end(token);
+            list.add(state);
+        }
+        return list;
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/services/core/java/com/android/server/apphibernation/GlobalLevelState.java
similarity index 74%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to services/core/java/com/android/server/apphibernation/GlobalLevelState.java
index 8b5b251..4f75675 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/services/core/java/com/android/server/apphibernation/GlobalLevelState.java
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+package com.android.server.apphibernation;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Data class that contains global hibernation state for a package.
  */
-@android.annotation.Hide
-package com.android.role;
+final class GlobalLevelState {
+    public String packageName;
+    public boolean hibernated;
+}
diff --git a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
new file mode 100644
index 0000000..c83659d
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2021 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.apphibernation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.text.format.DateUtils;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Disk store utility class for hibernation states.
+ *
+ * @param <T> the type of hibernation state data
+ */
+class HibernationStateDiskStore<T> {
+    private static final String TAG = "HibernationStateDiskStore";
+
+    // Time to wait before actually writing. Saves extra writes if data changes come in batches.
+    private static final long DISK_WRITE_DELAY = 1L * DateUtils.MINUTE_IN_MILLIS;
+    private static final String STATES_FILE_NAME = "states";
+
+    private final File mHibernationFile;
+    private final ScheduledExecutorService mExecutorService;
+    private final ProtoReadWriter<List<T>> mProtoReadWriter;
+    private List<T> mScheduledStatesToWrite = new ArrayList<>();
+    private ScheduledFuture<?> mFuture;
+
+    /**
+     * Initialize a disk store for hibernation states in the given directory.
+     *
+     * @param hibernationDir directory to write/read states file
+     * @param readWriter writer/reader of states proto
+     * @param executorService scheduled executor for writing data
+     */
+    HibernationStateDiskStore(@NonNull File hibernationDir,
+            @NonNull ProtoReadWriter<List<T>> readWriter,
+            @NonNull ScheduledExecutorService executorService) {
+        this(hibernationDir, readWriter, executorService, STATES_FILE_NAME);
+    }
+
+    @VisibleForTesting
+    HibernationStateDiskStore(@NonNull File hibernationDir,
+            @NonNull ProtoReadWriter<List<T>> readWriter,
+            @NonNull ScheduledExecutorService executorService,
+            @NonNull String fileName) {
+        mHibernationFile = new File(hibernationDir, fileName);
+        mExecutorService = executorService;
+        mProtoReadWriter = readWriter;
+    }
+
+    /**
+     * Schedule a full write of all the hibernation states to the file on disk. Does not run
+     * immediately and subsequent writes override previous ones.
+     *
+     * @param hibernationStates list of hibernation states to write to disk
+     */
+    void scheduleWriteHibernationStates(@NonNull List<T> hibernationStates) {
+        synchronized (this) {
+            mScheduledStatesToWrite = hibernationStates;
+            if (mExecutorService.isShutdown()) {
+                Slog.e(TAG, "Scheduled executor service is shut down.");
+                return;
+            }
+
+            // Already have write scheduled
+            if (mFuture != null) {
+                Slog.i(TAG, "Write already scheduled. Skipping schedule.");
+                return;
+            }
+
+            mFuture = mExecutorService.schedule(this::writeHibernationStates, DISK_WRITE_DELAY,
+                    TimeUnit.MILLISECONDS);
+        }
+    }
+
+    /**
+     * Read hibernation states from disk.
+     *
+     * @return the parsed list of hibernation states, null if file does not exist
+     */
+    @Nullable
+    List<T> readHibernationStates() {
+        synchronized (this) {
+            if (!mHibernationFile.exists()) {
+                Slog.i(TAG, "No hibernation file on disk for file " + mHibernationFile.getPath());
+                return null;
+            }
+            AtomicFile atomicFile = new AtomicFile(mHibernationFile);
+
+            try {
+                FileInputStream inputStream = atomicFile.openRead();
+                ProtoInputStream protoInputStream = new ProtoInputStream(inputStream);
+                return mProtoReadWriter.readFromProto(protoInputStream);
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to read states protobuf.", e);
+                return null;
+            }
+        }
+    }
+
+    @WorkerThread
+    private void writeHibernationStates() {
+        synchronized (this) {
+            writeStateProto(mScheduledStatesToWrite);
+            mScheduledStatesToWrite.clear();
+            mFuture = null;
+        }
+    }
+
+    @WorkerThread
+    private void writeStateProto(List<T> states) {
+        AtomicFile atomicFile = new AtomicFile(mHibernationFile);
+
+        FileOutputStream fileOutputStream;
+        try {
+            fileOutputStream = atomicFile.startWrite();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to start write to states protobuf.", e);
+            return;
+        }
+
+        try {
+            ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
+            mProtoReadWriter.writeToProto(protoOutputStream, states);
+            protoOutputStream.flush();
+            atomicFile.finishWrite(fileOutputStream);
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to finish write to states protobuf.", e);
+            atomicFile.failWrite(fileOutputStream);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java b/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java
new file mode 100644
index 0000000..0cbc09a
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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.apphibernation;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.IOException;
+
+/**
+ * Proto utility that reads and writes proto for some data.
+ *
+ * @param <T> data that can be written and read from a proto
+ */
+interface ProtoReadWriter<T> {
+
+    /**
+     * Write data to a proto stream
+     */
+    void writeToProto(@NonNull ProtoOutputStream stream, @NonNull T data);
+
+    /**
+     * Parse data from the proto stream and return
+     */
+    @Nullable T readFromProto(@NonNull ProtoInputStream stream) throws IOException;
+}
diff --git a/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java b/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java
new file mode 100644
index 0000000..a24c4c5
--- /dev/null
+++ b/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.apphibernation;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads and writes protos for {@link UserLevelState} hiberation states.
+ */
+final class UserLevelHibernationProto implements ProtoReadWriter<List<UserLevelState>> {
+    private static final String TAG = "UserLevelHibernationProtoReadWriter";
+
+    @Override
+    public void writeToProto(@NonNull ProtoOutputStream stream,
+            @NonNull List<UserLevelState> data) {
+        for (int i = 0, size = data.size(); i < size; i++) {
+            long token = stream.start(UserLevelHibernationStatesProto.HIBERNATION_STATE);
+            UserLevelState state = data.get(i);
+            stream.write(UserLevelHibernationStateProto.PACKAGE_NAME, state.packageName);
+            stream.write(UserLevelHibernationStateProto.HIBERNATED, state.hibernated);
+            stream.end(token);
+        }
+    }
+
+    @Override
+    public @Nullable List<UserLevelState> readFromProto(@NonNull ProtoInputStream stream)
+            throws IOException {
+        List<UserLevelState> list = new ArrayList<>();
+        while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            if (stream.getFieldNumber()
+                    != (int) UserLevelHibernationStatesProto.HIBERNATION_STATE) {
+                continue;
+            }
+            UserLevelState state = new UserLevelState();
+            long token = stream.start(UserLevelHibernationStatesProto.HIBERNATION_STATE);
+            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                switch (stream.getFieldNumber()) {
+                    case (int) UserLevelHibernationStateProto.PACKAGE_NAME:
+                        state.packageName =
+                                stream.readString(UserLevelHibernationStateProto.PACKAGE_NAME);
+                        break;
+                    case (int) UserLevelHibernationStateProto.HIBERNATED:
+                        state.hibernated =
+                                stream.readBoolean(UserLevelHibernationStateProto.HIBERNATED);
+                        break;
+                    default:
+                        Slog.w(TAG, "Undefined field in proto: " + stream.getFieldNumber());
+                }
+            }
+            stream.end(token);
+            list.add(state);
+        }
+        return list;
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/services/core/java/com/android/server/apphibernation/UserLevelState.java
similarity index 74%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to services/core/java/com/android/server/apphibernation/UserLevelState.java
index 8b5b251..c66dad8 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/services/core/java/com/android/server/apphibernation/UserLevelState.java
@@ -14,9 +14,12 @@
  * limitations under the License.
  */
 
+package com.android.server.apphibernation;
+
 /**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
+ * Data class that contains hibernation state info of a package for a user.
  */
-@android.annotation.Hide
-package com.android.role;
+final class UserLevelState {
+    public String packageName;
+    public boolean hibernated;
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index f07da8f..10fe1e1 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2796,6 +2796,8 @@
         if (callback == null) {
             return;
         }
+        final boolean mayWatchPackageName =
+                packageName != null && !filterAppAccessUnlocked(packageName);
         synchronized (this) {
             int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
 
@@ -2824,7 +2826,7 @@
                 }
                 cbs.add(cb);
             }
-            if (packageName != null) {
+            if (mayWatchPackageName) {
                 ArraySet<ModeCallback> cbs = mPackageModeWatchers.get(packageName);
                 if (cbs == null) {
                     cbs = new ArraySet<>();
@@ -3008,13 +3010,27 @@
         Objects.requireNonNull(packageName);
         try {
             verifyAndGetBypass(uid, packageName, null);
-
+            if (filterAppAccessUnlocked(packageName)) {
+                return AppOpsManager.MODE_ERRORED;
+            }
             return AppOpsManager.MODE_ALLOWED;
         } catch (SecurityException ignored) {
             return AppOpsManager.MODE_ERRORED;
         }
     }
 
+    /**
+     * This method will check with PackageManager to determine if the package provided should
+     * be visible to the {@link Binder#getCallingUid()}.
+     *
+     * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+     */
+    private boolean filterAppAccessUnlocked(String packageName) {
+        final int callingUid = Binder.getCallingUid();
+        return LocalServices.getService(PackageManagerInternal.class)
+                .filterAppAccess(packageName, callingUid, UserHandle.getUserId(callingUid));
+    }
+
     @Override
     public int noteProxyOperation(int code, int proxiedUid, String proxiedPackageName,
             String proxiedAttributionTag, int proxyUid, String proxyPackageName,
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 9aea7c4..f72fb1f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -765,10 +765,6 @@
         return mAudioService.getVssVolumeForDevice(streamType, device);
     }
 
-    /*package*/ int getModeOwnerPid() {
-        return mModeOwnerPid;
-    }
-
     /*package*/ int getDeviceForStream(int streamType) {
         return mAudioService.getDeviceForStream(streamType);
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d575963..c1c0c99 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -95,6 +95,7 @@
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.media.MediaMetrics;
+import android.media.MediaRecorder.AudioSource;
 import android.media.PlayerBase;
 import android.media.VolumePolicy;
 import android.media.audiofx.AudioEffect;
@@ -161,9 +162,11 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -300,6 +303,10 @@
     private static final int MSG_STREAM_DEVICES_CHANGED = 32;
     private static final int MSG_UPDATE_VOLUME_STATES_FOR_DEVICE = 33;
     private static final int MSG_REINIT_VOLUMES = 34;
+    private static final int MSG_UPDATE_A11Y_SERVICE_UIDS = 35;
+    private static final int MSG_UPDATE_AUDIO_MODE = 36;
+    private static final int MSG_RECORDING_CONFIG_CHANGE = 37;
+
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -699,8 +706,9 @@
     private long mLoweredFromNormalToVibrateTime;
 
     // Array of Uids of valid accessibility services to check if caller is one of them
-    private int[] mAccessibilityServiceUids;
     private final Object mAccessibilityServiceUidsLock = new Object();
+    @GuardedBy("mAccessibilityServiceUidsLock")
+    private int[] mAccessibilityServiceUids;
 
     // Uid of the active input method service to check if caller is the one or not.
     private int mInputMethodServiceUid = android.os.Process.INVALID_UID;
@@ -939,6 +947,7 @@
         mDeviceBroker = new AudioDeviceBroker(mContext, this);
 
         mRecordMonitor = new RecordingActivityMonitor(mContext);
+        mRecordMonitor.registerRecordingCallback(mVoiceRecordingActivityMonitor, true);
 
         // must be called before readPersistedSettings() which needs a valid mStreamVolumeAlias[]
         // array initialized by updateStreamVolumeAlias()
@@ -948,6 +957,8 @@
 
         mPlaybackMonitor =
                 new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+        mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
+
         mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
 
         readAndSetLowRamDevice();
@@ -1076,7 +1087,6 @@
 
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
             synchronized (mHdmiClientLock) {
-                mHdmiCecSink = false;
                 mHdmiManager = mContext.getSystemService(HdmiControlManager.class);
                 if (mHdmiManager != null) {
                     mHdmiManager.addHdmiControlStatusChangeListener(
@@ -1197,12 +1207,8 @@
 
         // Restore call state
         synchronized (mDeviceBroker.mSetModeLock) {
-            if (mAudioSystem.setPhoneState(mMode, getModeOwnerUid())
-                    ==  AudioSystem.AUDIO_STATUS_OK) {
-                mModeLogger.log(new AudioEventLogger.StringEvent(
-                        "onAudioServerDied causes setPhoneState(" + AudioSystem.modeToString(mMode)
-                        + ", uid=" + getModeOwnerUid() + ")"));
-            }
+            onUpdateAudioMode(AudioSystem.MODE_CURRENT, android.os.Process.myPid(),
+                    mContext.getPackageName());
         }
         final int forSys;
         synchronized (mSettingsLock) {
@@ -1508,7 +1514,8 @@
             if (isPlatformTelevision()) {
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null && mHdmiPlaybackClient != null) {
-                        updateHdmiCecSinkLocked(mHdmiCecSink | false);
+                        updateHdmiCecSinkLocked(
+                                mFullVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI));
                     }
                 }
             }
@@ -1518,7 +1525,8 @@
             if (isPlatformTelevision()) {
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null) {
-                        updateHdmiCecSinkLocked(mHdmiCecSink | false);
+                        updateHdmiCecSinkLocked(
+                                mFullVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI));
                     }
                 }
             }
@@ -2310,11 +2318,9 @@
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
             String callingPackage, String caller) {
-        boolean hasModifyAudioSettings =
-                mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
-                == PackageManager.PERMISSION_GRANTED;
         adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
-                caller, Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL);
+                caller, Binder.getCallingUid(), callingHasAudioSettingsPermission(),
+                VOL_ADJUST_NORMAL);
     }
 
     public void setFastScrollSoundEffectsEnabled(boolean enabled) {
@@ -2441,13 +2447,12 @@
                     + "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
             return;
         }
-        final boolean hasModifyAudioSettings =
-                mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
-                        == PackageManager.PERMISSION_GRANTED;
+
         sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
                 direction/*val1*/, flags/*val2*/, callingPackage));
         adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
-                Binder.getCallingUid(), hasModifyAudioSettings, VOL_ADJUST_NORMAL);
+                Binder.getCallingUid(), callingHasAudioSettingsPermission(),
+                VOL_ADJUST_NORMAL);
     }
 
     protected void adjustStreamVolume(int streamType, int direction, int flags,
@@ -2670,8 +2675,7 @@
         if (adjustVolume) {
             synchronized (mHdmiClientLock) {
                 if (mHdmiManager != null) {
-                    // mHdmiCecSink true => mHdmiPlaybackClient != null
-                    if (mHdmiCecSink
+                    if (mHdmiPlaybackClient != null
                             && mHdmiCecVolumeControlEnabled
                             && streamTypeAlias == AudioSystem.STREAM_MUSIC
                             // vol change on a full volume device
@@ -2966,13 +2970,11 @@
                     + " MODIFY_AUDIO_ROUTING  callingPackage=" + callingPackage);
             return;
         }
-        final boolean hasModifyAudioSettings =
-                mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
-                        == PackageManager.PERMISSION_GRANTED;
+
         sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
                 index/*val1*/, flags/*val2*/, callingPackage));
         setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
-                Binder.getCallingUid(), hasModifyAudioSettings);
+                Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
     }
 
     private boolean canChangeAccessibilityVolume() {
@@ -2994,7 +2996,7 @@
     }
 
     /*package*/ int getHearingAidStreamType() {
-        return getHearingAidStreamType(mMode);
+        return getHearingAidStreamType(getMode());
     }
 
     private int getHearingAidStreamType(int mode) {
@@ -3007,15 +3009,15 @@
                 // other conditions will influence the stream type choice, read on...
                 break;
         }
-        if (mVoiceActive.get()) {
+        if (mVoicePlaybackActive.get()) {
             return AudioSystem.STREAM_VOICE_CALL;
         }
         return AudioSystem.STREAM_MUSIC;
     }
 
-    private AtomicBoolean mVoiceActive = new AtomicBoolean(false);
+    private AtomicBoolean mVoicePlaybackActive = new AtomicBoolean(false);
 
-    private final IPlaybackConfigDispatcher mVoiceActivityMonitor =
+    private final IPlaybackConfigDispatcher mVoicePlaybackActivityMonitor =
             new IPlaybackConfigDispatcher.Stub() {
         @Override
         public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
@@ -3037,16 +3039,126 @@
                 break;
             }
         }
-        if (mVoiceActive.getAndSet(voiceActive) != voiceActive) {
+        if (mVoicePlaybackActive.getAndSet(voiceActive) != voiceActive) {
             updateHearingAidVolumeOnVoiceActivityUpdate();
         }
+
+        // Update playback active state for all apps in audio mode stack.
+        // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
+        // and request an audio mode update immediately. Upon any other change, queue the message
+        // and request an audio mode update after a grace period.
+        synchronized (mDeviceBroker.mSetModeLock) {
+            boolean updateAudioMode = false;
+            int existingMsgPolicy = SENDMSG_QUEUE;
+            int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
+            for (SetModeDeathHandler h : mSetModeDeathHandlers) {
+                boolean wasActive = h.isActive();
+                h.setPlaybackActive(false);
+                for (AudioPlaybackConfiguration config : configs) {
+                    final int usage = config.getAudioAttributes().getUsage();
+                    if (config.getClientUid() == h.getUid()
+                            && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
+                                || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
+                            && config.getPlayerState()
+                                == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                        h.setPlaybackActive(true);
+                        break;
+                    }
+                }
+                if (wasActive != h.isActive()) {
+                    updateAudioMode = true;
+                    if (h.isActive() && h == getAudioModeOwnerHandler()) {
+                        existingMsgPolicy = SENDMSG_REPLACE;
+                        delay = 0;
+                    }
+                }
+            }
+            if (updateAudioMode) {
+                sendMsg(mAudioHandler,
+                        MSG_UPDATE_AUDIO_MODE,
+                        existingMsgPolicy,
+                        AudioSystem.MODE_CURRENT,
+                        android.os.Process.myPid(),
+                        mContext.getPackageName(),
+                        delay);
+            }
+        }
+    }
+
+    private final IRecordingConfigDispatcher mVoiceRecordingActivityMonitor =
+            new IRecordingConfigDispatcher.Stub() {
+        @Override
+        public void dispatchRecordingConfigChange(List<AudioRecordingConfiguration> configs) {
+            sendMsg(mAudioHandler, MSG_RECORDING_CONFIG_CHANGE, SENDMSG_REPLACE,
+                    0 /*arg1 ignored*/, 0 /*arg2 ignored*/,
+                    configs /*obj*/, 0 /*delay*/);
+        }
+    };
+
+    private void onRecordingConfigChange(List<AudioRecordingConfiguration> configs) {
+        // Update recording active state for all apps in audio mode stack.
+        // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
+        // and request an audio mode update immediately. Upon any other change, queue the message
+        // and request an audio mode update after a grace period.
+        synchronized (mDeviceBroker.mSetModeLock) {
+            boolean updateAudioMode = false;
+            int existingMsgPolicy = SENDMSG_QUEUE;
+            int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
+            for (SetModeDeathHandler h : mSetModeDeathHandlers) {
+                boolean wasActive = h.isActive();
+                h.setRecordingActive(false);
+                for (AudioRecordingConfiguration config : configs) {
+                    if (config.getClientUid() == h.getUid()
+                            && config.getAudioSource() == AudioSource.VOICE_COMMUNICATION) {
+                        h.setRecordingActive(true);
+                        break;
+                    }
+                }
+                if (wasActive != h.isActive()) {
+                    updateAudioMode = true;
+                    if (h.isActive() && h == getAudioModeOwnerHandler()) {
+                        existingMsgPolicy = SENDMSG_REPLACE;
+                        delay = 0;
+                    }
+                }
+            }
+            if (updateAudioMode) {
+                sendMsg(mAudioHandler,
+                        MSG_UPDATE_AUDIO_MODE,
+                        existingMsgPolicy,
+                        AudioSystem.MODE_CURRENT,
+                        android.os.Process.myPid(),
+                        mContext.getPackageName(),
+                        delay);
+            }
+        }
+    }
+
+    private void dumpAudioMode(PrintWriter pw) {
+        pw.println("\nAudio mode: ");
+        pw.println("- Current mode = " + AudioSystem.modeToString(getMode()));
+        pw.println("- Mode owner: ");
+        SetModeDeathHandler hdlr = getAudioModeOwnerHandler();
+        if (hdlr != null) {
+            hdlr.dump(pw, -1);
+        } else {
+            pw.println("   None");
+        }
+        pw.println("- Mode owner stack: ");
+        if (mSetModeDeathHandlers.isEmpty()) {
+            pw.println("   Empty");
+        } else {
+            for (int i = 0; i < mSetModeDeathHandlers.size(); i++) {
+                mSetModeDeathHandlers.get(i).dump(pw, i);
+            }
+        }
     }
 
     private void updateHearingAidVolumeOnVoiceActivityUpdate() {
         final int streamType = getHearingAidStreamType();
         final int index = getStreamVolume(streamType);
         sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID,
-                mVoiceActive.get(), streamType, index));
+                mVoicePlaybackActive.get(), streamType, index));
         mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
 
     }
@@ -3639,8 +3751,7 @@
         ensureValidStreamType(streamType);
         final boolean isPrivileged =
                 Binder.getCallingUid() == Process.SYSTEM_UID
-                 || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
-                        == PackageManager.PERMISSION_GRANTED)
+                 || callingHasAudioSettingsPermission()
                  || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
                         == PackageManager.PERMISSION_GRANTED);
         return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10;
@@ -4064,65 +4175,46 @@
 
     }
 
-    /**
-     * Return the pid of the current audio mode owner
-     * @return 0 if nobody owns the mode
-     */
-    /*package*/ int getModeOwnerPid() {
-        int modeOwnerPid = 0;
-        try {
-            modeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
-        } catch (Exception e) {
-            // nothing to do, modeOwnerPid is not modified
-        }
-        return modeOwnerPid;
-    }
-
-    /**
-     * Return the uid of the current audio mode owner
-     * @return 0 if nobody owns the mode
-     */
-    /*package*/ int getModeOwnerUid() {
-        int modeOwnerUid = 0;
-        try {
-            modeOwnerUid = mSetModeDeathHandlers.get(0).getUid();
-        } catch (Exception e) {
-            // nothing to do, modeOwnerUid is not modified
-        }
-        return modeOwnerUid;
-    }
-
     private class SetModeDeathHandler implements IBinder.DeathRecipient {
         private final IBinder mCb; // To be notified of client's death
         private final int mPid;
         private final int mUid;
         private final boolean mIsPrivileged;
         private final String mPackage;
-        private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
+        private int mMode;
+        private long mUpdateTime;
+        private boolean mPlaybackActive = false;
+        private boolean mRecordingActive = false;
 
-        SetModeDeathHandler(IBinder cb, int pid, int uid, boolean isPrivileged, String caller) {
+        SetModeDeathHandler(IBinder cb, int pid, int uid, boolean isPrivileged,
+                            String caller, int mode) {
+            mMode = mode;
             mCb = cb;
             mPid = pid;
             mUid = uid;
-            mIsPrivileged = isPrivileged;
             mPackage = caller;
+            mIsPrivileged = isPrivileged;
+            mUpdateTime = java.lang.System.currentTimeMillis();
         }
 
         public void binderDied() {
-            int newModeOwnerPid = 0;
             synchronized (mDeviceBroker.mSetModeLock) {
-                Log.w(TAG, "setMode() client died");
+                Log.w(TAG, "SetModeDeathHandler client died");
                 int index = mSetModeDeathHandlers.indexOf(this);
                 if (index < 0) {
-                    Log.w(TAG, "unregistered setMode() client died");
+                    Log.w(TAG, "unregistered SetModeDeathHandler client died");
                 } else {
-                    newModeOwnerPid = setModeInt(
-                            AudioSystem.MODE_NORMAL, mCb, mPid, mUid, mIsPrivileged, TAG);
+                    SetModeDeathHandler h = mSetModeDeathHandlers.get(index);
+                    mSetModeDeathHandlers.remove(index);
+                    sendMsg(mAudioHandler,
+                            MSG_UPDATE_AUDIO_MODE,
+                            SENDMSG_QUEUE,
+                            AudioSystem.MODE_CURRENT,
+                            android.os.Process.myPid(),
+                            mContext.getPackageName(),
+                            0);
                 }
             }
-            // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
-            // SCO connections not started by the application changing the mode when pid changes
-            mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid, AudioService.this.getMode());
         }
 
         public int getPid() {
@@ -4131,6 +4223,7 @@
 
         public void setMode(int mode) {
             mMode = mode;
+            mUpdateTime = java.lang.System.currentTimeMillis();
         }
 
         public int getMode() {
@@ -4152,25 +4245,131 @@
         public boolean isPrivileged() {
             return mIsPrivileged;
         }
+
+        public long getUpdateTime() {
+            return mUpdateTime;
+        }
+
+        public void setPlaybackActive(boolean active) {
+            mPlaybackActive = active;
+        }
+
+        public void setRecordingActive(boolean active) {
+            mRecordingActive = active;
+        }
+
+        /**
+         * An app is considered active if:
+         * - It is privileged (has MODIFY_PHONE_STATE permission)
+         *  or
+         * - It requests mode MODE_IN_COMMUNICATION, and it is either playing
+         * or recording for VOICE_COMMUNICATION.
+         *   or
+         * - It requests a mode different from MODE_IN_COMMUNICATION or MODE_NORMAL
+         */
+        public boolean isActive() {
+            return mIsPrivileged
+                    || ((mMode == AudioSystem.MODE_IN_COMMUNICATION)
+                        && (mRecordingActive || mPlaybackActive))
+                    || mMode == AudioSystem.MODE_IN_CALL
+                    || mMode == AudioSystem.MODE_RINGTONE
+                    || mMode == AudioSystem.MODE_CALL_SCREENING;
+        }
+
+        public void dump(PrintWriter pw, int index) {
+            SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
+
+            if (index >= 0) {
+                pw.println("  Requester # " + (index + 1) + ":");
+            }
+            pw.println("  - Mode: " + AudioSystem.modeToString(mMode));
+            pw.println("  - Binder: " + mCb);
+            pw.println("  - Pid: " + mPid);
+            pw.println("  - Uid: " + mUid);
+            pw.println("  - Package: " + mPackage);
+            pw.println("  - Privileged: " + mIsPrivileged);
+            pw.println("  - Active: " + isActive());
+            pw.println("    Playback active: " + mPlaybackActive);
+            pw.println("    Recording active: " + mRecordingActive);
+            pw.println("  - update time: " + format.format(new Date(mUpdateTime)));
+        }
+    }
+
+    @GuardedBy("mDeviceBroker.mSetModeLock")
+    private SetModeDeathHandler getAudioModeOwnerHandler() {
+        // The Audio mode owner is:
+        // 1) the most recent privileged app in the stack
+        // 2) the most recent active app in the tack
+        SetModeDeathHandler modeOwner = null;
+        SetModeDeathHandler privilegedModeOwner = null;
+        for (SetModeDeathHandler h : mSetModeDeathHandlers) {
+            if (h.isActive()) {
+                // privileged apps are always active
+                if (h.isPrivileged()) {
+                    if (privilegedModeOwner == null
+                            || h.getUpdateTime() > privilegedModeOwner.getUpdateTime()) {
+                        privilegedModeOwner = h;
+                    }
+                } else {
+                    if (modeOwner == null
+                            || h.getUpdateTime() > modeOwner.getUpdateTime()) {
+                        modeOwner = h;
+                    }
+                }
+            }
+        }
+        return privilegedModeOwner != null ? privilegedModeOwner :  modeOwner;
+    }
+
+    /**
+     * Return the pid of the current audio mode owner
+     * @return 0 if nobody owns the mode
+     */
+    @GuardedBy("mDeviceBroker.mSetModeLock")
+    /*package*/ int getModeOwnerPid() {
+        SetModeDeathHandler hdlr = getAudioModeOwnerHandler();
+        if (hdlr != null) {
+            return hdlr.getPid();
+        }
+        return 0;
+    }
+
+    /**
+     * Return the uid of the current audio mode owner
+     * @return 0 if nobody owns the mode
+     */
+    @GuardedBy("mDeviceBroker.mSetModeLock")
+    /*package*/ int getModeOwnerUid() {
+        SetModeDeathHandler hdlr = getAudioModeOwnerHandler();
+        if (hdlr != null) {
+            return hdlr.getUid();
+        }
+        return 0;
     }
 
     /** @see AudioManager#setMode(int) */
     public void setMode(int mode, IBinder cb, String callingPackage) {
+        int pid = Binder.getCallingPid();
+        int uid = Binder.getCallingUid();
         if (DEBUG_MODE) {
-            Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")");
+            Log.v(TAG, "setMode(mode=" + mode + ", pid=" + pid
+                    + ", uid=" + uid + ", caller=" + callingPackage + ")");
         }
         if (!checkAudioSettingsPermission("setMode()")) {
             return;
         }
-        final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission(
-                        android.Manifest.permission.MODIFY_PHONE_STATE)
-                        == PackageManager.PERMISSION_GRANTED;
-        final int callingPid = Binder.getCallingPid();
-        if ((mode == AudioSystem.MODE_IN_CALL) && !hasModifyPhoneStatePermission) {
-            Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid="
-                    + callingPid + ", uid=" + Binder.getCallingUid());
+        if (cb == null) {
+            Log.e(TAG, "setMode() called with null binder");
             return;
         }
+        if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) {
+            Log.w(TAG, "setMode() invalid mode: " + mode);
+            return;
+        }
+
+        if (mode == AudioSystem.MODE_CURRENT) {
+            mode = getMode();
+        }
 
         if (mode == AudioSystem.MODE_CALL_SCREENING && !mIsCallScreeningModeSupported) {
             Log.w(TAG, "setMode(MODE_CALL_SCREENING) not permitted "
@@ -4178,171 +4377,152 @@
             return;
         }
 
-        if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) {
+        final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED;
+        if ((mode == AudioSystem.MODE_IN_CALL) && !hasModifyPhoneStatePermission) {
+            Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid="
+                    + pid + ", uid=" + Binder.getCallingUid());
             return;
         }
 
-        int newModeOwnerPid;
+        SetModeDeathHandler currentModeHandler = null;
         synchronized (mDeviceBroker.mSetModeLock) {
-            if (mode == AudioSystem.MODE_CURRENT) {
-                mode = mMode;
+            for (SetModeDeathHandler h : mSetModeDeathHandlers) {
+                if (h.getPid() == pid) {
+                    currentModeHandler = h;
+                    break;
+                }
             }
-            int oldModeOwnerPid = getModeOwnerPid();
-            // Do not allow changing mode if a call is active and the requester
-            // does not have permission to modify phone state or is not the mode owner,
-            // unless returning to NORMAL mode (will not change current mode owner) or
-            // not changing mode in which case the mode owner will reflect the last
-            // requester of current mode
-            if (!((mode == mMode) || (mode == AudioSystem.MODE_NORMAL))
-                    && ((mMode == AudioSystem.MODE_IN_CALL)
-                        || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
-                    && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) {
-                Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid
-                        + ", uid=" + Binder.getCallingUid()
-                        + ", cannot change mode from " + mMode
-                        + " without permission or being mode owner");
-                return;
+
+            if (mode == AudioSystem.MODE_NORMAL) {
+                if (currentModeHandler != null) {
+                    if (!currentModeHandler.isPrivileged()
+                            && currentModeHandler.getMode() == AudioSystem.MODE_IN_COMMUNICATION) {
+                        mAudioHandler.removeEqualMessages(
+                                MSG_CHECK_MODE_FOR_UID, currentModeHandler);
+                    }
+                    mSetModeDeathHandlers.remove(currentModeHandler);
+                    if (DEBUG_MODE) {
+                        Log.v(TAG, "setMode(" + mode + ") removing hldr for pid: " + pid);
+                    }
+                    try {
+                        currentModeHandler.getBinder().unlinkToDeath(currentModeHandler, 0);
+                    } catch (NoSuchElementException e) {
+                        Log.w(TAG, "setMode link does not exist ...");
+                    }
+                }
+            } else {
+                if (currentModeHandler != null) {
+                    currentModeHandler.setMode(mode);
+                    if (DEBUG_MODE) {
+                        Log.v(TAG, "setMode(" + mode + ") updating hldr for pid: " + pid);
+                    }
+                } else {
+                    currentModeHandler = new SetModeDeathHandler(cb, pid, uid,
+                            hasModifyPhoneStatePermission, callingPackage, mode);
+                    // Register for client death notification
+                    try {
+                        cb.linkToDeath(currentModeHandler, 0);
+                    } catch (RemoteException e) {
+                        // Client has died!
+                        Log.w(TAG, "setMode() could not link to " + cb + " binder death");
+                        return;
+                    }
+                    mSetModeDeathHandlers.add(currentModeHandler);
+                    if (DEBUG_MODE) {
+                        Log.v(TAG, "setMode(" + mode + ") adding handler for pid=" + pid);
+                    }
+                }
+                if (mode == AudioSystem.MODE_IN_COMMUNICATION) {
+                    // Force active state when entering/updating the stack to avoid glitches when
+                    // an app starts playing/recording after settng the audio mode,
+                    // and send a reminder to check activity after a grace period.
+                    if (!currentModeHandler.isPrivileged()) {
+                        currentModeHandler.setPlaybackActive(true);
+                        currentModeHandler.setRecordingActive(true);
+                        sendMsg(mAudioHandler,
+                                MSG_CHECK_MODE_FOR_UID,
+                                SENDMSG_QUEUE,
+                                0,
+                                0,
+                                currentModeHandler,
+                                CHECK_MODE_FOR_UID_PERIOD_MS);
+                    }
+                }
             }
-            newModeOwnerPid = setModeInt(mode, cb, callingPid, Binder.getCallingUid(),
-                    hasModifyPhoneStatePermission, callingPackage);
+
+            sendMsg(mAudioHandler,
+                    MSG_UPDATE_AUDIO_MODE,
+                    SENDMSG_REPLACE,
+                    mode,
+                    pid,
+                    callingPackage,
+                    0);
         }
-        // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
-        // SCO connections not started by the application changing the mode when pid changes
-        mDeviceBroker.postSetModeOwnerPid(newModeOwnerPid, getMode());
     }
 
-    // setModeInt() returns a valid PID if the audio mode was successfully set to
-    // any mode other than NORMAL.
     @GuardedBy("mDeviceBroker.mSetModeLock")
-    private int setModeInt(
-            int mode, IBinder cb, int pid, int uid, boolean isPrivileged, String caller) {
+    void onUpdateAudioMode(int requestedMode, int requesterPid, String requesterPackage) {
+        if (requestedMode == AudioSystem.MODE_CURRENT) {
+            requestedMode = getMode();
+        }
+        int mode = AudioSystem.MODE_NORMAL;
+        int uid = 0;
+        int pid = 0;
+        SetModeDeathHandler currentModeHandler = getAudioModeOwnerHandler();
+        if (currentModeHandler != null) {
+            mode = currentModeHandler.getMode();
+            uid = currentModeHandler.getUid();
+            pid = currentModeHandler.getPid();
+        }
         if (DEBUG_MODE) {
-            Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid
-                    + ", uid=" + uid + ", caller=" + caller + ")");
+            Log.v(TAG, "onUpdateAudioMode() mode: " + mode + ", mMode: " + mMode
+                    + " requestedMode: " + requestedMode);
         }
-        int newModeOwnerPid = 0;
-        if (cb == null) {
-            Log.e(TAG, "setModeInt() called with null binder");
-            return newModeOwnerPid;
-        }
+        if (mode != mMode) {
+            final long identity = Binder.clearCallingIdentity();
+            int status = mAudioSystem.setPhoneState(mode, uid);
+            Binder.restoreCallingIdentity(identity);
+            if (status == AudioSystem.AUDIO_STATUS_OK) {
+                if (DEBUG_MODE) {
+                    Log.v(TAG, "onUpdateAudioMode: mode successfully set to " + mode);
+                }
+                int previousMode = mMode;
+                mMode = mode;
+                // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
+                mModeLogger.log(new PhoneStateEvent(requesterPackage, requesterPid,
+                        requestedMode, pid, mode));
 
-        SetModeDeathHandler hdlr = null;
-        Iterator iter = mSetModeDeathHandlers.iterator();
-        while (iter.hasNext()) {
-            SetModeDeathHandler h = (SetModeDeathHandler)iter.next();
-            if (h.getPid() == pid) {
-                hdlr = h;
-                // Remove from client list so that it is re-inserted at top of list
-                iter.remove();
-                if (hdlr.getMode() == AudioSystem.MODE_IN_COMMUNICATION) {
-                    mAudioHandler.removeEqualMessages(MSG_CHECK_MODE_FOR_UID, hdlr);
-                }
-                try {
-                    hdlr.getBinder().unlinkToDeath(hdlr, 0);
-                    if (cb != hdlr.getBinder()){
-                        hdlr = null;
-                    }
-                } catch (NoSuchElementException e) {
-                    hdlr = null;
-                    Log.w(TAG, "link does not exist ...");
-                }
-                break;
-            }
-        }
-        final int oldMode = mMode;
-        int status = AudioSystem.AUDIO_STATUS_OK;
-        int actualMode;
-        do {
-            actualMode = mode;
-            if (mode == AudioSystem.MODE_NORMAL) {
-                // get new mode from client at top the list if any
-                if (!mSetModeDeathHandlers.isEmpty()) {
-                    hdlr = mSetModeDeathHandlers.get(0);
-                    cb = hdlr.getBinder();
-                    actualMode = hdlr.getMode();
-                    if (DEBUG_MODE) {
-                        Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid="
-                                + hdlr.mPid);
-                    }
-                }
+                int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
+                int device = getDeviceForStream(streamType);
+                int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
+                setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true,
+                        requesterPackage, true /*hasModifyAudioSettings*/);
+
+                updateStreamVolumeAlias(true /*updateVolumes*/, requesterPackage);
+
+                // change of mode may require volume to be re-applied on some devices
+                updateAbsVolumeMultiModeDevices(previousMode, mode);
+
+                // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
+                // connections not started by the application changing the mode when pid changes
+                mDeviceBroker.postSetModeOwnerPid(pid, mode);
             } else {
-                if (hdlr == null) {
-                    hdlr = new SetModeDeathHandler(cb, pid, uid, isPrivileged, caller);
-                }
-                // Register for client death notification
-                try {
-                    cb.linkToDeath(hdlr, 0);
-                } catch (RemoteException e) {
-                    // Client has died!
-                    Log.w(TAG, "setMode() could not link to "+cb+" binder death");
-                }
-
-                // Last client to call setMode() is always at top of client list
-                // as required by SetModeDeathHandler.binderDied()
-                mSetModeDeathHandlers.add(0, hdlr);
-                hdlr.setMode(mode);
-            }
-
-            if (actualMode != mMode) {
-                final long identity = Binder.clearCallingIdentity();
-                status = mAudioSystem.setPhoneState(actualMode, getModeOwnerUid());
-                Binder.restoreCallingIdentity(identity);
-                if (status == AudioSystem.AUDIO_STATUS_OK) {
-                    if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); }
-                    mMode = actualMode;
-                } else {
-                    if (hdlr != null) {
-                        mSetModeDeathHandlers.remove(hdlr);
-                        cb.unlinkToDeath(hdlr, 0);
-                    }
-                    // force reading new top of mSetModeDeathHandlers stack
-                    if (DEBUG_MODE) { Log.w(TAG, " mode set to MODE_NORMAL after phoneState pb"); }
-                    mode = AudioSystem.MODE_NORMAL;
-                }
-            } else {
-                status = AudioSystem.AUDIO_STATUS_OK;
-            }
-        } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty());
-
-        if (status == AudioSystem.AUDIO_STATUS_OK) {
-            if (actualMode != AudioSystem.MODE_NORMAL) {
-                newModeOwnerPid = getModeOwnerPid();
-                if (newModeOwnerPid == 0) {
-                    Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack");
-                }
-            }
-            // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
-            mModeLogger.log(
-                    new PhoneStateEvent(caller, pid, mode, newModeOwnerPid, actualMode));
-
-            int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
-            int device = getDeviceForStream(streamType);
-            int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
-            setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller,
-                    true /*hasModifyAudioSettings*/);
-
-            updateStreamVolumeAlias(true /*updateVolumes*/, caller);
-
-            // change of mode may require volume to be re-applied on some devices
-            updateAbsVolumeMultiModeDevices(oldMode, actualMode);
-
-            if (actualMode == AudioSystem.MODE_IN_COMMUNICATION
-                    && !hdlr.isPrivileged()) {
-                sendMsg(mAudioHandler,
-                        MSG_CHECK_MODE_FOR_UID,
-                        SENDMSG_QUEUE,
-                        0,
-                        0,
-                        hdlr,
-                        CHECK_MODE_FOR_UID_PERIOD_MS);
+                Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
             }
         }
-        return newModeOwnerPid;
     }
 
     /** @see AudioManager#getMode() */
     public int getMode() {
-        return mMode;
+        synchronized (mDeviceBroker.mSetModeLock) {
+            SetModeDeathHandler currentModeHandler = getAudioModeOwnerHandler();
+            if (currentModeHandler != null) {
+                return currentModeHandler.getMode();
+            }
+            return AudioSystem.MODE_NORMAL;
+        }
     }
 
     /** cached value read from audiopolicy manager after initialization. */
@@ -4383,13 +4563,10 @@
             throw new SecurityException("Should only be called from system process");
         }
 
-        final boolean hasModifyAudioSettings =
-                mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
-                        == PackageManager.PERMISSION_GRANTED;
         // direction and stream type swap here because the public
         // adjustSuggested has a different order than the other methods.
         adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName, uid,
-                hasModifyAudioSettings, VOL_ADJUST_NORMAL);
+                hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
     }
 
     /** @see AudioManager#adjustStreamVolumeForUid(int, int, int, String, int, int, int) */
@@ -4407,11 +4584,9 @@
                     new StringBuilder(packageName).append(" uid:").append(uid)
                     .toString()));
         }
-        final boolean hasModifyAudioSettings =
-                mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
-                        == PackageManager.PERMISSION_GRANTED;
+
         adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid,
-                hasModifyAudioSettings, VOL_ADJUST_NORMAL);
+                hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
     }
 
     /** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */
@@ -4423,11 +4598,8 @@
             throw new SecurityException("Should only be called from system process");
         }
 
-        final boolean hasModifyAudioSettings =
-                mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
-                        == PackageManager.PERMISSION_GRANTED;
         setStreamVolume(streamType, index, flags, packageName, packageName, uid,
-                hasModifyAudioSettings);
+                hasAudioSettingsPermission(uid, pid));
     }
 
     //==========================================================================================
@@ -5393,8 +5565,7 @@
     }
 
     boolean checkAudioSettingsPermission(String method) {
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)
-                == PackageManager.PERMISSION_GRANTED) {
+        if (callingOrSelfHasAudioSettingsPermission()) {
             return true;
         }
         String msg = "Audio Settings Permission Denial: " + method + " from pid="
@@ -5404,6 +5575,21 @@
         return false;
     }
 
+    private boolean callingOrSelfHasAudioSettingsPermission() {
+        return mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean callingHasAudioSettingsPermission() {
+        return mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean hasAudioSettingsPermission(int uid, int pid) {
+        return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     /**
      * Minimum attenuation that can be set for alarms over speaker by an application that
      * doesn't have the MODIFY_AUDIO_SETTINGS permission.
@@ -5683,11 +5869,6 @@
             throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
                     + " (dis)connection, got " + state);
         }
-        if (state == BluetoothProfile.STATE_CONNECTED) {
-            mPlaybackMonitor.registerPlaybackCallback(mVoiceActivityMonitor, true);
-        } else {
-            mPlaybackMonitor.unregisterPlaybackCallback(mVoiceActivityMonitor);
-        }
         mDeviceBroker.postBluetoothHearingAidDeviceConnectionState(
                 device, state, suppressNoisyIntent, musicDevice, "AudioService");
     }
@@ -7032,6 +7213,9 @@
                 case MSG_PLAYBACK_CONFIG_CHANGE:
                     onPlaybackConfigChange((List<AudioPlaybackConfiguration>) msg.obj);
                     break;
+                case MSG_RECORDING_CONFIG_CHANGE:
+                    onRecordingConfigChange((List<AudioRecordingConfiguration>) msg.obj);
+                    break;
 
                 case MSG_BROADCAST_MICROPHONE_MUTE:
                     mSystemServer.sendMicrophoneMuteChangedIntent();
@@ -7042,30 +7226,19 @@
                         if (msg.obj == null) {
                             break;
                         }
-                        // If no other app is currently owning the audio mode and
-                        // the app corresponding to this mode death handler object is still in the
-                        // mode owner stack but not capturing or playing audio after 3 seconds,
-                        // remove it from the stack.
-                        // Otherwise, check again in 3 seconds.
+                        // Update active playback/recording for apps requesting IN_COMMUNICATION
+                        // mode after a grace period following the mode change
                         SetModeDeathHandler h = (SetModeDeathHandler) msg.obj;
                         if (mSetModeDeathHandlers.indexOf(h) < 0) {
                             break;
                         }
-                        if (getModeOwnerUid() != h.getUid()
-                                || mRecordMonitor.isRecordingActiveForUid(h.getUid())
-                                || mPlaybackMonitor.isPlaybackActiveForUid(h.getUid())) {
-                            sendMsg(mAudioHandler,
-                                    MSG_CHECK_MODE_FOR_UID,
-                                    SENDMSG_QUEUE,
-                                    0,
-                                    0,
-                                    h,
-                                    CHECK_MODE_FOR_UID_PERIOD_MS);
-                            break;
+                        boolean wasActive = h.isActive();
+                        h.setPlaybackActive(mPlaybackMonitor.isPlaybackActiveForUid(h.getUid()));
+                        h.setRecordingActive(mRecordMonitor.isRecordingActiveForUid(h.getUid()));
+                        if (wasActive != h.isActive()) {
+                            onUpdateAudioMode(AudioSystem.MODE_CURRENT,
+                                    android.os.Process.myPid(), mContext.getPackageName());
                         }
-                        setModeInt(AudioSystem.MODE_NORMAL, h.getBinder(), h.getPid(), h.getUid(),
-                                h.isPrivileged(), "MSG_CHECK_MODE_FOR_UID");
-                        mModeLogger.log(new PhoneStateEvent(h.getPackage(), h.getPid()));
                     }
                     break;
 
@@ -7082,6 +7255,16 @@
                 case MSG_REINIT_VOLUMES:
                     onReinitVolumes((String) msg.obj);
                     break;
+
+                case MSG_UPDATE_A11Y_SERVICE_UIDS:
+                    onUpdateAccessibilityServiceUids();
+                    break;
+
+                case MSG_UPDATE_AUDIO_MODE:
+                    synchronized (mDeviceBroker.mSetModeLock) {
+                        onUpdateAudioMode(msg.arg1, msg.arg2, (String) msg.obj);
+                    }
+                    break;
             }
         }
     }
@@ -7825,9 +8008,8 @@
 
     @GuardedBy("mHdmiClientLock")
     private void updateHdmiCecSinkLocked(boolean hdmiCecSink) {
-        mHdmiCecSink = hdmiCecSink;
         if (!hasDeviceVolumeBehavior(AudioSystem.DEVICE_OUT_HDMI)) {
-            if (mHdmiCecSink) {
+            if (hdmiCecSink) {
                 if (DEBUG_VOL) {
                     Log.d(TAG, "CEC sink: setting HDMI as full vol device");
                 }
@@ -7885,9 +8067,6 @@
     // Set only when device is a set-top box.
     @GuardedBy("mHdmiClientLock")
     private HdmiPlaybackClient mHdmiPlaybackClient;
-    // true if we are a set-top box, an HDMI sink is connected and it supports CEC.
-    @GuardedBy("mHdmiClientLock")
-    private boolean mHdmiCecSink;
     // Set only when device is an audio system.
     @GuardedBy("mHdmiClientLock")
     private HdmiAudioSystemClient mHdmiAudioSystemClient;
@@ -8116,6 +8295,7 @@
         dumpStreamStates(pw);
         dumpVolumeGroups(pw);
         dumpRingerMode(pw);
+        dumpAudioMode(pw);
         pw.println("\nAudio routes:");
         pw.print("  mMainType=0x"); pw.println(Integer.toHexString(
                 mDeviceBroker.getCurAudioRoutes().mainType));
@@ -8142,7 +8322,6 @@
         pw.print("  mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
         pw.print("  mFullVolumeDevices="); pw.println(dumpDeviceTypes(mFullVolumeDevices));
         pw.print("  mExtVolumeController="); pw.println(mExtVolumeController);
-        pw.print("  mHdmiCecSink="); pw.println(mHdmiCecSink);
         pw.print("  mHdmiAudioSystemClient="); pw.println(mHdmiAudioSystemClient);
         pw.print("  mHdmiPlaybackClient="); pw.println(mHdmiPlaybackClient);
         pw.print("  mHdmiTvClient="); pw.println(mHdmiTvClient);
@@ -8153,6 +8332,9 @@
                         + " FromRestrictions=" + mMicMuteFromRestrictions
                         + " FromApi=" + mMicMuteFromApi
                         + " from system=" + mMicMuteFromSystemCached);
+        pw.print("\n  mAssistantUid="); pw.println(mAssistantUid);
+        pw.print("  mCurrentImeUid="); pw.println(mCurrentImeUid);
+        dumpAccessibilityServiceUids(pw);
 
         dumpAudioPolicies(pw);
         mDynPolicyLogger.dump(pw);
@@ -8186,6 +8368,19 @@
         }
     }
 
+    private void dumpAccessibilityServiceUids(PrintWriter pw) {
+        synchronized (mSupportedSystemUsagesLock) {
+            if (mAccessibilityServiceUids != null && mAccessibilityServiceUids.length > 0) {
+                pw.println("  Accessibility service Uids:");
+                for (int uid : mAccessibilityServiceUids) {
+                    pw.println("  - " + uid);
+                }
+            } else {
+                pw.println("  No accessibility service Uids.");
+            }
+        }
+    }
+
     /**
      * Audio Analytics ids.
      */
@@ -8489,7 +8684,8 @@
                         mAccessibilityServiceUids = uids.toArray();
                     }
                 }
-                AudioSystem.setA11yServicesUids(mAccessibilityServiceUids);
+                sendMsg(mAudioHandler, MSG_UPDATE_A11Y_SERVICE_UIDS, SENDMSG_REPLACE,
+                        0, 0, null, 0);
             }
         }
 
@@ -8507,6 +8703,14 @@
         }
     }
 
+    private void onUpdateAccessibilityServiceUids() {
+        int[] accessibilityServiceUids;
+        synchronized (mAccessibilityServiceUidsLock) {
+            accessibilityServiceUids = mAccessibilityServiceUids;
+        }
+        AudioSystem.setA11yServicesUids(accessibilityServiceUids);
+    }
+
     //==========================================================================================
     // Audio policy management
     //==========================================================================================
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 6905b3d..6851d71 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -90,6 +90,7 @@
             int userId, PromptInfo promptInfo, String opPackageName,
             boolean checkDevicePolicyManager)
             throws RemoteException {
+
         final boolean confirmationRequested = promptInfo.isConfirmationRequested();
         final boolean biometricRequested = Utils.isBiometricRequested(promptInfo);
         final int requestedStrength = Utils.getPublicBiometricStrength(promptInfo);
@@ -111,7 +112,7 @@
 
                 @AuthenticatorStatus int status = getStatusForBiometricAuthenticator(
                         devicePolicyManager, settingObserver, sensor, userId, opPackageName,
-                        checkDevicePolicyManager, requestedStrength);
+                        checkDevicePolicyManager, requestedStrength, promptInfo.getSensorId());
 
                 Slog.d(TAG, "Package: " + opPackageName
                         + " Sensor ID: " + sensor.id
@@ -141,7 +142,11 @@
             DevicePolicyManager devicePolicyManager,
             BiometricService.SettingObserver settingObserver,
             BiometricSensor sensor, int userId, String opPackageName,
-            boolean checkDevicePolicyManager, int requestedStrength) {
+            boolean checkDevicePolicyManager, int requestedStrength, int requestedSensorId) {
+
+        if (requestedSensorId != BiometricManager.SENSOR_ID_ANY && sensor.id != requestedSensorId) {
+            return BIOMETRIC_NO_HARDWARE;
+        }
 
         final boolean wasStrongEnough =
                 Utils.isAtLeastStrength(sensor.oemStrength, requestedStrength);
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index d87af42..5cd0bbf 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -399,10 +399,15 @@
         }
     }
 
-    public static boolean isKeyguard(Context context, String clientPackage) {
-        final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
-                == PackageManager.PERMISSION_GRANTED;
-
+    /**
+     * Checks if a client package matches Keyguard and can perform internal biometric operations.
+     *
+     * @param context The system context.
+     * @param clientPackage The name of the package to be checked against Keyguard.
+     * @return Whether the given package matches Keyguard.
+     */
+    public static boolean isKeyguard(@NonNull Context context, @Nullable String clientPackage) {
+        final boolean hasPermission = hasInternalPermission(context);
         final ComponentName keyguardComponent = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.config_keyguardComponent));
         final String keyguardPackage = keyguardComponent != null
@@ -410,6 +415,34 @@
         return hasPermission && keyguardPackage != null && keyguardPackage.equals(clientPackage);
     }
 
+    /**
+     * Checks if a client package matches the Android system and can perform internal biometric
+     * operations.
+     *
+     * @param context The system context.
+     * @param clientPackage The name of the package to be checked against the Android system.
+     * @return Whether the given package matches the Android system.
+     */
+    public static boolean isSystem(@NonNull Context context, @Nullable String clientPackage) {
+        return hasInternalPermission(context) && "android".equals(clientPackage);
+    }
+
+    /**
+     * Checks if a client package matches Settings and can perform internal biometric operations.
+     *
+     * @param context The system context.
+     * @param clientPackage The name of the package to be checked against Settings.
+     * @return Whether the given package matches Settings.
+     */
+    public static boolean isSettings(@NonNull Context context, @Nullable String clientPackage) {
+        return hasInternalPermission(context) && "com.android.settings".equals(clientPackage);
+    }
+
+    private static boolean hasInternalPermission(@NonNull Context context) {
+        return context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     public static String getClientName(@Nullable BaseClientMonitor client) {
         return client != null ? client.getClass().getSimpleName() : "null";
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 14433fb..0536e78 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -149,9 +149,10 @@
                 pm.incrementAuthForUser(getTargetUserId(), authenticated);
             }
 
-            // Ensure authentication only succeeds if the client activity is on top or is keyguard.
+            // Ensure authentication only succeeds if the client activity is on top.
             boolean isBackgroundAuth = false;
-            if (authenticated && !Utils.isKeyguard(getContext(), getOwnerString())) {
+            if (authenticated && !Utils.isKeyguard(getContext(), getOwnerString())
+                    && !Utils.isSystem(getContext(), getOwnerString())) {
                 final List<ActivityManager.RunningTaskInfo> tasks =
                         mActivityTaskManager.getTasks(1);
                 if (tasks == null || tasks.isEmpty()) {
@@ -166,7 +167,7 @@
                         final String topPackage = topActivity.getPackageName();
                         if (!topPackage.contentEquals(getOwnerString())) {
                             Slog.e(TAG, "Background authentication detected, top: " + topPackage
-                                    + ", client: " + this);
+                                    + ", client: " + getOwnerString());
                             isBackgroundAuth = true;
                         }
                     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index c86bfcb..c5237ab 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -563,14 +563,26 @@
         final boolean isAuthenticating =
                 mCurrentOperation.mClientMonitor instanceof AuthenticationConsumer;
         final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
-        if (!isAuthenticating || !tokenMatches) {
-            Slog.w(getTag(), "Not cancelling authentication"
-                    + ", current operation : " + mCurrentOperation
-                    + ", tokenMatches: " + tokenMatches);
-            return;
-        }
 
-        cancelInternal(mCurrentOperation);
+        if (isAuthenticating && tokenMatches) {
+            Slog.d(getTag(), "Cancelling authentication: " + mCurrentOperation);
+            cancelInternal(mCurrentOperation);
+        } else if (!isAuthenticating) {
+            // Look through the current queue for all authentication clients for the specified
+            // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
+            // all of them, instead of just the first one, since the API surface currently doesn't
+            // allow us to distinguish between multiple authentication requests from the same
+            // process. However, this generally does not happen anyway, and would be a class of
+            // bugs on its own.
+            for (Operation operation : mPendingOperations) {
+                if (operation.mClientMonitor instanceof AuthenticationConsumer
+                        && operation.mClientMonitor.getToken() == token) {
+                    Slog.d(getTag(), "Marking " + operation
+                            + " as STATE_WAITING_IN_QUEUE_CANCELING");
+                    operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
+                }
+            }
+        }
     }
 
     /**
@@ -623,10 +635,15 @@
         proto.write(BiometricSchedulerProto.CURRENT_OPERATION, mCurrentOperation != null
                 ? mCurrentOperation.mClientMonitor.getProtoEnum() : BiometricsProto.CM_NONE);
         proto.write(BiometricSchedulerProto.TOTAL_OPERATIONS, mTotalOperationsHandled);
-        Slog.d(getTag(), "Total operations: " + mTotalOperationsHandled);
-        for (int i = 0; i < mRecentOperations.size(); i++) {
-            Slog.d(getTag(), "Operation: " + mRecentOperations.get(i));
-            proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, mRecentOperations.get(i));
+
+        if (!mRecentOperations.isEmpty()) {
+            for (int i = 0; i < mRecentOperations.size(); i++) {
+                proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, mRecentOperations.get(i));
+            }
+        } else {
+            // TODO:(b/178828362) Unsure why protobuf has a problem decoding when an empty list
+            //  is returned. So, let's just add a no-op for this case.
+            proto.write(BiometricSchedulerProto.RECENT_OPERATIONS, BiometricsProto.CM_NONE);
         }
         proto.flush();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 1a63dde..8253927 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -218,7 +218,7 @@
         @Override // Binder call
         public void enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
                 final IFaceServiceReceiver receiver, final String opPackageName,
-                final int[] disabledFeatures, Surface surface) {
+                final int[] disabledFeatures, Surface surface, boolean debugConsent) {
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -229,7 +229,7 @@
 
             provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
                     receiver, opPackageName, disabledFeatures,
-                    convertSurfaceToNativeHandle(surface));
+                    convertSurfaceToNativeHandle(surface), debugConsent);
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 32428ac1..cc24b89 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -94,7 +94,8 @@
 
     void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
             int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
-            @NonNull int[] disabledFeatures, @Nullable NativeHandle surfaceHandle);
+            @NonNull int[] disabledFeatures, @Nullable NativeHandle surfaceHandle,
+            boolean debugConsent);
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
new file mode 100644
index 0000000..769c47a
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 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.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.face.AuthenticationFrame;
+import android.hardware.biometrics.face.BaseFrame;
+import android.hardware.biometrics.face.Cell;
+import android.hardware.biometrics.face.EnrollmentFrame;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceDataFrame;
+import android.hardware.face.FaceEnrollCell;
+import android.hardware.face.FaceEnrollFrame;
+
+/**
+ * Utilities for converting between hardware and framework-defined AIDL models.
+ */
+final class AidlConversionUtils {
+    // Prevent instantiation.
+    private AidlConversionUtils() {}
+
+    @NonNull
+    public static FaceAuthenticationFrame convert(@NonNull AuthenticationFrame frame) {
+        return new FaceAuthenticationFrame(convert(frame.data));
+    }
+
+    @NonNull
+    public static AuthenticationFrame convert(@NonNull FaceAuthenticationFrame frame) {
+        final AuthenticationFrame convertedFrame = new AuthenticationFrame();
+        convertedFrame.data = convert(frame.getData());
+        return convertedFrame;
+    }
+
+    @NonNull
+    public static FaceEnrollFrame convert(@NonNull EnrollmentFrame frame) {
+        return new FaceEnrollFrame(convert(frame.cell), frame.stage, convert(frame.data));
+    }
+
+    @NonNull
+    public static EnrollmentFrame convert(@NonNull FaceEnrollFrame frame) {
+        final EnrollmentFrame convertedFrame = new EnrollmentFrame();
+        convertedFrame.cell = convert(frame.getCell());
+        convertedFrame.stage = (byte) frame.getStage();
+        convertedFrame.data = convert(frame.getData());
+        return convertedFrame;
+    }
+
+    @NonNull
+    public static FaceDataFrame convert(@NonNull BaseFrame frame) {
+        return new FaceDataFrame(
+                frame.acquiredInfo,
+                frame.vendorCode,
+                frame.pan,
+                frame.tilt,
+                frame.distance,
+                frame.isCancellable);
+    }
+
+    @NonNull
+    public static BaseFrame convert(@NonNull FaceDataFrame frame) {
+        final BaseFrame convertedFrame = new BaseFrame();
+        convertedFrame.acquiredInfo = (byte) frame.getAcquiredInfo();
+        convertedFrame.vendorCode = frame.getVendorCode();
+        convertedFrame.pan = frame.getPan();
+        convertedFrame.tilt = frame.getTilt();
+        convertedFrame.distance = frame.getDistance();
+        convertedFrame.isCancellable = frame.isCancellable();
+        return convertedFrame;
+    }
+
+    @Nullable
+    public static FaceEnrollCell convert(@Nullable Cell cell) {
+        return cell == null ? null : new FaceEnrollCell(cell.x, cell.y, cell.z);
+    }
+
+    @Nullable
+    public static Cell convert(@Nullable FaceEnrollCell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        final Cell convertedCell = new Cell();
+        convertedCell.x = cell.getX();
+        convertedCell.y = cell.getY();
+        convertedCell.z = cell.getZ();
+        return convertedCell;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 211d79c..d2673d2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -142,7 +142,8 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName(), new int[0] /* disabledFeatures */, null /* surface */);
+                mContext.getOpPackageName(), new int[0] /* disabledFeatures */, null /* surface */,
+                false /* debugConsent */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index a7bfc4b..3057766 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -28,6 +28,8 @@
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
+import android.hardware.face.FaceAuthenticationFrame;
+import android.hardware.face.FaceDataFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -193,6 +195,17 @@
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
     }
 
+    /**
+     * Called each time a new frame is received during face authentication.
+     *
+     * @param frame Information about the current frame.
+     */
+    public void onAuthenticationFrame(@NonNull FaceAuthenticationFrame frame) {
+        // TODO(b/178414967): Send additional frame data to the client callback.
+        final FaceDataFrame data = frame.getData();
+        onAcquired(data.getAcquiredInfo(), data.getVendorCode());
+    }
+
     @Override public void onLockoutTimed(long durationMillis) {
         mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
         // Lockout metrics are logged as an error code.
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index d60bb79..da657b9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -23,9 +23,12 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.face.EnrollmentType;
+import android.hardware.biometrics.face.Feature;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
 import android.hardware.face.Face;
+import android.hardware.face.FaceDataFrame;
+import android.hardware.face.FaceEnrollFrame;
 import android.hardware.face.FaceManager;
 import android.os.IBinder;
 import android.os.NativeHandle;
@@ -55,12 +58,14 @@
     @Nullable private ICancellationSignal mCancellationSignal;
     @Nullable private android.hardware.common.NativeHandle mPreviewSurface;
     private final int mMaxTemplatesPerUser;
+    private final boolean mDebugConsent;
 
     FaceEnrollClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
             @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName,
             @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
-            @Nullable NativeHandle previewSurface, int sensorId, int maxTemplatesPerUser) {
+            @Nullable NativeHandle previewSurface, int sensorId, int maxTemplatesPerUser,
+            boolean debugConsent) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId,
                 false /* shouldVibrate */);
@@ -69,6 +74,7 @@
         mEnrollIgnoreListVendor = getContext().getResources()
                 .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
+        mDebugConsent = debugConsent;
         try {
             // We must manually close the duplicate handle after it's no longer needed.
             // The caller is responsible for closing the original handle.
@@ -106,6 +112,17 @@
         onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
     }
 
+    /**
+     * Called each time a new frame is received during face enrollment.
+     *
+     * @param frame Information about the current frame.
+     */
+    public void onEnrollmentFrame(@NonNull FaceEnrollFrame frame) {
+        // TODO(b/178414967): Send additional frame data to the client callback.
+        final FaceDataFrame data = frame.getData();
+        onAcquired(data.getAcquiredInfo(), data.getVendorCode());
+    }
+
     @Override
     protected void startHalOperation() {
         final ArrayList<Byte> token = new ArrayList<>();
@@ -116,9 +133,17 @@
         try {
             // TODO(b/172593978): Pass features.
             // TODO(b/174619156): Handle accessibility enrollment.
+            byte[] features;
+            if (mDebugConsent) {
+                features = new byte[1];
+                features[0] = Feature.DEBUG;
+            } else {
+                features = new byte[0];
+            }
+
             mCancellationSignal = getFreshDaemon().enroll(mSequentialId,
                     HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken),
-                    EnrollmentType.DEFAULT, new byte[0], mPreviewSurface);
+                    EnrollmentType.DEFAULT, features, mPreviewSurface);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting enroll", e);
             onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index f7feffd..e685ee2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -382,7 +382,7 @@
     public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
             @NonNull String opPackageName, @NonNull int[] disabledFeatures,
-            @Nullable NativeHandle previewSurface) {
+            @Nullable NativeHandle previewSurface, boolean debugConsent) {
         mHandler.post(() -> {
             final IFace daemon = getHalInstance();
             if (daemon == null) {
@@ -404,7 +404,8 @@
                         mSensors.get(sensorId).getLazySession(), token,
                         new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                         opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
-                        ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser);
+                        ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
+                        debugConsent);
                 scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
                     @Override
                     public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index f49601a..640838c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -45,7 +45,6 @@
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
 import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.sensors.AcquisitionClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -170,33 +169,39 @@
 
         @Override
         public void onAuthenticationFrame(AuthenticationFrame frame) {
-            // TODO(b/174619156): propagate the frame to an AuthenticationClient
             mHandler.post(() -> {
                 final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AcquisitionClient)) {
-                    Slog.e(mTag, "onAcquired for non-acquisition client: "
+                if (!(client instanceof FaceAuthenticationClient)) {
+                    Slog.e(mTag, "onAuthenticationFrame for incompatible client: "
+                            + Utils.getClientName(client));
+                    return;
+
+                }
+                if (frame == null) {
+                    Slog.e(mTag, "Received null authentication frame for client: "
                             + Utils.getClientName(client));
                     return;
                 }
-
-                final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
-                acquisitionClient.onAcquired(frame.data.acquiredInfo, frame.data.vendorCode);
+                ((FaceAuthenticationClient) client).onAuthenticationFrame(
+                        AidlConversionUtils.convert(frame));
             });
         }
 
         @Override
         public void onEnrollmentFrame(EnrollmentFrame frame) {
-            // TODO(b/174619156): propagate the frame to an EnrollmentClient
             mHandler.post(() -> {
                 final BaseClientMonitor client = mScheduler.getCurrentClient();
-                if (!(client instanceof AcquisitionClient)) {
-                    Slog.e(mTag, "onAcquired for non-acquisition client: "
+                if (!(client instanceof FaceEnrollClient)) {
+                    Slog.e(mTag, "onEnrollmentFrame for incompatible client: "
                             + Utils.getClientName(client));
                     return;
                 }
-
-                final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
-                acquisitionClient.onAcquired(frame.data.acquiredInfo, frame.data.vendorCode);
+                if (frame == null) {
+                    Slog.e(mTag, "Received null enrollment frame for client: "
+                            + Utils.getClientName(client));
+                    return;
+                }
+                ((FaceEnrollClient) client).onEnrollmentFrame(AidlConversionUtils.convert(frame));
             });
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 9ed8f78..4142a52 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -131,7 +131,7 @@
 
         mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
                 mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
-                null /* surfaceHandle */);
+                null /* surfaceHandle */, false /* debugConsent */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 775d8d4..e46661a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -602,7 +602,7 @@
     public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
             @NonNull String opPackageName, @NonNull int[] disabledFeatures,
-            @Nullable NativeHandle surfaceHandle) {
+            @Nullable NativeHandle surfaceHandle, boolean debugConsent) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 0265cb9..b0e42cd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -25,6 +25,10 @@
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -32,6 +36,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricService;
@@ -49,6 +54,7 @@
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Build;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Process;
@@ -80,6 +86,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * A service to manage multiple clients that want to access the fingerprint HAL API.
@@ -190,7 +197,7 @@
         @Override // Binder call
         public void enroll(final IBinder token, final byte[] hardwareAuthToken, final int userId,
                 final IFingerprintServiceReceiver receiver, final String opPackageName,
-                boolean shouldLogMetrics) {
+                @FingerprintManager.EnrollReason int enrollReason) {
             Utils.checkPermission(getContext(), MANAGE_FINGERPRINT);
 
             final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -200,7 +207,7 @@
             }
 
             provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
-                    receiver, opPackageName, shouldLogMetrics);
+                    receiver, opPackageName, enrollReason);
         }
 
         @Override // Binder call
@@ -219,8 +226,8 @@
         @SuppressWarnings("deprecation")
         @Override // Binder call
         public void authenticate(final IBinder token, final long operationId,
-                @FingerprintManager.SensorId final int sensorId, final int userId,
-                final IFingerprintServiceReceiver receiver, final String opPackageName) {
+                final int sensorId, final int userId, final IFingerprintServiceReceiver receiver,
+                final String opPackageName) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final int callingUserId = UserHandle.getCallingUserId();
@@ -236,7 +243,7 @@
             final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);
 
             // Clear calling identity when checking LockPatternUtils for StrongAuth flags.
-            final long identity = Binder.clearCallingIdentity();
+            long identity = Binder.clearCallingIdentity();
             try {
                 if (isKeyguard && Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
                     // If this happens, something in KeyguardUpdateMonitor is wrong.
@@ -266,9 +273,101 @@
                 return;
             }
 
-            provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
-                    0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
-                    restricted, statsClient, isKeyguard);
+            final FingerprintSensorPropertiesInternal sensorProps =
+                    provider.second.getSensorProperties(sensorId);
+            if (!isKeyguard && !Utils.isSettings(getContext(), opPackageName)
+                    && sensorProps != null && sensorProps.isAnyUdfpsType()) {
+                identity = Binder.clearCallingIdentity();
+                try {
+                    authenticateWithPrompt(operationId, sensorProps, userId, receiver);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            } else {
+                provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+                        0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
+                        restricted, statsClient, isKeyguard);
+            }
+        }
+
+        private void authenticateWithPrompt(
+                final long operationId,
+                @NonNull final FingerprintSensorPropertiesInternal props,
+                final int userId,
+                final IFingerprintServiceReceiver receiver) {
+
+            final Context context = getUiContext();
+            final Executor executor = context.getMainExecutor();
+
+            final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(context)
+                    .setTitle(context.getString(R.string.biometric_dialog_default_title))
+                    .setSubtitle(context.getString(R.string.fingerprint_dialog_default_subtitle))
+                    .setNegativeButton(
+                            context.getString(R.string.cancel),
+                            executor,
+                            (dialog, which) -> {
+                                try {
+                                    receiver.onError(
+                                            FINGERPRINT_ERROR_USER_CANCELED, 0 /* vendorCode */);
+                                } catch (RemoteException e) {
+                                    Slog.e(TAG, "Remote exception in negative button onClick()", e);
+                                }
+                            })
+                    .setSensorId(props.sensorId)
+                    .build();
+
+            final BiometricPrompt.AuthenticationCallback promptCallback =
+                    new BiometricPrompt.AuthenticationCallback() {
+                        @Override
+                        public void onAuthenticationError(int errorCode, CharSequence errString) {
+                            try {
+                                if (FingerprintUtils.isKnownErrorCode(errorCode)) {
+                                    receiver.onError(errorCode, 0 /* vendorCode */);
+                                } else {
+                                    receiver.onError(FINGERPRINT_ERROR_VENDOR, errorCode);
+                                }
+                            } catch (RemoteException e) {
+                                Slog.e(TAG, "Remote exception in onAuthenticationError()", e);
+                            }
+                        }
+
+                        @Override
+                        public void onAuthenticationSucceeded(
+                                BiometricPrompt.AuthenticationResult result) {
+                            final Fingerprint fingerprint = new Fingerprint("", 0, 0L);
+                            final boolean isStrong = props.sensorStrength == STRENGTH_STRONG;
+                            try {
+                                receiver.onAuthenticationSucceeded(fingerprint, userId, isStrong);
+                            } catch (RemoteException e) {
+                                Slog.e(TAG, "Remote exception in onAuthenticationSucceeded()", e);
+                            }
+                        }
+
+                        @Override
+                        public void onAuthenticationFailed() {
+                            try {
+                                receiver.onAuthenticationFailed();
+                            } catch (RemoteException e) {
+                                Slog.e(TAG, "Remote exception in onAuthenticationFailed()", e);
+                            }
+                        }
+
+                        @Override
+                        public void onAuthenticationAcquired(int acquireInfo) {
+                            try {
+                                if (FingerprintUtils.isKnownAcquiredCode(acquireInfo)) {
+                                    receiver.onAcquired(acquireInfo, 0 /* vendorCode */);
+                                } else {
+                                    receiver.onAcquired(FINGERPRINT_ACQUIRED_VENDOR, acquireInfo);
+                                }
+                            } catch (RemoteException e) {
+                                Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
+                            }
+                        }
+                    };
+
+            biometricPrompt.authenticateUserForOperation(
+                    new CancellationSignal(), executor, promptCallback, userId, operationId);
         }
 
         @Override
@@ -374,6 +473,7 @@
         @Override // Binder call
         public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
                 final String opPackageName) {
+
             Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
 
             final ServiceProvider provider = getProviderForSensor(sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
index dc6fd3a..d69151d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
@@ -16,8 +16,18 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_IMAGER_DIRTY;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_INSUFFICIENT;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_PARTIAL;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_TOO_FAST;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_TOO_SLOW;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.biometrics.fingerprint.V2_1.FingerprintError;
 import android.hardware.fingerprint.Fingerprint;
 import android.text.TextUtils;
 import android.util.SparseArray;
@@ -138,5 +148,51 @@
             return state;
         }
     }
+
+    /**
+     * Checks if the given error code corresponds to a known fingerprint error.
+     *
+     * @param errorCode The error code to be checked.
+     * @return Whether the error code corresponds to a known error.
+     */
+    public static boolean isKnownErrorCode(int errorCode) {
+        switch (errorCode) {
+            case FingerprintError.ERROR_HW_UNAVAILABLE:
+            case FingerprintError.ERROR_UNABLE_TO_PROCESS:
+            case FingerprintError.ERROR_TIMEOUT:
+            case FingerprintError.ERROR_NO_SPACE:
+            case FingerprintError.ERROR_CANCELED:
+            case FingerprintError.ERROR_UNABLE_TO_REMOVE:
+            case FingerprintError.ERROR_LOCKOUT:
+            case FingerprintError.ERROR_VENDOR:
+                return true;
+
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Checks if the given acquired code corresponds to a known fingerprint error.
+     *
+     * @param acquiredCode The acquired code to be checked.
+     * @return Whether the acquired code corresponds to a known error.
+     */
+    public static boolean isKnownAcquiredCode(int acquiredCode) {
+        switch (acquiredCode) {
+            case FINGERPRINT_ACQUIRED_GOOD:
+            case FINGERPRINT_ACQUIRED_PARTIAL:
+            case FINGERPRINT_ACQUIRED_INSUFFICIENT:
+            case FINGERPRINT_ACQUIRED_IMAGER_DIRTY:
+            case FINGERPRINT_ACQUIRED_TOO_SLOW:
+            case FINGERPRINT_ACQUIRED_TOO_FAST:
+            case FINGERPRINT_ACQUIRED_VENDOR:
+            case FINGERPRINT_ACQUIRED_START:
+                return true;
+
+            default:
+                return false;
+        }
+    }
 }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index d3b5b5a..f672ae5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -78,7 +78,7 @@
 
     void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
-            boolean shouldLogMetrics);
+            @FingerprintManager.EnrollReason int enrollReason);
 
     void cancelEnrollment(int sensorId, @NonNull IBinder token);
 
@@ -114,11 +114,8 @@
      * Requests for the authenticatorId (whose source of truth is in the TEE or equivalent) to
      * be invalidated. See {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}
      */
-    default void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
-            @NonNull IInvalidationCallback callback) {
-        throw new IllegalStateException("Providers that support invalidation must override"
-                + " this method");
-    }
+    void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
+            @NonNull IInvalidationCallback callback);
 
     long getAuthenticatorId(int sensorId, int userId);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
index 01a620f..37f8e8c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
@@ -16,8 +16,11 @@
 
 package com.android.server.biometrics.sensors.fingerprint;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -62,6 +65,17 @@
         }
     }
 
+    public static int getReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) {
+        switch (reason) {
+            case FingerprintManager.ENROLL_FIND_SENSOR:
+                return IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR;
+            case FingerprintManager.ENROLL_ENROLL:
+                return IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
+            default:
+                return IUdfpsOverlayController.REASON_UNKNOWN;
+        }
+    }
+
     public static void showUdfpsOverlay(int sensorId, int reason,
             @Nullable IUdfpsOverlayController udfpsOverlayController) {
         if (udfpsOverlayController == null) {
@@ -85,4 +99,33 @@
             Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
         }
     }
+
+    public static void onEnrollmentProgress(int sensorId, int remaining,
+            @Nullable IUdfpsOverlayController udfpsOverlayController) {
+        if (udfpsOverlayController == null) {
+            return;
+        }
+        try {
+            udfpsOverlayController.onEnrollmentProgress(sensorId, remaining);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when sending onEnrollmentProgress", e);
+        }
+    }
+
+    public static void onEnrollmentHelp(int sensorId,
+            @Nullable IUdfpsOverlayController udfpsOverlayController) {
+        if (udfpsOverlayController == null) {
+            return;
+        }
+        try {
+            udfpsOverlayController.onEnrollmentHelp(sensorId);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Remote exception when sending onEnrollmentHelp", e);
+        }
+    }
+
+    public static boolean isValidAcquisitionMessage(@NonNull Context context,
+            int acquireInfo, int vendorCode) {
+        return FingerprintManager.getAcquiredString(context, acquireInfo, vendorCode) != null;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index c2a30be..ea9c709 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
 import android.util.Slog;
@@ -131,7 +132,7 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName(), true /* shouldLogMetrics */);
+                mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 0864c1a..ae64c77 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -25,6 +25,7 @@
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -43,6 +44,7 @@
     private static final String TAG = "FingerprintEnrollClient";
 
     @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+    private final @FingerprintManager.EnrollReason int mEnrollReason;
     @Nullable private ICancellationSignal mCancellationSignal;
     private final int mMaxTemplatesPerUser;
 
@@ -52,24 +54,40 @@
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @Nullable IUdfpsOverlayController udfpsOvelayController, int maxTemplatesPerUser,
-            boolean shouldLogMetrics) {
+            @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 true /* shouldVibrate */);
         mUdfpsOverlayController = udfpsOvelayController;
         mMaxTemplatesPerUser = maxTemplatesPerUser;
-        setShouldLog(shouldLogMetrics);
+
+        mEnrollReason = enrollReason;
+        if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
+            setShouldLog(false);
+        }
     }
 
     @Override
     public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
         super.onEnrollResult(identifier, remaining);
 
+        UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController);
+
         if (remaining == 0) {
             UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
         }
     }
 
+
+    @Override
+    public void onAcquired(int acquiredInfo, int vendorCode) {
+        super.onAcquired(acquiredInfo, vendorCode);
+
+        if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
+            UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController);
+        }
+    }
+
     @Override
     public void onError(int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
@@ -101,7 +119,8 @@
 
     @Override
     protected void startHalOperation() {
-        UdfpsHelper.showUdfpsOverlay(getSensorId(), IUdfpsOverlayController.REASON_ENROLL,
+        UdfpsHelper.showUdfpsOverlay(getSensorId(),
+                UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
                 mUdfpsOverlayController);
         try {
             mCancellationSignal = getFreshDaemon().enroll(mSequentialId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index f845024..ced46e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -28,6 +28,7 @@
 import android.hardware.biometrics.fingerprint.IFingerprint;
 import android.hardware.biometrics.fingerprint.SensorProps;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -96,7 +97,8 @@
                         Slog.e(getTag(), "Task stack changed for client: " + client);
                         continue;
                     }
-                    if (Utils.isKeyguard(mContext, client.getOwnerString())) {
+                    if (Utils.isKeyguard(mContext, client.getOwnerString())
+                            || Utils.isSystem(mContext, client.getOwnerString())) {
                         continue; // Keyguard is always allowed
                     }
 
@@ -365,7 +367,7 @@
     @Override
     public void scheduleEnroll(int sensorId, @NonNull IBinder token, byte[] hardwareAuthToken,
             int userId, @NonNull IFingerprintServiceReceiver receiver,
-            @NonNull String opPackageName, boolean shouldLogMetrics) {
+            @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
         mHandler.post(() -> {
             final IFingerprint daemon = getHalInstance();
             if (daemon == null) {
@@ -387,7 +389,7 @@
                         mSensors.get(sensorId).getLazySession(), token,
                         new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                         opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
-                        mUdfpsOverlayController, maxTemplatesPerUser, shouldLogMetrics);
+                        mUdfpsOverlayController, maxTemplatesPerUser, enrollReason);
                 scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
                     @Override
                     public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index 6893e72..312ee0a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.os.Binder;
 import android.util.Slog;
@@ -132,7 +133,7 @@
         Utils.checkPermission(mContext, TEST_BIOMETRIC);
 
         mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
-                mContext.getOpPackageName(), true/* shouldLogMetrics */);
+                mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index acc575f..7a74c6a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -28,10 +28,12 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IInvalidationCallback;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -47,6 +49,8 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
@@ -111,6 +115,7 @@
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     private int mCurrentUserId = UserHandle.USER_NULL;
+    private final boolean mIsUdfps;
     private final int mSensorId;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -122,7 +127,8 @@
                     Slog.e(TAG, "Task stack changed for client: " + client);
                     return;
                 }
-                if (Utils.isKeyguard(mContext, client.getOwnerString())) {
+                if (Utils.isKeyguard(mContext, client.getOwnerString())
+                        || Utils.isSystem(mContext, client.getOwnerString())) {
                     return; // Keyguard is always allowed
                 }
 
@@ -143,11 +149,11 @@
 
     private final LockoutFrameworkImpl.LockoutResetCallback mLockoutResetCallback =
             new LockoutFrameworkImpl.LockoutResetCallback() {
-        @Override
-        public void onLockoutReset(int userId) {
-            mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
-        }
-    };
+                @Override
+                public void onLockoutReset(int userId) {
+                    mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
+                }
+            };
 
     private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
         @Override
@@ -332,29 +338,20 @@
             Slog.e(TAG, "Unable to register user switch observer");
         }
 
-        final IBiometricsFingerprint daemon = getDaemon();
-        boolean isUdfps = false;
-        android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint extension =
-                android.hardware.biometrics.fingerprint.V2_3.IBiometricsFingerprint.castFrom(
-                        daemon);
-        if (extension != null) {
-            try {
-                isUdfps = extension.isUdfps(sensorId);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception while quering udfps", e);
-                isUdfps = false;
-            }
-        }
+        // TODO(b/179175438): Remove this code block after transition to AIDL.
+        // The existence of config_udfps_sensor_props indicates that the sensor is UDFPS.
+        mIsUdfps = !ArrayUtils.isEmpty(
+                mContext.getResources().getIntArray(R.array.config_udfps_sensor_props));
 
         final @FingerprintSensorProperties.SensorType int sensorType =
-                isUdfps ? FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+                mIsUdfps ? FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
                         : FingerprintSensorProperties.TYPE_REAR;
         // resetLockout is controlled by the framework, so hardwareAuthToken is not required
         final boolean resetLockoutRequiresHardwareAuthToken = false;
         final int maxEnrollmentsPerUser = mContext.getResources()
                 .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
 
-        mSensorProperties = new FingerprintSensorPropertiesInternal(sensorId,
+        mSensorProperties = new FingerprintSensorPropertiesInternal(context, sensorId,
                 Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
                 sensorType, resetLockoutRequiresHardwareAuthToken);
     }
@@ -397,7 +394,8 @@
         });
     }
 
-    private synchronized IBiometricsFingerprint getDaemon() {
+    @VisibleForTesting
+    synchronized IBiometricsFingerprint getDaemon() {
         if (mTestHalEnabled) {
             final TestHal testHal = new TestHal();
             testHal.setNotify(mHalResultController);
@@ -550,7 +548,7 @@
     public void scheduleEnroll(int sensorId, @NonNull IBinder token,
             @NonNull byte[] hardwareAuthToken, int userId,
             @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
-            boolean shouldLogMetrics) {
+            @FingerprintManager.EnrollReason int enrollReason) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -558,7 +556,7 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
                     ENROLL_TIMEOUT_SEC, mSensorProperties.sensorId, mUdfpsOverlayController,
-                    shouldLogMetrics);
+                    enrollReason);
             mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
                 @Override
                 public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
@@ -628,13 +626,13 @@
             @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
             @NonNull String opPackageName) {
         mHandler.post(() -> {
-           scheduleUpdateActiveUserWithoutHandler(userId);
+            scheduleUpdateActiveUserWithoutHandler(userId);
 
-           final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
-                   mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
-                   userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
-                   mSensorProperties.sensorId, mAuthenticatorIds);
-           mScheduler.scheduleClientMonitor(client);
+            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
+                    userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
+                    mSensorProperties.sensorId, mAuthenticatorIds);
+            mScheduler.scheduleClientMonitor(client);
         });
     }
 
@@ -778,6 +776,17 @@
     }
 
     @Override
+    public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
+            @NonNull IInvalidationCallback callback) {
+        // TODO (b/179101888): Remove this temporary workaround.
+        try {
+            callback.onCompleted();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to complete InvalidateAuthenticatorId");
+        }
+    }
+
+    @Override
     public void dumpInternal(int sensorId, @NonNull PrintWriter pw) {
         PerformanceTracker performanceTracker =
                 PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
@@ -785,6 +794,7 @@
         JSONObject dump = new JSONObject();
         try {
             dump.put("service", TAG);
+            dump.put("isUdfps", mIsUdfps);
 
             JSONArray sets = new JSONArray();
             for (UserInfo user : UserManager.get(mContext).getUsers()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 8493af1..33db64c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -24,6 +24,7 @@
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -46,6 +47,7 @@
     private static final String TAG = "FingerprintEnrollClient";
 
     @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+    private final @FingerprintManager.EnrollReason int mEnrollReason;
 
     FingerprintEnrollClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
@@ -53,12 +55,16 @@
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            boolean shouldLogMetrics) {
+            @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
                 true /* shouldVibrate */);
         mUdfpsOverlayController = udfpsOverlayController;
-        setShouldLog(shouldLogMetrics);
+
+        mEnrollReason = enrollReason;
+        if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
+            setShouldLog(false);
+        }
     }
 
     @Override
@@ -76,7 +82,8 @@
 
     @Override
     protected void startHalOperation() {
-        UdfpsHelper.showUdfpsOverlay(getSensorId(), IUdfpsOverlayController.REASON_ENROLL,
+        UdfpsHelper.showUdfpsOverlay(getSensorId(),
+                UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
                 mUdfpsOverlayController);
         try {
             // GroupId was never used. In fact, groupId is always the same as userId.
@@ -107,12 +114,23 @@
     public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
         super.onEnrollResult(identifier, remaining);
 
+        UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController);
+
         if (remaining == 0) {
             UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
         }
     }
 
     @Override
+    public void onAcquired(int acquiredInfo, int vendorCode) {
+        super.onAcquired(acquiredInfo, vendorCode);
+
+        if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
+            UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController);
+        }
+    }
+
+    @Override
     public void onError(int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
index 11ffbb2..db7f4bc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java
@@ -62,13 +62,7 @@
         super.start(callback);
 
         if (mCurrentUserId == getTargetUserId()) {
-            Slog.d(TAG, "Already user: " + mCurrentUserId + ", refreshing authenticatorId");
-            try {
-                mAuthenticatorIds.put(getTargetUserId(), mHasEnrolledBiometrics
-                        ? getFreshDaemon().getAuthenticatorId() : 0L);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to refresh authenticatorId", e);
-            }
+            Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
             callback.onClientFinished(this, true /* success */);
             return;
         }
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 9ba957e..e3757df 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -23,8 +23,11 @@
 
 import com.android.internal.compat.CompatibilityChangeInfo;
 import com.android.server.compat.config.Change;
+import com.android.server.compat.overrides.ChangeOverrides;
+import com.android.server.compat.overrides.OverrideValue;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -253,6 +256,71 @@
         return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName);
     }
 
+    /**
+     * Checks whether a change has any package overrides.
+     * @return true if the change has at least one deferred override
+     */
+    boolean hasAnyPackageOverride() {
+        return mDeferredOverrides != null && !mDeferredOverrides.isEmpty();
+    }
+
+    /**
+     * Checks whether a change has any deferred overrides.
+     * @return true if the change has at least one deferred override
+     */
+    boolean hasAnyDeferredOverride() {
+        return mPackageOverrides != null && !mPackageOverrides.isEmpty();
+    }
+
+    void loadOverrides(ChangeOverrides changeOverrides) {
+        if (mDeferredOverrides == null) {
+            mDeferredOverrides = new HashMap<>();
+        }
+        mDeferredOverrides.clear();
+        for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
+            mDeferredOverrides.put(override.getPackageName(), override.getEnabled());
+        }
+
+        if (mPackageOverrides == null) {
+            mPackageOverrides = new HashMap<>();
+        }
+        mPackageOverrides.clear();
+        for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) {
+            mPackageOverrides.put(override.getPackageName(), override.getEnabled());
+        }
+    }
+
+    ChangeOverrides saveOverrides() {
+        if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) {
+            return null;
+        }
+        ChangeOverrides changeOverrides = new ChangeOverrides();
+        changeOverrides.setChangeId(getId());
+        ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred();
+        List<OverrideValue> deferredList = deferredOverrides.getOverrideValue();
+        if (mDeferredOverrides != null) {
+            for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) {
+                OverrideValue override = new OverrideValue();
+                override.setPackageName(entry.getKey());
+                override.setEnabled(entry.getValue());
+                deferredList.add(override);
+            }
+        }
+        changeOverrides.setDeferred(deferredOverrides);
+        ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated();
+        List<OverrideValue> validatedList = validatedOverrides.getOverrideValue();
+        if (mPackageOverrides != null) {
+            for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) {
+                OverrideValue override = new OverrideValue();
+                override.setPackageName(entry.getKey());
+                override.setEnabled(entry.getValue());
+                validatedList.add(override);
+            }
+        }
+        changeOverrides.setValidated(validatedOverrides);
+        return changeOverrides;
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("ChangeId(")
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 69686a2..6b77b9d 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -34,7 +34,10 @@
 import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
-import com.android.server.compat.config.XmlParser;
+import com.android.server.compat.config.Config;
+import com.android.server.compat.overrides.ChangeOverrides;
+import com.android.server.compat.overrides.Overrides;
+import com.android.server.compat.overrides.XmlWriter;
 import com.android.server.pm.ApexManager;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -60,11 +63,14 @@
 final class CompatConfig {
 
     private static final String TAG = "CompatConfig";
+    private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat";
+    private static final String OVERRIDES_FILE = "compat_framework_overrides.xml";
 
     @GuardedBy("mChanges")
     private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
 
     private final OverrideValidatorImpl mOverrideValidator;
+    private File mOverridesFile;
 
     @VisibleForTesting
     CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
@@ -83,6 +89,8 @@
             config.initConfigFromLib(Environment.buildPath(
                     apex.apexDirectory, "etc", "compatconfig"));
         }
+        File overridesFile = new File(APP_COMPAT_DATA_DIR, OVERRIDES_FILE);
+        config.initOverrides(overridesFile);
         config.invalidateCache();
         return config;
     }
@@ -202,6 +210,17 @@
      * @throws IllegalStateException if overriding is not allowed
      */
     boolean addOverride(long changeId, String packageName, boolean enabled) {
+        boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled);
+        saveOverrides();
+        invalidateCache();
+        return alreadyKnown;
+    }
+
+    /**
+     * Unsafe version of {@link #addOverride(long, String, boolean)}.
+     * It does not invalidate the cache nor save the overrides.
+     */
+    private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) {
         boolean alreadyKnown = true;
         OverrideAllowedState allowedState =
                 mOverrideValidator.getOverrideAllowedState(changeId, packageName);
@@ -224,7 +243,6 @@
                     throw new IllegalStateException("Should only be able to override changes that "
                             + "are allowed or can be deferred.");
             }
-            invalidateCache();
         }
         return alreadyKnown;
     }
@@ -282,6 +300,17 @@
      * @return {@code true} if an override existed;
      */
     boolean removeOverride(long changeId, String packageName) {
+        boolean overrideExists = removeOverrideUnsafe(changeId, packageName);
+        saveOverrides();
+        invalidateCache();
+        return overrideExists;
+    }
+
+    /**
+     * Unsafe version of {@link #removeOverride(long, String)}.
+     * It does not invalidate the cache nor save the overrides.
+     */
+    private boolean removeOverrideUnsafe(long changeId, String packageName) {
         boolean overrideExists = false;
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
@@ -300,7 +329,6 @@
                 }
             }
         }
-        invalidateCache();
         return overrideExists;
     }
 
@@ -315,12 +343,13 @@
     void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
         synchronized (mChanges) {
             for (Long changeId : overrides.enabledChanges()) {
-                addOverride(changeId, packageName, true);
+                addOverrideUnsafe(changeId, packageName, true);
             }
             for (Long changeId : overrides.disabledChanges()) {
-                addOverride(changeId, packageName, false);
+                addOverrideUnsafe(changeId, packageName, false);
 
             }
+            saveOverrides();
             invalidateCache();
         }
     }
@@ -337,8 +366,9 @@
         synchronized (mChanges) {
             for (int i = 0; i < mChanges.size(); ++i) {
                 CompatChange change = mChanges.valueAt(i);
-                removeOverride(change.getId(), packageName);
+                removeOverrideUnsafe(change.getId(), packageName);
             }
+            saveOverrides();
             invalidateCache();
         }
     }
@@ -372,8 +402,10 @@
     int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
         for (long changeId : changes) {
-            addOverride(changeId, packageName, true);
+            addOverrideUnsafe(changeId, packageName, true);
         }
+        saveOverrides();
+        invalidateCache();
         return changes.length;
     }
 
@@ -386,8 +418,10 @@
     int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
         long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
         for (long changeId : changes) {
-            addOverride(changeId, packageName, false);
+            addOverrideUnsafe(changeId, packageName, false);
         }
+        saveOverrides();
+        invalidateCache();
         return changes.length;
     }
 
@@ -494,7 +528,8 @@
 
     private void readConfig(File configFile) {
         try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
-            for (Change change : XmlParser.read(in).getCompatChange()) {
+            Config config = com.android.server.compat.config.XmlParser.read(in);
+            for (Change change : config.getCompatChange()) {
                 Slog.d(TAG, "Adding: " + change.toString());
                 addChange(new CompatChange(change));
             }
@@ -503,6 +538,65 @@
         }
     }
 
+    void initOverrides(File overridesFile) {
+        if (!overridesFile.exists()) {
+            mOverridesFile = overridesFile;
+            // There have not been any overrides added yet.
+            return;
+        }
+
+        try (InputStream in = new BufferedInputStream(new FileInputStream(overridesFile))) {
+            Overrides overrides = com.android.server.compat.overrides.XmlParser.read(in);
+            for (ChangeOverrides changeOverrides : overrides.getChangeOverrides()) {
+                long changeId = changeOverrides.getChangeId();
+                CompatChange compatChange = mChanges.get(changeId);
+                if (compatChange == null) {
+                    Slog.w(TAG, "Change ID " + changeId + " not found. "
+                            + "Skipping overrides for it.");
+                    continue;
+                }
+                compatChange.loadOverrides(changeOverrides);
+            }
+        } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
+            Slog.w(TAG, "Error processing " + overridesFile + " " + e.toString());
+            return;
+        }
+        mOverridesFile = overridesFile;
+    }
+
+    /**
+     * Persist compat framework overrides to /data/misc/appcompat/compat_framework_overrides.xml
+     */
+    void saveOverrides() {
+        if (mOverridesFile == null) {
+            return;
+        }
+        synchronized (mChanges) {
+            // Create the file if it doesn't already exist
+            try {
+                mOverridesFile.createNewFile();
+            } catch (IOException e) {
+                Slog.e(TAG, "Could not create override config file: " + e.toString());
+                return;
+            }
+            try (PrintWriter out = new PrintWriter(mOverridesFile)) {
+                XmlWriter writer = new XmlWriter(out);
+                Overrides overrides = new Overrides();
+                List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides();
+                for (int idx = 0; idx < mChanges.size(); ++idx) {
+                    CompatChange c = mChanges.valueAt(idx);
+                    ChangeOverrides changeOverrides = c.saveOverrides();
+                    if (changeOverrides != null) {
+                        changeOverridesList.add(changeOverrides);
+                    }
+                }
+                XmlWriter.write(writer, overrides);
+            } catch (IOException e) {
+                Slog.e(TAG, e.toString());
+            }
+        }
+    }
+
     IOverrideValidator getOverrideValidator() {
         return mOverrideValidator;
     }
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index c70bb08..43d9ade 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -32,6 +32,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.IDnsResolver;
+import android.net.InetAddresses;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.ResolverOptionsParcel;
@@ -190,7 +191,7 @@
             for (String ipAddress : ipAddresses) {
                 try {
                     latestDnses.add(new Pair(hostname,
-                            InetAddress.parseNumericAddress(ipAddress)));
+                            InetAddresses.parseNumericAddress(ipAddress)));
                 } catch (IllegalArgumentException e) {}
             }
             // Remove <hostname, ipAddress> pairs that should not be tracked.
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 952193b..46c49e7 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -34,9 +34,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.net.module.util.NetworkStackConstants;
 import com.android.server.net.BaseNetworkObserver;
 
-import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.util.Objects;
 
@@ -433,7 +433,7 @@
         // clat IPv4 address itself (for those apps, it doesn't matter what
         // the IP of the gateway is, only that there is one).
         RouteInfo ipv4Default = new RouteInfo(
-                new LinkAddress(Inet4Address.ANY, 0),
+                new LinkAddress(NetworkStackConstants.IPV4_ADDR_ANY, 0),
                 clatAddress.getAddress(), mIface);
         stacked.addRoute(ipv4Default);
         stacked.addLinkAddress(clatAddress);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index ab0360b..bff1a5c 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -57,6 +57,7 @@
 import com.android.server.ConnectivityService;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Objects;
@@ -121,6 +122,13 @@
 //
 // When ConnectivityService disconnects a network:
 // -----------------------------------------------
+// If a network is just connected, ConnectivityService will think it will be used soon, but might
+// not be used. Thus, a 5s timer will be held to prevent the network being torn down immediately.
+// This "nascent" state is implemented by the "lingering" logic below without relating to any
+// request, and is used in some cases where network requests race with network establishment. The
+// nascent state ends when the 5-second timer fires, or as soon as the network satisfies a
+// request, whichever is earlier. In this state, the network is considered in the background.
+//
 // If a network has no chance of satisfying any requests (even if it were to become validated
 // and enter state #5), ConnectivityService will disconnect the NetworkAgent's AsyncChannel.
 //
@@ -209,23 +217,23 @@
     // network is taken down.  This usually only happens to the default network. Lingering ends with
     // either the linger timeout expiring and the network being taken down, or the network
     // satisfying a request again.
-    public static class LingerTimer implements Comparable<LingerTimer> {
+    public static class InactivityTimer implements Comparable<InactivityTimer> {
         public final int requestId;
         public final long expiryMs;
 
-        public LingerTimer(int requestId, long expiryMs) {
+        public InactivityTimer(int requestId, long expiryMs) {
             this.requestId = requestId;
             this.expiryMs = expiryMs;
         }
         public boolean equals(Object o) {
-            if (!(o instanceof LingerTimer)) return false;
-            LingerTimer other = (LingerTimer) o;
+            if (!(o instanceof InactivityTimer)) return false;
+            InactivityTimer other = (InactivityTimer) o;
             return (requestId == other.requestId) && (expiryMs == other.expiryMs);
         }
         public int hashCode() {
             return Objects.hash(requestId, expiryMs);
         }
-        public int compareTo(LingerTimer other) {
+        public int compareTo(InactivityTimer other) {
             return (expiryMs != other.expiryMs) ?
                     Long.compare(expiryMs, other.expiryMs) :
                     Integer.compare(requestId, other.requestId);
@@ -268,30 +276,32 @@
      */
     public static final int ARG_AGENT_SUCCESS = 1;
 
-    // All linger timers for this network, sorted by expiry time. A linger timer is added whenever
+    // All inactivity timers for this network, sorted by expiry time. A timer is added whenever
     // a request is moved to a network with a better score, regardless of whether the network is or
-    // was lingering or not.
+    // was lingering or not. An inactivity timer is also added when a network connects
+    // without immediately satisfying any requests.
     // TODO: determine if we can replace this with a smaller or unsorted data structure. (e.g.,
     // SparseLongArray) combined with the timestamp of when the last timer is scheduled to fire.
-    private final SortedSet<LingerTimer> mLingerTimers = new TreeSet<>();
+    private final SortedSet<InactivityTimer> mInactivityTimers = new TreeSet<>();
 
-    // For fast lookups. Indexes into mLingerTimers by request ID.
-    private final SparseArray<LingerTimer> mLingerTimerForRequest = new SparseArray<>();
+    // For fast lookups. Indexes into mInactivityTimers by request ID.
+    private final SparseArray<InactivityTimer> mInactivityTimerForRequest = new SparseArray<>();
 
-    // Linger expiry timer. Armed whenever mLingerTimers is non-empty, regardless of whether the
-    // network is lingering or not. Always set to the expiry of the LingerTimer that expires last.
-    // When the timer fires, all linger state is cleared, and if the network has no requests, it is
-    // torn down.
-    private WakeupMessage mLingerMessage;
+    // Inactivity expiry timer. Armed whenever mInactivityTimers is non-empty, regardless of
+    // whether the network is inactive or not. Always set to the expiry of the mInactivityTimers
+    // that expires last. When the timer fires, all inactivity state is cleared, and if the network
+    // has no requests, it is torn down.
+    private WakeupMessage mInactivityMessage;
 
-    // Linger expiry. Holds the expiry time of the linger timer, or 0 if the timer is not armed.
-    private long mLingerExpiryMs;
+    // Inactivity expiry. Holds the expiry time of the inactivity timer, or 0 if the timer is not
+    // armed.
+    private long mInactivityExpiryMs;
 
-    // Whether the network is lingering or not. Must be maintained separately from the above because
+    // Whether the network is inactive or not. Must be maintained separately from the above because
     // it depends on the state of other networks and requests, which only ConnectivityService knows.
     // (Example: we don't linger a network if it would become the best for a NetworkRequest if it
     // validated).
-    private boolean mLingering;
+    private boolean mInactive;
 
     // This represents the quality of the network with no clear scale.
     private int mScore;
@@ -329,7 +339,7 @@
     private final QosCallbackTracker mQosCallbackTracker;
 
     public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info,
-            LinkProperties lp, NetworkCapabilities nc, int score, Context context,
+            @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, int score, Context context,
             Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
             IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber,
             int creatorUid, QosCallbackTracker qosCallbackTracker) {
@@ -894,20 +904,25 @@
 
     /**
      * Sets the specified requestId to linger on this network for the specified time. Called by
-     * ConnectivityService when the request is moved to another network with a higher score.
+     * ConnectivityService when the request is moved to another network with a higher score, or
+     * when a network is newly created.
+     *
+     * @param requestId The requestId of the request that no longer need to be served by this
+     *                  network. Or {@link NetworkRequest.REQUEST_ID_NONE} if this is the
+     *                  {@code LingerTimer} for a newly created network.
      */
     public void lingerRequest(int requestId, long now, long duration) {
-        if (mLingerTimerForRequest.get(requestId) != null) {
+        if (mInactivityTimerForRequest.get(requestId) != null) {
             // Cannot happen. Once a request is lingering on a particular network, we cannot
             // re-linger it unless that network becomes the best for that request again, in which
             // case we should have unlingered it.
             Log.wtf(TAG, toShortString() + ": request " + requestId + " already lingered");
         }
         final long expiryMs = now + duration;
-        LingerTimer timer = new LingerTimer(requestId, expiryMs);
-        if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + toShortString());
-        mLingerTimers.add(timer);
-        mLingerTimerForRequest.put(requestId, timer);
+        InactivityTimer timer = new InactivityTimer(requestId, expiryMs);
+        if (VDBG) Log.d(TAG, "Adding InactivityTimer " + timer + " to " + toShortString());
+        mInactivityTimers.add(timer);
+        mInactivityTimerForRequest.put(requestId, timer);
     }
 
     /**
@@ -915,23 +930,25 @@
      * Returns true if the given requestId was lingering on this network, false otherwise.
      */
     public boolean unlingerRequest(int requestId) {
-        LingerTimer timer = mLingerTimerForRequest.get(requestId);
+        InactivityTimer timer = mInactivityTimerForRequest.get(requestId);
         if (timer != null) {
-            if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + toShortString());
-            mLingerTimers.remove(timer);
-            mLingerTimerForRequest.remove(requestId);
+            if (VDBG) {
+                Log.d(TAG, "Removing InactivityTimer " + timer + " from " + toShortString());
+            }
+            mInactivityTimers.remove(timer);
+            mInactivityTimerForRequest.remove(requestId);
             return true;
         }
         return false;
     }
 
-    public long getLingerExpiry() {
-        return mLingerExpiryMs;
+    public long getInactivityExpiry() {
+        return mInactivityExpiryMs;
     }
 
-    public void updateLingerTimer() {
-        long newExpiry = mLingerTimers.isEmpty() ? 0 : mLingerTimers.last().expiryMs;
-        if (newExpiry == mLingerExpiryMs) return;
+    public void updateInactivityTimer() {
+        long newExpiry = mInactivityTimers.isEmpty() ? 0 : mInactivityTimers.last().expiryMs;
+        if (newExpiry == mInactivityExpiryMs) return;
 
         // Even if we're going to reschedule the timer, cancel it first. This is because the
         // semantics of WakeupMessage guarantee that if cancel is called then the alarm will
@@ -939,49 +956,65 @@
         // WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage
         // has already been dispatched, rescheduling to some time in the future won't stop it
         // from calling its callback immediately.
-        if (mLingerMessage != null) {
-            mLingerMessage.cancel();
-            mLingerMessage = null;
+        if (mInactivityMessage != null) {
+            mInactivityMessage.cancel();
+            mInactivityMessage = null;
         }
 
         if (newExpiry > 0) {
-            mLingerMessage = new WakeupMessage(
+            mInactivityMessage = new WakeupMessage(
                     mContext, mHandler,
                     "NETWORK_LINGER_COMPLETE." + network.getNetId() /* cmdName */,
                     EVENT_NETWORK_LINGER_COMPLETE /* cmd */,
                     0 /* arg1 (unused) */, 0 /* arg2 (unused) */,
                     this /* obj (NetworkAgentInfo) */);
-            mLingerMessage.schedule(newExpiry);
+            mInactivityMessage.schedule(newExpiry);
         }
 
-        mLingerExpiryMs = newExpiry;
+        mInactivityExpiryMs = newExpiry;
     }
 
-    public void linger() {
-        mLingering = true;
+    public void setInactive() {
+        mInactive = true;
     }
 
-    public void unlinger() {
-        mLingering = false;
+    public void unsetInactive() {
+        mInactive = false;
+    }
+
+    public boolean isInactive() {
+        return mInactive;
     }
 
     public boolean isLingering() {
-        return mLingering;
+        return mInactive && !isNascent();
     }
 
-    public void clearLingerState() {
-        if (mLingerMessage != null) {
-            mLingerMessage.cancel();
-            mLingerMessage = null;
+    /**
+     * Return whether the network is just connected and about to be torn down because of not
+     * satisfying any request.
+     */
+    public boolean isNascent() {
+        return mInactive && mInactivityTimers.size() == 1
+                && mInactivityTimers.first().requestId == NetworkRequest.REQUEST_ID_NONE;
+    }
+
+    public void clearInactivityState() {
+        if (mInactivityMessage != null) {
+            mInactivityMessage.cancel();
+            mInactivityMessage = null;
         }
-        mLingerTimers.clear();
-        mLingerTimerForRequest.clear();
-        updateLingerTimer();  // Sets mLingerExpiryMs, cancels and nulls out mLingerMessage.
-        mLingering = false;
+        mInactivityTimers.clear();
+        mInactivityTimerForRequest.clear();
+        // Sets mInactivityExpiryMs, cancels and nulls out mInactivityMessage.
+        updateInactivityTimer();
+        mInactive = false;
     }
 
-    public void dumpLingerTimers(PrintWriter pw) {
-        for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
+    public void dumpInactivityTimers(PrintWriter pw) {
+        for (InactivityTimer timer : mInactivityTimers) {
+            pw.println(timer);
+        }
     }
 
     /**
@@ -1015,7 +1048,7 @@
                 + "network{" + network + "}  handle{" + network.getNetworkHandle() + "}  ni{"
                 + networkInfo.toShortString() + "} "
                 + "  Score{" + getCurrentScore() + "} "
-                + (isLingering() ? " lingering" : "")
+                + (isNascent() ? " nascent" : (isLingering() ? " lingering" : ""))
                 + (everValidated ? " everValidated" : "")
                 + (lastValidated ? " lastValidated" : "")
                 + (partialConnectivity ? " partialConnectivity" : "")
@@ -1025,6 +1058,8 @@
                 + (networkAgentConfig.acceptUnvalidated ? " acceptUnvalidated" : "")
                 + (networkAgentConfig.acceptPartialConnectivity ? " acceptPartialConnectivity" : "")
                 + (clatd.isStarted() ? " clat{" + clatd + "} " : "")
+                + (declaredUnderlyingNetworks != null
+                        ? " underlying{" + Arrays.toString(declaredUnderlyingNetworks) + "}" : "")
                 + "  lp{" + linkProperties + "}"
                 + "  nc{" + networkCapabilities + "}"
                 + "}";
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index a7be657..5e6b9f3 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -686,7 +686,7 @@
 
             mHostname = hostname;
             mMeasurement.description = "DNS TLS dst{" + mTarget.getHostAddress() + "} hostname{"
-                    + TextUtils.emptyIfNull(mHostname) + "}";
+                    + (mHostname == null ? "" : mHostname) + "}";
         }
 
         private SSLSocket setupSSLSocket() throws IOException {
diff --git a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java b/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
index 5dc8c1a..aadaf4d 100644
--- a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
+++ b/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
@@ -16,7 +16,6 @@
 
 package com.android.server.connectivity;
 
-import android.annotation.NonNull;
 import android.annotation.WorkerThread;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -72,6 +71,10 @@
     private static final int DELAY_LONG = 4;
     private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
 
+    // Return values for #setCurrentProxyScriptUrl
+    public static final boolean DONT_SEND_BROADCAST = false;
+    public static final boolean DO_SEND_BROADCAST = true;
+
     private String mCurrentPac;
     @GuardedBy("mProxyLock")
     private volatile Uri mPacUrl = Uri.EMPTY;
@@ -90,7 +93,7 @@
     private volatile boolean mHasSentBroadcast;
     private volatile boolean mHasDownloaded;
 
-    private final Handler mConnectivityHandler;
+    private Handler mConnectivityHandler;
     private final int mProxyMessage;
 
     /**
@@ -99,13 +102,6 @@
     private final Object mProxyLock = new Object();
 
     /**
-     * Lock ensuring consistency between the values of mHasSentBroadcast, mHasDownloaded, the
-     * last URL and port, and the broadcast message being sent with the correct arguments.
-     * TODO : this should probably protect all instances of these variables
-     */
-    private final Object mBroadcastStateLock = new Object();
-
-    /**
      * Runnable to download PAC script.
      * The behavior relies on the assumption it always runs on mNetThread to guarantee that the
      * latest data fetched from mPacUrl is stored in mProxyService.
@@ -150,7 +146,7 @@
         }
     }
 
-    public PacProxyInstaller(@NonNull Context context, @NonNull Handler handler, int proxyMessage) {
+    public PacProxyInstaller(Context context, Handler handler, int proxyMessage) {
         mContext = context;
         mLastPort = -1;
         final HandlerThread netThread = new HandlerThread("android.pacproxyinstaller",
@@ -180,27 +176,31 @@
      * PacProxyInstaller will trigger a new broadcast when it is ready.
      *
      * @param proxy Proxy information that is about to be broadcast.
+     * @return Returns whether the broadcast should be sent : either DO_ or DONT_SEND_BROADCAST
      */
-    public void setCurrentProxyScriptUrl(@NonNull ProxyInfo proxy) {
-        synchronized (mBroadcastStateLock) {
-            if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
-                if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) return;
-                mPacUrl = proxy.getPacFileUrl();
-                mCurrentDelay = DELAY_1;
-                mHasSentBroadcast = false;
-                mHasDownloaded = false;
-                getAlarmManager().cancel(mPacRefreshIntent);
-                bind();
-            } else {
-                getAlarmManager().cancel(mPacRefreshIntent);
-                synchronized (mProxyLock) {
-                    mPacUrl = Uri.EMPTY;
-                    mCurrentPac = null;
-                    if (mProxyService != null) {
-                        unbind();
-                    }
+    public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
+        if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
+            if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
+                // Allow to send broadcast, nothing to do.
+                return DO_SEND_BROADCAST;
+            }
+            mPacUrl = proxy.getPacFileUrl();
+            mCurrentDelay = DELAY_1;
+            mHasSentBroadcast = false;
+            mHasDownloaded = false;
+            getAlarmManager().cancel(mPacRefreshIntent);
+            bind();
+            return DONT_SEND_BROADCAST;
+        } else {
+            getAlarmManager().cancel(mPacRefreshIntent);
+            synchronized (mProxyLock) {
+                mPacUrl = Uri.EMPTY;
+                mCurrentPac = null;
+                if (mProxyService != null) {
+                    unbind();
                 }
             }
+            return DO_SEND_BROADCAST;
         }
     }
 
@@ -275,7 +275,6 @@
         getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent);
     }
 
-    @GuardedBy("mProxyLock")
     private void setCurrentProxyScript(String script) {
         if (mProxyService == null) {
             Log.e(TAG, "setCurrentProxyScript: no proxy service");
@@ -348,9 +347,6 @@
                             public void setProxyPort(int port) {
                                 if (mLastPort != -1) {
                                     // Always need to send if port changed
-                                    // TODO: Here lacks synchronization because this write cannot
-                                    // guarantee that it's visible from sendProxyIfNeeded() when
-                                    // it's called by a Runnable which is post by mNetThread.
                                     mHasSentBroadcast = false;
                                 }
                                 mLastPort = port;
@@ -390,15 +386,13 @@
         mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
     }
 
-    private void sendProxyIfNeeded() {
-        synchronized (mBroadcastStateLock) {
-            if (!mHasDownloaded || (mLastPort == -1)) {
-                return;
-            }
-            if (!mHasSentBroadcast) {
-                sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
-                mHasSentBroadcast = true;
-            }
+    private synchronized void sendProxyIfNeeded() {
+        if (!mHasDownloaded || (mLastPort == -1)) {
+            return;
+        }
+        if (!mHasSentBroadcast) {
+            sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
+            mHasSentBroadcast = true;
         }
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index d507b5f..8d21f6f 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -265,7 +265,10 @@
         for (Entry<Integer, Boolean> app : apps.entrySet()) {
             List<Integer> list = app.getValue() ? system : network;
             for (int user : users) {
-                list.add(UserHandle.getUid(user, app.getKey()));
+                final UserHandle handle = UserHandle.of(user);
+                if (handle == null) continue;
+
+                list.add(UserHandle.getUid(handle, app.getKey()));
             }
         }
         try {
@@ -550,7 +553,10 @@
         for (UidRange range : ranges) {
             for (int userId = range.getStartUser(); userId <= range.getEndUser(); userId++) {
                 for (int appId : appIds) {
-                    final int uid = UserHandle.getUid(userId, appId);
+                    final UserHandle handle = UserHandle.of(userId);
+                    if (handle == null) continue;
+
+                    final int uid = UserHandle.getUid(handle, appId);
                     if (range.contains(uid)) {
                         result.add(uid);
                     }
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index b618d2b..d83ff83 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -226,9 +226,9 @@
         final ProxyInfo defaultProxy = getDefaultProxy();
         final ProxyInfo proxyInfo = null != defaultProxy ?
                 defaultProxy : ProxyInfo.buildDirectProxy("", 0, Collections.emptyList());
-        mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo);
 
-        if (!shouldSendBroadcast(proxyInfo)) {
+        if (mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo)
+                == PacProxyInstaller.DONT_SEND_BROADCAST) {
             return;
         }
         if (DBG) Log.d(TAG, "sending Proxy Broadcast for " + proxyInfo);
@@ -244,13 +244,6 @@
         }
     }
 
-    private boolean shouldSendBroadcast(ProxyInfo proxy) {
-        if (Uri.EMPTY.equals(proxy.getPacFileUrl())) return false;
-        if (proxy.getPacFileUrl().equals(proxy.getPacFileUrl())
-                && (proxy.getPort() > 0)) return true;
-        return true;
-    }
-
     /**
      * Sets the global proxy in memory. Also writes the values to the global settings of the device.
      *
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index fb1e819..fc2c7e0 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -51,6 +51,7 @@
 import android.net.INetd;
 import android.net.INetworkManagementEventObserver;
 import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.IpSecManager;
 import android.net.IpSecManager.IpSecTunnelInterface;
@@ -70,6 +71,7 @@
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
+import android.net.UnderlyingNetworkInfo;
 import android.net.VpnManager;
 import android.net.VpnService;
 import android.net.ipsec.ike.ChildSessionCallback;
@@ -109,8 +111,8 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
+import com.android.net.module.util.NetworkStackConstants;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.net.BaseNetworkObserver;
@@ -203,6 +205,7 @@
     protected final NetworkCapabilities mNetworkCapabilities;
     private final SystemServices mSystemServices;
     private final Ikev2SessionCreator mIkev2SessionCreator;
+    private final UserManager mUserManager;
 
     /**
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
@@ -277,6 +280,10 @@
             return LocalServices.getService(DeviceIdleInternal.class);
         }
 
+        public PendingIntent getIntentForStatusPanel(Context context) {
+            return VpnConfig.getIntentForStatusPanel(context);
+        }
+
         public void sendArgumentsToDaemon(
                 final String daemon, final LocalSocket socket, final String[] arguments,
                 final RetryScheduler retryScheduler) throws IOException, InterruptedException {
@@ -327,7 +334,7 @@
         public InetAddress resolve(final String endpoint)
                 throws ExecutionException, InterruptedException {
             try {
-                return InetAddress.parseNumericAddress(endpoint);
+                return InetAddresses.parseNumericAddress(endpoint);
             } catch (IllegalArgumentException e) {
                 // Endpoint is not numeric : fall through and resolve
             }
@@ -405,6 +412,7 @@
         mLooper = looper;
         mSystemServices = systemServices;
         mIkev2SessionCreator = ikev2SessionCreator;
+        mUserManager = mContext.getSystemService(UserManager.class);
 
         mPackage = VpnConfig.LEGACY_VPN;
         mOwnerUID = getAppUid(mPackage, mUserId);
@@ -426,6 +434,7 @@
         mNetworkCapabilities = new NetworkCapabilities();
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
 
         loadAlwaysOnPackage(keyStore);
     }
@@ -1118,7 +1127,7 @@
 
         if (mConfig.dnsServers != null) {
             for (String dnsServer : mConfig.dnsServers) {
-                InetAddress address = InetAddress.parseNumericAddress(dnsServer);
+                InetAddress address = InetAddresses.parseNumericAddress(dnsServer);
                 lp.addDnsServer(address);
                 allowIPv4 |= address instanceof Inet4Address;
                 allowIPv6 |= address instanceof Inet6Address;
@@ -1128,10 +1137,12 @@
         lp.setHttpProxy(mConfig.proxyInfo);
 
         if (!allowIPv4) {
-            lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE));
+            lp.addRoute(new RouteInfo(new IpPrefix(
+                    NetworkStackConstants.IPV4_ADDR_ANY, 0), RTN_UNREACHABLE));
         }
         if (!allowIPv6) {
-            lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
+            lp.addRoute(new RouteInfo(new IpPrefix(
+                    NetworkStackConstants.IPV6_ADDR_ANY, 0), RTN_UNREACHABLE));
         }
 
         // Concatenate search domains into a string.
@@ -1430,7 +1441,7 @@
             final long token = Binder.clearCallingIdentity();
             List<UserInfo> users;
             try {
-                users = UserManager.get(mContext).getAliveUsers();
+                users = mUserManager.getAliveUsers();
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1514,7 +1525,7 @@
      */
     public void onUserAdded(int userId) {
         // If the user is restricted tie them to the parent user's VPN
-        UserInfo user = UserManager.get(mContext).getUserInfo(userId);
+        UserInfo user = mUserManager.getUserInfo(userId);
         if (user.isRestricted() && user.restrictedProfileParentId == mUserId) {
             synchronized(Vpn.this) {
                 final Set<UidRange> existingRanges = mNetworkCapabilities.getUids();
@@ -1542,7 +1553,7 @@
      */
     public void onUserRemoved(int userId) {
         // clean up if restricted
-        UserInfo user = UserManager.get(mContext).getUserInfo(userId);
+        UserInfo user = mUserManager.getUserInfo(userId);
         if (user.isRestricted() && user.restrictedProfileParentId == mUserId) {
             synchronized(Vpn.this) {
                 final Set<UidRange> existingRanges = mNetworkCapabilities.getUids();
@@ -1767,7 +1778,7 @@
     private void prepareStatusIntent() {
         final long token = Binder.clearCallingIdentity();
         try {
-            mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext);
+            mStatusIntent = mDeps.getIntentForStatusPanel(mContext);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -1816,18 +1827,15 @@
     }
 
     /**
-     * This method should only be called by ConnectivityService because it doesn't
-     * have enough data to fill VpnInfo.primaryUnderlyingIface field.
+     * This method should not be called if underlying interfaces field is needed, because it doesn't
+     * have enough data to fill VpnInfo.underlyingIfaces field.
      */
-    public synchronized VpnInfo getVpnInfo() {
+    public synchronized UnderlyingNetworkInfo getUnderlyingNetworkInfo() {
         if (!isRunningLocked()) {
             return null;
         }
 
-        VpnInfo info = new VpnInfo();
-        info.ownerUid = mOwnerUID;
-        info.vpnIface = mInterface;
-        return info;
+        return new UnderlyingNetworkInfo(mOwnerUID, mInterface, new ArrayList<>());
     }
 
     public synchronized boolean appliesToUid(int uid) {
@@ -1970,8 +1978,7 @@
 
     private void enforceNotRestrictedUser() {
         Binder.withCleanCallingIdentity(() -> {
-            final UserManager mgr = UserManager.get(mContext);
-            final UserInfo user = mgr.getUserInfo(mUserId);
+            final UserInfo user = mUserManager.getUserInfo(mUserId);
 
             if (user.isRestricted()) {
                 throw new SecurityException("Restricted users cannot configure VPNs");
@@ -1984,30 +1991,30 @@
      * secondary thread to perform connection work, returning quickly.
      *
      * Should only be called to respond to Binder requests as this enforces caller permission. Use
-     * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, LinkProperties)} to skip the
+     * {@link #startLegacyVpnPrivileged(VpnProfile, KeyStore, Network, LinkProperties)} to skip the
      * permission check only when the caller is trusted (or the call is initiated by the system).
      */
-    public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
+    public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, @Nullable Network underlying,
+            LinkProperties egress) {
         enforceControlPermission();
         final long token = Binder.clearCallingIdentity();
         try {
-            startLegacyVpnPrivileged(profile, keyStore, egress);
+            startLegacyVpnPrivileged(profile, keyStore, underlying, egress);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
     /**
-     * Like {@link #startLegacyVpn(VpnProfile, KeyStore, LinkProperties)}, but does not check
-     * permissions under the assumption that the caller is the system.
+     * Like {@link #startLegacyVpn(VpnProfile, KeyStore, Network, LinkProperties)}, but does not
+     * check permissions under the assumption that the caller is the system.
      *
      * Callers are responsible for checking permissions if needed.
      */
     public void startLegacyVpnPrivileged(VpnProfile profile, KeyStore keyStore,
-            LinkProperties egress) {
-        UserManager mgr = UserManager.get(mContext);
-        UserInfo user = mgr.getUserInfo(mUserId);
-        if (user.isRestricted() || mgr.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
+            @Nullable Network underlying, @NonNull LinkProperties egress) {
+        UserInfo user = mUserManager.getUserInfo(mUserId);
+        if (user.isRestricted() || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN,
                     new UserHandle(mUserId))) {
             throw new SecurityException("Restricted users cannot establish VPNs");
         }
@@ -2130,6 +2137,9 @@
         config.session = profile.name;
         config.isMetered = false;
         config.proxyInfo = profile.proxy;
+        if (underlying != null) {
+            config.underlyingNetworks = new Network[] { underlying };
+        }
 
         config.addLegacyRoutes(profile.routes);
         if (!profile.dnsServers.isEmpty()) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
new file mode 100644
index 0000000..e496d77
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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.devicestate;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * A state of the device defined by the {@link DeviceStateProvider} and managed by the
+ * {@link DeviceStateManagerService}.
+ * <p>
+ * Device state is an abstract concept that allows mapping the current state of the device to the
+ * state of the system. This is useful for variable-state devices, like foldable or rollable
+ * devices, that can be configured by users into differing hardware states, which each may have a
+ * different expected use case.
+ *
+ * @see DeviceStateProvider
+ * @see DeviceStateManagerService
+ */
+public final class DeviceState {
+    /** Unique identifier for the device state. */
+    @IntRange(from = 0)
+    private final int mIdentifier;
+
+    /** String description of the device state. */
+    @NonNull
+    private final String mName;
+
+    public DeviceState(@IntRange(from = 0) int identifier,
+            @NonNull String name) {
+        if (identifier < 0) {
+            throw new IllegalArgumentException("Identifier must be greater than or equal to zero.");
+        }
+        mIdentifier = identifier;
+        mName = name;
+    }
+
+    /** Returns the unique identifier for the device state. */
+    @IntRange(from = 0)
+    public int getIdentifier() {
+        return mIdentifier;
+    }
+
+    /** Returns a string description of the device state. */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        DeviceState that = (DeviceState) o;
+        return mIdentifier == that.mIdentifier
+                && Objects.equals(mName, that.mName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIdentifier, mName);
+    }
+}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index d7dcbde..984a176 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,11 +17,13 @@
 package com.android.server.devicestate;
 
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
-import android.content.pm.PackageManager;
+import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.IDeviceStateManager;
 import android.hardware.devicestate.IDeviceStateManagerCallback;
 import android.os.Binder;
@@ -29,7 +31,8 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
-import android.util.IntArray;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -42,7 +45,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Optional;
 
 /**
  * A system service that manages the state of a device with user-configurable hardware like a
@@ -61,8 +64,12 @@
  * the {@link DeviceStateProvider} to modify the current device state and communicating with the
  * {@link DeviceStatePolicy policy} to ensure the system is configured to match the requested state.
  * </p>
+ * The service also provides the {@link DeviceStateManager} API allowing clients to listen for
+ * changes in device state and submit requests to override the device state provided by the
+ * {@link DeviceStateProvider}.
  *
  * @see DeviceStatePolicy
+ * @see DeviceStateManager
  */
 public final class DeviceStateManagerService extends SystemService {
     private static final String TAG = "DeviceStateManagerService";
@@ -74,30 +81,39 @@
     @NonNull
     private final BinderService mBinderService;
 
+    // All supported device states keyed by identifier.
     @GuardedBy("mLock")
-    private IntArray mSupportedDeviceStates;
+    private SparseArray<DeviceState> mDeviceStates = new SparseArray<>();
 
-    // The current committed device state.
+    // The current committed device state. The default of UNSET will be replaced by
+    // the current state after the initial callback from the DeviceStateProvider.
     @GuardedBy("mLock")
-    private int mCommittedState = INVALID_DEVICE_STATE;
+    @NonNull
+    private DeviceState mCommittedState = new DeviceState(0, "UNSET");
     // The device state that is currently awaiting callback from the policy to be committed.
     @GuardedBy("mLock")
-    private int mPendingState = INVALID_DEVICE_STATE;
+    @NonNull
+    private Optional<DeviceState> mPendingState = Optional.empty();
     // Whether or not the policy is currently waiting to be notified of the current pending state.
     @GuardedBy("mLock")
     private boolean mIsPolicyWaitingForState = false;
-    // The device state that is currently requested and is next to be configured and committed.
-    // Can be overwritten by an override state value if requested.
-    @GuardedBy("mLock")
-    private int mRequestedState = INVALID_DEVICE_STATE;
-    // The most recently requested override state, or INVALID_DEVICE_STATE if no override is
-    // requested.
-    @GuardedBy("mLock")
-    private int mRequestedOverrideState = INVALID_DEVICE_STATE;
 
-    // List of registered callbacks indexed by process id.
+    // The device state that is set by the device state provider.
     @GuardedBy("mLock")
-    private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
+    @NonNull
+    private Optional<DeviceState> mBaseState = Optional.empty();
+
+    // List of processes registered to receive notifications about changes to device state and
+    // request status indexed by process id.
+    @GuardedBy("mLock")
+    private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
+    // List of override requests with the highest precedence request at the end.
+    @GuardedBy("mLock")
+    private final ArrayList<OverrideRequestRecord> mRequestRecords = new ArrayList<>();
+    // Set of override requests that are pending a call to notifyStatusIfNeeded() to be notified
+    // of a change in status.
+    @GuardedBy("mLock")
+    private final ArraySet<OverrideRequestRecord> mRequestsPendingStatusChange = new ArraySet<>();
 
     public DeviceStateManagerService(@NonNull Context context) {
         this(context, new DeviceStatePolicyImpl(context));
@@ -122,79 +138,74 @@
      *
      * @see #getPendingState()
      */
-    int getCommittedState() {
+    @NonNull
+    DeviceState getCommittedState() {
         synchronized (mLock) {
             return mCommittedState;
         }
     }
 
     /**
-     * Returns the state the system is currently configuring, or {@link #INVALID_DEVICE_STATE} if
-     * the system is not in the process of configuring a state.
+     * Returns the state the system is currently configuring, or {@link Optional#empty()} if the
+     * system is not in the process of configuring a state.
      */
     @VisibleForTesting
-    int getPendingState() {
+    @NonNull
+    Optional<DeviceState> getPendingState() {
         synchronized (mLock) {
             return mPendingState;
         }
     }
 
     /**
-     * Returns the requested state. The service will configure the device to match the requested
-     * state when possible.
+     * Returns the base state. The service will configure the device to match the base state when
+     * there is no active request to override the base state.
+     *
+     * @see #getOverrideState()
      */
-    int getRequestedState() {
+    @NonNull
+    Optional<DeviceState> getBaseState() {
         synchronized (mLock) {
-            return mRequestedState;
+            return mBaseState;
         }
     }
 
     /**
-     * Overrides the current device state with the provided state.
-     *
-     * @return {@code true} if the override state is valid and supported, {@code false} otherwise.
+     * Returns the current override state, or {@link Optional#empty()} if no override state is
+     * requested. If an override states is present, the returned state will take precedence over
+     * the base state returned from {@link #getBaseState()}.
      */
-    boolean setOverrideState(int overrideState) {
-        if (getContext().checkCallingOrSelfPermission(CONTROL_DEVICE_STATE)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Must hold permission " + CONTROL_DEVICE_STATE);
-        }
-
+    @NonNull
+    Optional<DeviceState> getOverrideState() {
         synchronized (mLock) {
-            if (overrideState != INVALID_DEVICE_STATE && !isSupportedStateLocked(overrideState)) {
-                return false;
+            if (mRequestRecords.isEmpty()) {
+                return Optional.empty();
             }
 
-            mRequestedOverrideState = overrideState;
-            updatePendingStateLocked();
-        }
-
-        notifyPolicyIfNeeded();
-        return true;
-    }
-
-    /**
-     * Clears an override state set with {@link #setOverrideState(int)}.
-     */
-    void clearOverrideState() {
-        setOverrideState(INVALID_DEVICE_STATE);
-    }
-
-    /**
-     * Returns the current requested override state, or {@link #INVALID_DEVICE_STATE} is no override
-     * state is requested.
-     */
-    int getOverrideState() {
-        synchronized (mLock) {
-            return mRequestedOverrideState;
+            OverrideRequestRecord topRequest = mRequestRecords.get(mRequestRecords.size() - 1);
+            return Optional.of(topRequest.mRequestedState);
         }
     }
 
     /** Returns the list of currently supported device states. */
-    int[] getSupportedStates() {
+    DeviceState[] getSupportedStates() {
         synchronized (mLock) {
-            // Copy array to prevent external modification of internal state.
-            return Arrays.copyOf(mSupportedDeviceStates.toArray(), mSupportedDeviceStates.size());
+            DeviceState[] supportedStates = new DeviceState[mDeviceStates.size()];
+            for (int i = 0; i < supportedStates.length; i++) {
+                supportedStates[i] = mDeviceStates.valueAt(i);
+            }
+            return supportedStates;
+        }
+    }
+
+    /** Returns the list of currently supported device state identifiers. */
+    private int[] getSupportedStateIdentifiers() {
+        synchronized (mLock) {
+            int[] supportedStates = new int[mDeviceStates.size()];
+            for (int i = 0; i < supportedStates.length; i++) {
+                supportedStates[i] = mDeviceStates.valueAt(i).getIdentifier();
+            }
+            return supportedStates;
         }
     }
 
@@ -203,53 +214,86 @@
         return mBinderService;
     }
 
-    private void updateSupportedStates(int[] supportedDeviceStates) {
-        // Must ensure sorted as isSupportedStateLocked() impl uses binary search.
-        Arrays.sort(supportedDeviceStates, 0, supportedDeviceStates.length);
+    private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
         synchronized (mLock) {
-            mSupportedDeviceStates = IntArray.wrap(supportedDeviceStates);
+            mDeviceStates.clear();
+            for (int i = 0; i < supportedDeviceStates.length; i++) {
+                DeviceState state = supportedDeviceStates[i];
+                mDeviceStates.put(state.getIdentifier(), state);
+            }
 
-            if (mRequestedState != INVALID_DEVICE_STATE
-                    && !isSupportedStateLocked(mRequestedState)) {
-                // The current requested state is no longer valid. We'll clear it here, though
+            if (mBaseState.isPresent()
+                    && !isSupportedStateLocked(mBaseState.get().getIdentifier())) {
+                // The current base state is no longer valid. We'll clear it here, though
                 // we won't actually update the current state until a callback comes from the
                 // provider with the most recent state.
-                mRequestedState = INVALID_DEVICE_STATE;
+                mBaseState = Optional.empty();
             }
-            if (mRequestedOverrideState != INVALID_DEVICE_STATE
-                    && !isSupportedStateLocked(mRequestedOverrideState)) {
-                // The current override state is no longer valid. We'll clear it here and update
-                // the committed state if necessary.
-                mRequestedOverrideState = INVALID_DEVICE_STATE;
+
+            final int requestSize = mRequestRecords.size();
+            for (int i = 0; i < requestSize; i++) {
+                OverrideRequestRecord request = mRequestRecords.get(i);
+                if (!isSupportedStateLocked(request.mRequestedState.getIdentifier())) {
+                    request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
+                }
             }
+
             updatePendingStateLocked();
         }
 
+        notifyRequestsOfStatusChangeIfNeeded();
         notifyPolicyIfNeeded();
     }
 
     /**
      * Returns {@code true} if the provided state is supported. Requires that
-     * {@link #mSupportedDeviceStates} is sorted prior to calling.
+     * {@link #mDeviceStates} is sorted prior to calling.
      */
-    private boolean isSupportedStateLocked(int state) {
-        return mSupportedDeviceStates.binarySearch(state) >= 0;
+    private boolean isSupportedStateLocked(int identifier) {
+        return mDeviceStates.contains(identifier);
     }
 
     /**
-     * Requests that the system enter the provided {@code state}. The request may not be honored
-     * under certain conditions, for example if the provided state is not supported.
+     * Returns the {@link DeviceState} with the supplied {@code identifier}, or {@code null} if
+     * there is no device state with the identifier.
+     */
+    @Nullable
+    private Optional<DeviceState> getStateLocked(int identifier) {
+        return Optional.ofNullable(mDeviceStates.get(identifier));
+    }
+
+    /**
+     * Requests to set the base state. The request may not be honored under certain conditions, for
+     * example if the provided state is not supported.
      *
      * @see #isSupportedStateLocked(int)
      */
-    private void requestState(int state) {
+    private void setBaseState(int identifier) {
         synchronized (mLock) {
-            if (isSupportedStateLocked(state)) {
-                mRequestedState = state;
+            if (mBaseState.isPresent() && mBaseState.get().getIdentifier() == identifier) {
+                // Base state hasn't changed. Nothing to do.
+                return;
             }
+
+            final Optional<DeviceState> baseState = getStateLocked(identifier);
+            if (!baseState.isPresent()) {
+                throw new IllegalArgumentException("Base state is not supported");
+            }
+
+            mBaseState = baseState;
+
+            final int requestSize = mRequestRecords.size();
+            for (int i = 0; i < requestSize; i++) {
+                OverrideRequestRecord request = mRequestRecords.get(i);
+                if ((request.mFlags & FLAG_CANCEL_WHEN_BASE_CHANGES) > 0) {
+                    request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
+                }
+            }
+
             updatePendingStateLocked();
         }
 
+        notifyRequestsOfStatusChangeIfNeeded();
         notifyPolicyIfNeeded();
     }
 
@@ -259,19 +303,19 @@
      * changed.
      */
     private void updatePendingStateLocked() {
-        if (mPendingState != INVALID_DEVICE_STATE) {
+        if (mPendingState.isPresent()) {
             // Have pending state, can not configure a new state until the state is committed.
             return;
         }
 
-        int stateToConfigure;
-        if (mRequestedOverrideState != INVALID_DEVICE_STATE) {
-            stateToConfigure = mRequestedOverrideState;
+        final DeviceState stateToConfigure;
+        if (!mRequestRecords.isEmpty()) {
+            stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState;
         } else {
-            stateToConfigure = mRequestedState;
+            stateToConfigure = mBaseState.orElse(null);
         }
 
-        if (stateToConfigure == INVALID_DEVICE_STATE) {
+        if (stateToConfigure == null) {
             // No currently requested state.
             return;
         }
@@ -281,7 +325,7 @@
             return;
         }
 
-        mPendingState = stateToConfigure;
+        mPendingState = Optional.of(stateToConfigure);
         mIsPolicyWaitingForState = true;
     }
 
@@ -302,7 +346,7 @@
                 return;
             }
             mIsPolicyWaitingForState = false;
-            state = mPendingState;
+            state = mPendingState.get().getIdentifier();
         }
 
         if (DEBUG) {
@@ -333,15 +377,25 @@
             if (DEBUG) {
                 Slog.d(TAG, "Committing state: " + mPendingState);
             }
-            mCommittedState = mPendingState;
-            newState = mCommittedState;
-            mPendingState = INVALID_DEVICE_STATE;
+            mCommittedState = mPendingState.get();
+            newState = mCommittedState.getIdentifier();
+
+            if (!mRequestRecords.isEmpty()) {
+                final OverrideRequestRecord topRequest =
+                        mRequestRecords.get(mRequestRecords.size() - 1);
+                topRequest.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE);
+            }
+
+            mPendingState = Optional.empty();
             updatePendingStateLocked();
         }
 
         // Notify callbacks of a change.
         notifyDeviceStateChanged(newState);
 
+        // Notify the top request that it's active.
+        notifyRequestsOfStatusChangeIfNeeded();
+
         // Try to configure the next state if needed.
         notifyPolicyIfNeeded();
     }
@@ -352,114 +406,221 @@
                     "Attempting to notify callbacks with service lock held.");
         }
 
-        // Grab the lock and copy the callbacks.
-        ArrayList<CallbackRecord> callbacks;
+        // Grab the lock and copy the process records.
+        ArrayList<ProcessRecord> registeredProcesses;
         synchronized (mLock) {
-            if (mCallbacks.size() == 0) {
+            if (mProcessRecords.size() == 0) {
                 return;
             }
 
-            callbacks = new ArrayList<>();
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                callbacks.add(mCallbacks.valueAt(i));
+            registeredProcesses = new ArrayList<>();
+            for (int i = 0; i < mProcessRecords.size(); i++) {
+                registeredProcesses.add(mProcessRecords.valueAt(i));
             }
         }
 
         // After releasing the lock, send the notifications out.
-        for (int i = 0; i < callbacks.size(); i++) {
-            callbacks.get(i).notifyDeviceStateAsync(deviceState);
+        for (int i = 0; i < registeredProcesses.size(); i++) {
+            registeredProcesses.get(i).notifyDeviceStateAsync(deviceState);
         }
     }
 
-    private void registerCallbackInternal(IDeviceStateManagerCallback callback, int callingPid) {
+    /**
+     * Notifies all dirty requests (requests that have a change in status, but have not yet been
+     * notified) that their status has changed.
+     */
+    private void notifyRequestsOfStatusChangeIfNeeded() {
+        if (Thread.holdsLock(mLock)) {
+            throw new IllegalStateException(
+                    "Attempting to notify requests with service lock held.");
+        }
+
+        ArraySet<OverrideRequestRecord> dirtyRequests;
+        synchronized (mLock) {
+            if (mRequestsPendingStatusChange.isEmpty()) {
+                return;
+            }
+
+            dirtyRequests = new ArraySet<>(mRequestsPendingStatusChange);
+            mRequestsPendingStatusChange.clear();
+        }
+
+        // After releasing the lock, send the notifications out.
+        for (int i = 0; i < dirtyRequests.size(); i++) {
+            dirtyRequests.valueAt(i).notifyStatusIfNeeded();
+        }
+    }
+
+    private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
         int currentState;
-        CallbackRecord record;
+        ProcessRecord record;
         // Grab the lock to register the callback and get the current state.
         synchronized (mLock) {
-            if (mCallbacks.contains(callingPid)) {
+            if (mProcessRecords.contains(pid)) {
                 throw new SecurityException("The calling process has already registered an"
                         + " IDeviceStateManagerCallback.");
             }
 
-            record = new CallbackRecord(callback, callingPid);
+            record = new ProcessRecord(callback, pid);
             try {
                 callback.asBinder().linkToDeath(record, 0);
             } catch (RemoteException ex) {
                 throw new RuntimeException(ex);
             }
 
-            mCallbacks.put(callingPid, record);
-            currentState = mCommittedState;
+            mProcessRecords.put(pid, record);
+            currentState = mCommittedState.getIdentifier();
         }
 
         // Notify the callback of the state at registration.
         record.notifyDeviceStateAsync(currentState);
     }
 
-    private void unregisterCallbackInternal(CallbackRecord record) {
+    private void handleProcessDied(ProcessRecord processRecord) {
         synchronized (mLock) {
-            mCallbacks.remove(record.mPid);
+            // Cancel all requests from this process.
+            final int requestCount = processRecord.mRequestRecords.size();
+            for (int i = 0; i < requestCount; i++) {
+                final OverrideRequestRecord request = processRecord.mRequestRecords.valueAt(i);
+                // Cancel the request but don't mark it as dirty since there's no need to send
+                // notifications if the process has died.
+                request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED,
+                        false /* markDirty */);
+            }
+
+            mProcessRecords.remove(processRecord.mPid);
+
+            updatePendingStateLocked();
         }
+
+        notifyPolicyIfNeeded();
+    }
+
+    private void requestStateInternal(int state, int flags, int callingPid,
+            @NonNull IBinder token) {
+        synchronized (mLock) {
+            final ProcessRecord processRecord = mProcessRecords.get(callingPid);
+            if (processRecord == null) {
+                throw new IllegalStateException("Process " + callingPid
+                        + " has no registered callback.");
+            }
+
+            if (processRecord.mRequestRecords.get(token) != null) {
+                throw new IllegalStateException("Request has already been made for the supplied"
+                        + " token: " + token);
+            }
+
+            final Optional<DeviceState> deviceState = getStateLocked(state);
+            if (!deviceState.isPresent()) {
+                throw new IllegalArgumentException("Requested state: " + state
+                        + " is not supported.");
+            }
+
+            OverrideRequestRecord topRecord = mRequestRecords.isEmpty()
+                    ? null : mRequestRecords.get(mRequestRecords.size() - 1);
+            if (topRecord != null) {
+                topRecord.setStatusLocked(OverrideRequestRecord.STATUS_SUSPENDED);
+            }
+
+            final OverrideRequestRecord request =
+                    new OverrideRequestRecord(processRecord, token, deviceState.get(), flags);
+            mRequestRecords.add(request);
+            processRecord.mRequestRecords.put(request.mToken, request);
+            // We don't set the status of the new request to ACTIVE here as it will be set in
+            // commitPendingState().
+
+            updatePendingStateLocked();
+        }
+
+        notifyRequestsOfStatusChangeIfNeeded();
+        notifyPolicyIfNeeded();
+    }
+
+    private void cancelRequestInternal(int callingPid, @NonNull IBinder token) {
+        synchronized (mLock) {
+            final ProcessRecord processRecord = mProcessRecords.get(callingPid);
+            if (processRecord == null) {
+                throw new IllegalStateException("Process " + callingPid
+                        + " has no registered callback.");
+            }
+
+            OverrideRequestRecord request = processRecord.mRequestRecords.get(token);
+            if (request == null) {
+                throw new IllegalStateException("No known request for the given token");
+            }
+
+            request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
+
+            updatePendingStateLocked();
+        }
+
+        notifyRequestsOfStatusChangeIfNeeded();
+        notifyPolicyIfNeeded();
     }
 
     private void dumpInternal(PrintWriter pw) {
         pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
 
         synchronized (mLock) {
-            pw.println("  mCommittedState=" + toString(mCommittedState));
-            pw.println("  mPendingState=" + toString(mPendingState));
-            pw.println("  mRequestedState=" + toString(mRequestedState));
-            pw.println("  mRequestedOverrideState=" + toString(mRequestedOverrideState));
+            pw.println("  mCommittedState=" + mCommittedState);
+            pw.println("  mPendingState=" + mPendingState);
+            pw.println("  mBaseState=" + mBaseState);
+            pw.println("  mOverrideState=" + getOverrideState());
 
-            final int callbackCount = mCallbacks.size();
+            final int processCount = mProcessRecords.size();
             pw.println();
-            pw.println("Callbacks: size=" + callbackCount);
-            for (int i = 0; i < callbackCount; i++) {
-                CallbackRecord callback = mCallbacks.valueAt(i);
-                pw.println("  " + i + ": mPid=" + callback.mPid);
+            pw.println("Registered processes: size=" + processCount);
+            for (int i = 0; i < processCount; i++) {
+                ProcessRecord processRecord = mProcessRecords.valueAt(i);
+                pw.println("  " + i + ": mPid=" + processRecord.mPid);
+            }
+
+            final int requestCount = mRequestRecords.size();
+            pw.println();
+            pw.println("Override requests: size=" + requestCount);
+            for (int i = 0; i < requestCount; i++) {
+                OverrideRequestRecord requestRecord = mRequestRecords.get(i);
+                pw.println("  " + i + ": mPid=" + requestRecord.mProcessRecord.mPid
+                        + ", mRequestedState=" + requestRecord.mRequestedState
+                        + ", mFlags=" + requestRecord.mFlags
+                        + ", mStatus=" + requestRecord.statusToString(requestRecord.mStatus));
             }
         }
     }
 
-    private String toString(int state) {
-        return state == INVALID_DEVICE_STATE ? "(none)" : String.valueOf(state);
-    }
-
     private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
         @Override
-        public void onSupportedDeviceStatesChanged(int[] newDeviceStates) {
-            for (int i = 0; i < newDeviceStates.length; i++) {
-                if (newDeviceStates[i] < 0) {
-                    throw new IllegalArgumentException("Supported device states includes invalid"
-                            + " value: " + newDeviceStates[i]);
-                }
+        public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
+            if (newDeviceStates.length == 0) {
+                throw new IllegalArgumentException("Supported device states must not be empty");
             }
-
             updateSupportedStates(newDeviceStates);
         }
 
         @Override
-        public void onStateChanged(int state) {
-            if (state < 0) {
-                throw new IllegalArgumentException("Invalid state: " + state);
+        public void onStateChanged(@IntRange(from = 0) int identifier) {
+            if (identifier < 0) {
+                throw new IllegalArgumentException("Invalid identifier: " + identifier);
             }
 
-            requestState(state);
+            setBaseState(identifier);
         }
     }
 
-    private final class CallbackRecord implements IBinder.DeathRecipient {
+    private final class ProcessRecord implements IBinder.DeathRecipient {
         private final IDeviceStateManagerCallback mCallback;
         private final int mPid;
 
-        CallbackRecord(IDeviceStateManagerCallback callback, int pid) {
+        private final ArrayMap<IBinder, OverrideRequestRecord> mRequestRecords = new ArrayMap<>();
+
+        ProcessRecord(IDeviceStateManagerCallback callback, int pid) {
             mCallback = callback;
             mPid = pid;
         }
 
         @Override
         public void binderDied() {
-            unregisterCallbackInternal(this);
+            handleProcessDied(this);
         }
 
         public void notifyDeviceStateAsync(int devicestate) {
@@ -470,6 +631,119 @@
                         ex);
             }
         }
+
+        public void notifyRequestActiveAsync(OverrideRequestRecord request) {
+            try {
+                mCallback.onRequestActive(request.mToken);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
+                        ex);
+            }
+        }
+
+        public void notifyRequestSuspendedAsync(OverrideRequestRecord request) {
+            try {
+                mCallback.onRequestSuspended(request.mToken);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
+                        ex);
+            }
+        }
+
+        public void notifyRequestCanceledAsync(OverrideRequestRecord request) {
+            try {
+                mCallback.onRequestCanceled(request.mToken);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
+                        ex);
+            }
+        }
+    }
+
+    /** A record describing a request to override the state of the device. */
+    private final class OverrideRequestRecord {
+        public static final int STATUS_UNKNOWN = 0;
+        public static final int STATUS_ACTIVE = 1;
+        public static final int STATUS_SUSPENDED = 2;
+        public static final int STATUS_CANCELED = 3;
+
+        @Nullable
+        public String statusToString(int status) {
+            switch (status) {
+                case STATUS_ACTIVE:
+                    return "ACTIVE";
+                case STATUS_SUSPENDED:
+                    return "SUSPENDED";
+                case STATUS_CANCELED:
+                    return "CANCELED";
+                case STATUS_UNKNOWN:
+                    return "UNKNOWN";
+                default:
+                    return null;
+            }
+        }
+
+        private final ProcessRecord mProcessRecord;
+        @NonNull
+        private final IBinder mToken;
+        @NonNull
+        private final DeviceState mRequestedState;
+        private final int mFlags;
+
+        private int mStatus = STATUS_UNKNOWN;
+        private int mLastNotifiedStatus = STATUS_UNKNOWN;
+
+        OverrideRequestRecord(@NonNull ProcessRecord processRecord, @NonNull IBinder token,
+                @NonNull DeviceState requestedState, int flags) {
+            mProcessRecord = processRecord;
+            mToken = token;
+            mRequestedState = requestedState;
+            mFlags = flags;
+        }
+
+        public void setStatusLocked(int status) {
+            setStatusLocked(status, true /* markDirty */);
+        }
+
+        public void setStatusLocked(int status, boolean markDirty) {
+            if (mStatus != status) {
+                if (mStatus == STATUS_CANCELED) {
+                    throw new IllegalStateException(
+                            "Can not alter the status of a request after set to CANCELED.");
+                }
+
+                mStatus = status;
+
+                if (mStatus == STATUS_CANCELED) {
+                    mRequestRecords.remove(this);
+                    mProcessRecord.mRequestRecords.remove(mToken);
+                }
+
+                if (markDirty) {
+                    mRequestsPendingStatusChange.add(this);
+                }
+            }
+        }
+
+        public void notifyStatusIfNeeded() {
+            int stateToReport;
+            synchronized (mLock) {
+                if (mLastNotifiedStatus == mStatus) {
+                    return;
+                }
+
+                stateToReport = mStatus;
+                mLastNotifiedStatus = mStatus;
+            }
+
+            if (stateToReport == STATUS_ACTIVE) {
+                mProcessRecord.notifyRequestActiveAsync(this);
+            } else if (stateToReport == STATUS_SUSPENDED) {
+                mProcessRecord.notifyRequestSuspendedAsync(this);
+            } else if (stateToReport == STATUS_CANCELED) {
+                mProcessRecord.notifyRequestCanceledAsync(this);
+            }
+        }
     }
 
     /** Implementation of {@link IDeviceStateManager} published as a binder service. */
@@ -483,13 +757,59 @@
             final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
             try {
-                registerCallbackInternal(callback, callingPid);
+                registerProcess(callingPid, callback);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override // Binder call
+        public int[] getSupportedDeviceStates() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return getSupportedStateIdentifiers();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
+        public void requestState(IBinder token, int state, int flags) {
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "Permission required to request device state.");
+
+            if (token == null) {
+                throw new IllegalArgumentException("Request token must not be null.");
+            }
+
+            final int callingPid = Binder.getCallingPid();
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                requestStateInternal(state, flags, callingPid, token);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override // Binder call
+        public void cancelRequest(IBinder token) {
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "Permission required to clear requested device state.");
+
+            if (token == null) {
+                throw new IllegalArgumentException("Request token must not be null.");
+            }
+
+            final int callingPid = Binder.getCallingPid();
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                cancelRequestInternal(callingPid, token);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override // Binder call
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver result) {
             new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index cf3b297..6cc55a6 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -16,11 +16,17 @@
 
 package com.android.server.devicestate;
 
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+import static android.Manifest.permission.CONTROL_DEVICE_STATE;
 
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.os.Binder;
 import android.os.ShellCommand;
 
 import java.io.PrintWriter;
+import java.util.Optional;
 
 /**
  * ShellCommands for {@link DeviceStateManagerService}.
@@ -28,10 +34,15 @@
  * Use with {@code adb shell cmd device_state ...}.
  */
 public class DeviceStateManagerShellCommand extends ShellCommand {
-    private final DeviceStateManagerService mInternal;
+    @Nullable
+    private static DeviceStateRequest sLastRequest;
+
+    private final DeviceStateManagerService mService;
+    private final DeviceStateManager mClient;
 
     public DeviceStateManagerShellCommand(DeviceStateManagerService service) {
-        mInternal = service;
+        mService = service;
+        mClient = service.getContext().getSystemService(DeviceStateManager.class);
     }
 
     @Override
@@ -52,24 +63,15 @@
     }
 
     private void printState(PrintWriter pw) {
-        int committedState = mInternal.getCommittedState();
-        int requestedState = mInternal.getRequestedState();
-        int requestedOverrideState = mInternal.getOverrideState();
+        DeviceState committedState = mService.getCommittedState();
+        Optional<DeviceState> baseState = mService.getBaseState();
+        Optional<DeviceState> overrideState = mService.getOverrideState();
 
-        if (committedState == INVALID_DEVICE_STATE) {
-            pw.println("Device state: (invalid)");
-        } else {
-            pw.println("Device state: " + committedState);
-        }
-
-        if (requestedOverrideState != INVALID_DEVICE_STATE) {
+        pw.println("Committed state: " + committedState);
+        if (overrideState.isPresent()) {
             pw.println("----------------------");
-            if (requestedState == INVALID_DEVICE_STATE) {
-                pw.println("Base state: (invalid)");
-            } else {
-                pw.println("Base state: " + requestedState);
-            }
-            pw.println("Override state: " + committedState);
+            pw.println("Base state: " + baseState.orElse(null));
+            pw.println("Override state: " + overrideState.get());
         }
     }
 
@@ -77,40 +79,56 @@
         final String nextArg = getNextArg();
         if (nextArg == null) {
             printState(pw);
-        } else if ("reset".equals(nextArg)) {
-            mInternal.clearOverrideState();
-        } else {
-            int requestedState;
-            try {
-                requestedState = Integer.parseInt(nextArg);
-            } catch (NumberFormatException e) {
-                getErrPrintWriter().println("Error: requested state should be an integer");
-                return -1;
-            }
-
-            boolean success = mInternal.setOverrideState(requestedState);
-            if (!success) {
-                getErrPrintWriter().println("Error: failed to set override state. Run:");
-                getErrPrintWriter().println("");
-                getErrPrintWriter().println("    print-states");
-                getErrPrintWriter().println("");
-                getErrPrintWriter().println("to get the list of currently supported device states");
-                return -1;
-            }
         }
+
+        final Context context = mService.getContext();
+        context.enforceCallingOrSelfPermission(
+                CONTROL_DEVICE_STATE,
+                "Permission required to request device state.");
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            if ("reset".equals(nextArg)) {
+                if (sLastRequest != null) {
+                    mClient.cancelRequest(sLastRequest);
+                    sLastRequest = null;
+                }
+            } else {
+                int requestedState = Integer.parseInt(nextArg);
+                DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build();
+
+                mClient.requestState(request, null /* executor */, null /* callback */);
+                if (sLastRequest != null) {
+                    mClient.cancelRequest(sLastRequest);
+                }
+
+                sLastRequest = request;
+            }
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: requested state should be an integer");
+            return -1;
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println("Error: " + e.getMessage());
+            getErrPrintWriter().println("-------------------");
+            getErrPrintWriter().println("Run:");
+            getErrPrintWriter().println("");
+            getErrPrintWriter().println("    print-states");
+            getErrPrintWriter().println("");
+            getErrPrintWriter().println("to get the list of currently supported device states");
+            return -1;
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+
         return 0;
     }
 
     private int runPrintStates(PrintWriter pw) {
-        int[] states = mInternal.getSupportedStates();
-        pw.print("Supported states: [ ");
+        DeviceState[] states = mService.getSupportedStates();
+        pw.print("Supported states: [\n");
         for (int i = 0; i < states.length; i++) {
-            pw.print(states[i]);
-            if (i < states.length - 1) {
-                pw.print(", ");
-            }
+            pw.print("  " + states[i] + ",\n");
         }
-        pw.println(" ]");
+        pw.println("]");
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 0e8bf9b..2d4377f 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -16,9 +16,11 @@
 
 package com.android.server.devicestate;
 
+import android.annotation.IntRange;
+
 /**
- * Responsible for providing the set of currently supported device states and well as the current
- * device state.
+ * Responsible for providing the set of supported {@link DeviceState device states} as well as the
+ * current device state.
  *
  * @see DeviceStatePolicy
  */
@@ -26,8 +28,8 @@
     /**
      * Registers a listener for changes in provider state.
      * <p>
-     * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(int[])} be called
-     * followed by {@link Listener#onStateChanged(int)} with the initial values on successful
+     * It is <b>required</b> that {@link Listener#onSupportedDeviceStatesChanged(DeviceState[])} be
+     * called followed by {@link Listener#onStateChanged(int)} with the initial values on successful
      * registration of the listener.
      */
     void setListener(Listener listener);
@@ -35,35 +37,36 @@
     /** Callback for changes in {@link DeviceStateProvider} state. */
     interface Listener {
         /**
-         * Called to notify the listener of a change in supported device states. Required to be
-         * called once on successful registration of the listener and then once on every
-         * subsequent change in supported device states.
+         * Called to notify the listener of a change in supported {@link DeviceState device states}.
+         * Required to be called once on successful registration of the listener and then once on
+         * every subsequent change in supported device states.
          * <p>
          * The set of device states can change based on the current hardware state of the device.
          * For example, if a device state depends on a particular peripheral device (display, etc)
          * it would only be reported as supported when the device is plugged. Otherwise, it should
          * not be included in the set of supported states.
          * <p>
-         * All values provided must be greater than or equal to zero and there must always be at
-         * least one supported device state.
+         * The identifier for every provided device state must be unique and greater than or equal
+         * to zero and there must always be at least one supported device state.
          *
          * @param newDeviceStates array of supported device states.
          *
          * @throws IllegalArgumentException if the list of device states is empty or if one of the
-         * provided states is less than 0.
+         * provided states contains an invalid identifier.
          */
-        void onSupportedDeviceStatesChanged(int[] newDeviceStates);
+        void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates);
 
         /**
          * Called to notify the listener of a change in current device state. Required to be called
          * once on successful registration of the listener and then once on every subsequent change
          * in device state. Value must have been included in the set of supported device states
-         * provided in the most recent call to {@link #onSupportedDeviceStatesChanged(int[])}.
+         * provided in the most recent call to
+         * {@link #onSupportedDeviceStatesChanged(DeviceState[])}.
          *
-         * @param state the new device state.
+         * @param identifier the identifier of the new device state.
          *
          * @throws IllegalArgumentException if the state is less than 0.
          */
-        void onStateChanged(int state);
+        void onStateChanged(@IntRange(from = 0) int identifier);
     }
 }
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index b8e579d..4832e46 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -300,6 +300,8 @@
     /** @return The default brightness configuration. */
     public abstract BrightnessConfiguration getDefaultConfig();
 
+    /** Recalculates the backlight-to-nits and nits-to-backlight splines. */
+    public abstract void recalculateSplines(boolean applyAdjustment, float[] adjustment);
 
     /**
      * Returns the timeout for the short term model
@@ -658,6 +660,11 @@
         }
 
         @Override
+        public void recalculateSplines(boolean applyAdjustment, float[] adjustment) {
+            // Do nothing.
+        }
+
+        @Override
         public void dump(PrintWriter pw) {
             pw.println("SimpleMappingStrategy");
             pw.println("  mSpline=" + mSpline);
@@ -696,7 +703,7 @@
 
         // A spline mapping from nits to the corresponding backlight value, normalized to the range
         // [0, 1.0].
-        private final Spline mNitsToBacklightSpline;
+        private Spline mNitsToBacklightSpline;
 
         // The default brightness configuration.
         private final BrightnessConfiguration mDefaultConfig;
@@ -705,6 +712,11 @@
         // a brightness in nits.
         private Spline mBacklightToNitsSpline;
 
+        private float[] mNits;
+        private int[] mBacklight;
+
+        private boolean mBrightnessRangeAdjustmentApplied;
+
         private float mMaxGamma;
         private float mAutoBrightnessAdjustment;
         private float mUserLux;
@@ -726,15 +738,9 @@
             mUserLux = -1;
             mUserBrightness = -1;
 
-            // Setup the backlight spline
-            final int N = nits.length;
-            float[] normalizedBacklight = new float[N];
-            for (int i = 0; i < N; i++) {
-                normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]);
-            }
-
-            mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
-            mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
+            mNits = nits;
+            mBacklight = backlight;
+            computeNitsBrightnessSplines(mNits);
 
             mDefaultConfig = config;
             if (mLoggingEnabled) {
@@ -868,6 +874,12 @@
         }
 
         @Override
+        public void recalculateSplines(boolean applyAdjustment, float[] adjustedNits) {
+            mBrightnessRangeAdjustmentApplied = applyAdjustment;
+            computeNitsBrightnessSplines(mBrightnessRangeAdjustmentApplied ? adjustedNits : mNits);
+        }
+
+        @Override
         public void dump(PrintWriter pw) {
             pw.println("PhysicalMappingStrategy");
             pw.println("  mConfig=" + mConfig);
@@ -878,6 +890,18 @@
             pw.println("  mUserLux=" + mUserLux);
             pw.println("  mUserBrightness=" + mUserBrightness);
             pw.println("  mDefaultConfig=" + mDefaultConfig);
+            pw.println("  mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied);
+        }
+
+        private void computeNitsBrightnessSplines(float[] nits) {
+            final int len = nits.length;
+            float[] normalizedBacklight = new float[len];
+            for (int i = 0; i < len; i++) {
+                normalizedBacklight[i] = normalizeAbsoluteBrightness(mBacklight[i]);
+            }
+
+            mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
+            mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
         }
 
         private void computeSpline() {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 2375f74..a186e33 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -63,7 +63,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.RingBuffer;
 import com.android.server.LocalServices;
 
@@ -71,7 +70,6 @@
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -80,7 +78,6 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -118,6 +115,9 @@
     private static final String ATTR_BATTERY_LEVEL = "batteryLevel";
     private static final String ATTR_NIGHT_MODE = "nightMode";
     private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature";
+    private static final String ATTR_REDUCE_BRIGHT_COLORS = "reduceBrightColors";
+    private static final String ATTR_REDUCE_BRIGHT_COLORS_STRENGTH = "reduceBrightColorsStrength";
+    private static final String ATTR_REDUCE_BRIGHT_COLORS_OFFSET = "reduceBrightColorsOffset";
     private static final String ATTR_LAST_NITS = "lastNits";
     private static final String ATTR_DEFAULT_CONFIG = "defaultConfig";
     private static final String ATTR_POWER_SAVE = "powerSaveFactor";
@@ -398,6 +398,10 @@
 
         builder.setNightMode(mInjector.isNightDisplayActivated(mContext));
         builder.setColorTemperature(mInjector.getNightDisplayColorTemperature(mContext));
+        builder.setReduceBrightColors(mInjector.isReduceBrightColorsActivated(mContext));
+        builder.setReduceBrightColorsStrength(mInjector.getReduceBrightColorsStrength(mContext));
+        builder.setReduceBrightColorsOffset(mInjector.getReduceBrightColorsOffsetFactor(mContext)
+                * brightness);
 
         if (mColorSamplingEnabled) {
             DisplayedContentSample sample = mInjector.sampleColor(mNoFramesToSample);
@@ -563,6 +567,12 @@
                 out.attributeBoolean(null, ATTR_NIGHT_MODE, toWrite[i].nightMode);
                 out.attributeInt(null, ATTR_COLOR_TEMPERATURE,
                         toWrite[i].colorTemperature);
+                out.attributeBoolean(null, ATTR_REDUCE_BRIGHT_COLORS,
+                        toWrite[i].reduceBrightColors);
+                out.attributeInt(null, ATTR_REDUCE_BRIGHT_COLORS_STRENGTH,
+                        toWrite[i].reduceBrightColorsStrength);
+                out.attributeFloat(null, ATTR_REDUCE_BRIGHT_COLORS_OFFSET,
+                        toWrite[i].reduceBrightColorsOffset);
                 out.attributeFloat(null, ATTR_LAST_NITS,
                         toWrite[i].lastBrightness);
                 out.attributeBoolean(null, ATTR_DEFAULT_CONFIG,
@@ -641,6 +651,12 @@
                     builder.setNightMode(parser.getAttributeBoolean(null, ATTR_NIGHT_MODE));
                     builder.setColorTemperature(
                             parser.getAttributeInt(null, ATTR_COLOR_TEMPERATURE));
+                    builder.setReduceBrightColors(
+                            parser.getAttributeBoolean(null, ATTR_REDUCE_BRIGHT_COLORS));
+                    builder.setReduceBrightColorsStrength(
+                            parser.getAttributeInt(null, ATTR_REDUCE_BRIGHT_COLORS_STRENGTH));
+                    builder.setReduceBrightColorsOffset(
+                            parser.getAttributeFloat(null, ATTR_REDUCE_BRIGHT_COLORS_OFFSET));
                     builder.setLastBrightness(parser.getAttributeFloat(null, ATTR_LAST_NITS));
 
                     String luxValue = parser.getAttributeValue(null, ATTR_LUX);
@@ -1114,6 +1130,21 @@
             return context.getSystemService(ColorDisplayManager.class).isNightDisplayActivated();
         }
 
+        public int getReduceBrightColorsStrength(Context context) {
+            return context.getSystemService(ColorDisplayManager.class)
+                    .getReduceBrightColorsStrength();
+        }
+
+        public float getReduceBrightColorsOffsetFactor(Context context) {
+            return context.getSystemService(ColorDisplayManager.class)
+                    .getReduceBrightColorsOffsetFactor();
+        }
+
+        public boolean isReduceBrightColorsActivated(Context context) {
+            return context.getSystemService(ColorDisplayManager.class)
+                    .isReduceBrightColorsActivated();
+        }
+
         public DisplayedContentSample sampleColor(int noFramesToSample) {
             final DisplayManagerInternal displayManagerInternal =
                     LocalServices.getService(DisplayManagerInternal.class);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 2641ee7..bf16a6d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -23,6 +23,7 @@
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
+import android.view.RoundedCorners;
 import android.view.Surface;
 
 import com.android.internal.BrightnessSynchronizer;
@@ -281,6 +282,11 @@
     public DisplayCutout displayCutout;
 
     /**
+     * The {@link RoundedCorners} if present or {@code null} otherwise.
+     */
+    public RoundedCorners roundedCorners;
+
+    /**
      * The touch attachment, per {@link DisplayViewport#touch}.
      */
     public int touch;
@@ -401,7 +407,8 @@
                 || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
                 || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
                 || !BrightnessSynchronizer.floatEquals(brightnessDefault,
-                other.brightnessDefault)) {
+                other.brightnessDefault)
+                || !Objects.equals(roundedCorners, other.roundedCorners)) {
             diff |= DIFF_OTHER;
         }
         return diff;
@@ -444,6 +451,7 @@
         brightnessMinimum = other.brightnessMinimum;
         brightnessMaximum = other.brightnessMaximum;
         brightnessDefault = other.brightnessDefault;
+        roundedCorners = other.roundedCorners;
     }
 
     // For debugging purposes
@@ -487,6 +495,9 @@
         sb.append(", brightnessMinimum ").append(brightnessMinimum);
         sb.append(", brightnessMaximum ").append(brightnessMaximum);
         sb.append(", brightnessDefault ").append(brightnessDefault);
+        if (roundedCorners != null) {
+            sb.append(", roundedCorners ").append(roundedCorners);
+        }
         sb.append(flagsToString(flags));
         sb.append("}");
         return sb.toString();
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index 2ba8758..663883a 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -21,13 +21,12 @@
 
 /**
  * Represents a collection of {@link LogicalDisplay}s which act in unison for certain behaviors and
- * operations.
+ * operations; particularly display-state.
+ *
  * @hide
  */
 public class DisplayGroup {
 
-    public static final int DEFAULT = 0;
-
     private final List<LogicalDisplay> mDisplays = new ArrayList<>();
     private final int mGroupId;
 
@@ -35,17 +34,44 @@
         mGroupId = groupId;
     }
 
+    /** Returns the identifier for the Group. */
     int getGroupId() {
         return mGroupId;
     }
 
-    void addDisplay(LogicalDisplay display) {
+    /**
+     * Adds the provided {@code display} to the Group
+     *
+     * @param display the {@link LogicalDisplay} to add to the Group
+     */
+    void addDisplayLocked(LogicalDisplay display) {
         if (!mDisplays.contains(display)) {
             mDisplays.add(display);
         }
     }
 
-    boolean removeDisplay(LogicalDisplay display) {
+    /**
+     * Removes the provided {@code display} from the Group.
+     *
+     * @param display The {@link LogicalDisplay} to remove from the Group.
+     * @return {@code true} if the {@code display} was removed; otherwise {@code false}
+     */
+    boolean removeDisplayLocked(LogicalDisplay display) {
         return mDisplays.remove(display);
     }
+
+    /** Returns {@code true} if there are no {@link LogicalDisplay LogicalDisplays} in the Group. */
+    boolean isEmptyLocked() {
+        return mDisplays.isEmpty();
+    }
+
+    /** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */
+    int getSizeLocked() {
+        return mDisplays.size();
+    }
+
+    /** Returns the ID of the {@link LogicalDisplay} at the provided {@code index}. */
+    int getIdLocked(int index) {
+        return mDisplays.get(index).getDisplayIdLocked();
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 55103ca..0950d5d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -57,6 +57,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
 import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayedContentSample;
@@ -180,8 +181,8 @@
     private static final String FORCE_WIFI_DISPLAY_ENABLE = "persist.debug.wfd.enable";
 
     private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
-
     private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
+    private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
 
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
     private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
@@ -190,6 +191,7 @@
     private static final int MSG_UPDATE_VIEWPORT = 5;
     private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6;
     private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
+    private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8;
 
     private final Context mContext;
     private final DisplayManagerHandler mHandler;
@@ -237,6 +239,10 @@
     private final CopyOnWriteArrayList<DisplayTransactionListener> mDisplayTransactionListeners =
             new CopyOnWriteArrayList<DisplayTransactionListener>();
 
+    /** List of all display group listeners. */
+    private final CopyOnWriteArrayList<DisplayGroupListener> mDisplayGroupListeners =
+            new CopyOnWriteArrayList<>();
+
     /** All {@link DisplayPowerController}s indexed by {@link LogicalDisplay} ID. */
     private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
             new SparseArray<>();
@@ -365,7 +371,7 @@
 
     private final boolean mAllowNonNativeRefreshRateOverride;
 
-    private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
+    private final BrightnessSynchronizer mBrightnessSynchronizer;
 
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
@@ -402,6 +408,7 @@
         mLogicalDisplayMapper = new LogicalDisplayMapper(context, mDisplayDeviceRepo,
                 new LogicalDisplayListener());
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
+        mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
         Resources resources = mContext.getResources();
         mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_defaultDisplayDefaultColorMode);
@@ -507,8 +514,8 @@
 
             DeviceStateManager deviceStateManager =
                     mContext.getSystemService(DeviceStateManager.class);
-            deviceStateManager.registerDeviceStateListener(new DeviceStateListener(),
-                    new HandlerExecutor(mHandler));
+            deviceStateManager.addDeviceStateListener(new HandlerExecutor(mHandler),
+                    new DeviceStateListener());
 
             scheduleTraversalLocked(false);
         }
@@ -536,6 +543,7 @@
         mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
 
         mSettingsObserver = new SettingsObserver();
+        mBrightnessSynchronizer.startSynchronizing();
     }
 
     @VisibleForTesting
@@ -1357,7 +1365,7 @@
                 // Scan supported modes returned by display.getInfo() to find a mode with the same
                 // size as the default display mode but with the specified refresh rate instead.
                 requestedModeId = display.getDisplayInfoLocked().findDefaultModeByRefreshRate(
-                        requestedRefreshRate);
+                        requestedRefreshRate).getModeId();
             }
             mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode(
                     displayId, requestedModeId);
@@ -1538,6 +1546,14 @@
         }
     }
 
+    void setDisplayModeDirectorLoggingEnabled(boolean enabled) {
+        synchronized (mSyncRoot) {
+            if (mDisplayModeDirector != null) {
+                mDisplayModeDirector.setLoggingEnabled(enabled);
+            }
+        }
+    }
+
     void setAmbientColorTemperatureOverride(float cct) {
         synchronized (mSyncRoot) {
             final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
@@ -1669,6 +1685,11 @@
         mHandler.sendMessage(msg);
     }
 
+    private void sendDisplayGroupEvent(int groupId, int event) {
+        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_GROUP_EVENT, groupId, event);
+        mHandler.sendMessage(msg);
+    }
+
     private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
         Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
                 displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
@@ -1713,6 +1734,35 @@
         mTempCallbacks.clear();
     }
 
+    // Runs on Handler thread.
+    // Delivers display group event notifications to callbacks.
+    private void deliverDisplayGroupEvent(int groupId, int event) {
+        if (DEBUG) {
+            Slog.d(TAG, "Delivering display group event: groupId=" + groupId + ", event="
+                    + event);
+        }
+
+        switch (event) {
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupAdded(groupId);
+                }
+                break;
+
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupChanged(groupId);
+                }
+                break;
+
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupRemoved(groupId);
+                }
+                break;
+        }
+    }
+
     private IMediaProjectionManager getProjectionService() {
         if (mProjectionService == null) {
             IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
@@ -1935,6 +1985,11 @@
                     }
                     deliverDisplayEvent(msg.arg1, uids, msg.arg2);
                     break;
+
+                case MSG_DELIVER_DISPLAY_GROUP_EVENT:
+                    deliverDisplayGroupEvent(msg.arg1, msg.arg2);
+                    break;
+
             }
         }
     }
@@ -1966,6 +2021,11 @@
         }
 
         @Override
+        public void onDisplayGroupEventLocked(int groupId, int event) {
+            sendDisplayGroupEvent(groupId, event);
+        }
+
+        @Override
         public void onTraversalRequested() {
             synchronized (mSyncRoot) {
                 scheduleTraversalLocked(false);
@@ -2700,7 +2760,7 @@
         }
 
         @Override
-        public boolean requestPowerState(DisplayPowerRequest request,
+        public boolean requestPowerState(int groupId, DisplayPowerRequest request,
                 boolean waitForNegativeProximity) {
             synchronized (mSyncRoot) {
                 return mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY)
@@ -2724,6 +2784,16 @@
         }
 
         @Override
+        public void registerDisplayGroupListener(DisplayGroupListener listener) {
+            mDisplayGroupListeners.add(listener);
+        }
+
+        @Override
+        public void unregisterDisplayGroupListener(DisplayGroupListener listener) {
+            mDisplayGroupListeners.remove(listener);
+        }
+
+        @Override
         public SurfaceControl.ScreenshotHardwareBuffer systemScreenshot(int displayId) {
             return systemScreenshotInternal(displayId);
         }
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 111664a..aaea15a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -54,6 +54,10 @@
                 return setDisplayWhiteBalanceLoggingEnabled(true);
             case "dwb-logging-disable":
                 return setDisplayWhiteBalanceLoggingEnabled(false);
+            case "dmd-logging-enable":
+                return setDisplayModeDirectorLoggingEnabled(true);
+            case "dmd-logging-disable":
+                return setDisplayModeDirectorLoggingEnabled(false);
             case "dwb-set-cct":
                 return setAmbientColorTemperatureOverride();
             case "set-fold":
@@ -82,6 +86,10 @@
         pw.println("    Enable display white-balance logging.");
         pw.println("  dwb-logging-disable");
         pw.println("    Disable display white-balance logging.");
+        pw.println("  dmd-logging-enable");
+        pw.println("    Enable display mode director logging.");
+        pw.println("  dmd-logging-disable");
+        pw.println("    Disable display mode director logging.");
         pw.println("  dwb-set-cct CCT");
         pw.println("    Sets the ambient color temperature override to CCT (use -1 to disable).");
         pw.println("  set-fold [fold|unfold|reset]");
@@ -136,6 +144,11 @@
         return 0;
     }
 
+    private int setDisplayModeDirectorLoggingEnabled(boolean enabled) {
+        mService.setDisplayModeDirectorLoggingEnabled(enabled);
+        return 0;
+    }
+
     private int setAmbientColorTemperatureOverride() {
         String cctText = getNextArg();
         if (cctText == null) {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 006f875..dce6bd8 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -67,7 +67,7 @@
  */
 public class DisplayModeDirector {
     private static final String TAG = "DisplayModeDirector";
-    private static final boolean DEBUG = false;
+    private boolean mLoggingEnabled;
 
     private static final int MSG_REFRESH_RATE_RANGE_CHANGED = 1;
     private static final int MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
@@ -155,6 +155,14 @@
         }
     }
 
+    public void setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return;
+        }
+        mLoggingEnabled = loggingEnabled;
+        mBrightnessObserver.setLoggingEnabled(loggingEnabled);
+    }
+
     @NonNull
     private SparseArray<Vote> getVotesLocked(int displayId) {
         SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
@@ -269,7 +277,7 @@
 
                 availableModes = filterModes(modes, primarySummary);
                 if (availableModes.length > 0) {
-                    if (DEBUG) {
+                    if (mLoggingEnabled) {
                         Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes)
                                 + " with lowest priority considered "
                                 + Vote.priorityToString(lowestConsideredPriority)
@@ -282,7 +290,7 @@
                     break;
                 }
 
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Couldn't find available modes with lowest priority set to "
                             + Vote.priorityToString(lowestConsideredPriority)
                             + " and with the following constraints: "
@@ -307,7 +315,7 @@
                     Math.min(appRequestSummary.minRefreshRate, primarySummary.minRefreshRate);
             appRequestSummary.maxRefreshRate =
                     Math.max(appRequestSummary.maxRefreshRate, primarySummary.maxRefreshRate);
-            if (DEBUG) {
+            if (mLoggingEnabled) {
                 Slog.i(TAG,
                         String.format("App request range: [%.0f %.0f]",
                                 appRequestSummary.minRefreshRate,
@@ -357,7 +365,7 @@
         for (Display.Mode mode : supportedModes) {
             if (mode.getPhysicalWidth() != summary.width
                     || mode.getPhysicalHeight() != summary.height) {
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
                             + ": desiredWidth=" + summary.width
                             + ": desiredHeight=" + summary.height
@@ -372,7 +380,7 @@
             // comparison.
             if (refreshRate < (summary.minRefreshRate - FLOAT_TOLERANCE)
                     || refreshRate > (summary.maxRefreshRate + FLOAT_TOLERANCE)) {
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.w(TAG, "Discarding mode " + mode.getModeId()
                             + ", outside refresh rate bounds"
                             + ": minRefreshRate=" + summary.minRefreshRate
@@ -516,7 +524,7 @@
     }
 
     private void updateVoteLocked(int displayId, int priority, Vote vote) {
-        if (DEBUG) {
+        if (mLoggingEnabled) {
             Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
                     + ", priority=" + Vote.priorityToString(priority)
                     + ", vote=" + vote + ")");
@@ -537,7 +545,7 @@
         }
 
         if (votes.size() == 0) {
-            if (DEBUG) {
+            if (mLoggingEnabled) {
                 Slog.i(TAG, "No votes left for display " + displayId + ", removing.");
             }
             mVotesByDisplay.remove(displayId);
@@ -1287,6 +1295,7 @@
         private boolean mShouldObserveAmbientLowChange;
         private boolean mShouldObserveDisplayHighChange;
         private boolean mShouldObserveAmbientHighChange;
+        private boolean mLoggingEnabled;
 
         private SensorManager mSensorManager;
         private Sensor mLightSensor;
@@ -1303,7 +1312,6 @@
         // changeable and low power mode off. After initialization, these states will
         // be updated from the same handler thread.
         private int mDefaultDisplayState = Display.STATE_UNKNOWN;
-        private boolean mIsDeviceActive = false;
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
@@ -1415,6 +1423,14 @@
             mDeviceConfigDisplaySettings.startListening();
         }
 
+        public void setLoggingEnabled(boolean loggingEnabled) {
+            if (mLoggingEnabled == loggingEnabled) {
+                return;
+            }
+            mLoggingEnabled = loggingEnabled;
+            mLightSensorListener.setLoggingEnabled(loggingEnabled);
+        }
+
         public void onRefreshRateSettingChangedLocked(float min, float max) {
             boolean changeable = (max - min > 1f && max > 60f);
             if (mRefreshRateChangeable != changeable) {
@@ -1485,7 +1501,6 @@
             pw.println("    mAmbientLux: " + mAmbientLux);
             pw.println("    mBrightness: " + mBrightness);
             pw.println("    mDefaultDisplayState: " + mDefaultDisplayState);
-            pw.println("    mIsDeviceActive: " + mIsDeviceActive);
             pw.println("    mLowPowerModeEnabled: " + mLowPowerModeEnabled);
             pw.println("    mRefreshRateChangeable: " + mRefreshRateChangeable);
             pw.println("    mShouldObserveDisplayLowChange: " + mShouldObserveDisplayLowChange);
@@ -1691,7 +1706,7 @@
                 vote = Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone);
             }
 
-            if (DEBUG) {
+            if (mLoggingEnabled) {
                 Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " +  mAmbientLux
                         + ", Vote " + vote);
             }
@@ -1720,6 +1735,11 @@
 
         @VisibleForTesting
         public void setDefaultDisplayState(int state) {
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "setDefaultDisplayState: mDefaultDisplayState = "
+                        + mDefaultDisplayState + ", state = " + state);
+            }
+
             if (mDefaultDisplayState != state) {
                 mDefaultDisplayState = state;
                 updateSensorStatus();
@@ -1731,36 +1751,58 @@
                 return;
             }
 
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "updateSensorStatus: mShouldObserveAmbientLowChange = "
+                        + mShouldObserveAmbientLowChange + ", mShouldObserveAmbientHighChange = "
+                        + mShouldObserveAmbientHighChange);
+                Slog.d(TAG, "updateSensorStatus: mLowPowerModeEnabled = "
+                        + mLowPowerModeEnabled + ", mRefreshRateChangeable = "
+                        + mRefreshRateChangeable);
+            }
+
             if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
                      && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
                 mSensorManager.registerListener(mLightSensorListener,
                         mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
+                if (mLoggingEnabled) {
+                    Slog.d(TAG, "updateSensorStatus: registerListener");
+                }
             } else {
                 mLightSensorListener.removeCallbacks();
                 mSensorManager.unregisterListener(mLightSensorListener);
+                if (mLoggingEnabled) {
+                    Slog.d(TAG, "updateSensorStatus: unregisterListener");
+                }
             }
         }
 
         private boolean isDeviceActive() {
-            mIsDeviceActive = mInjector.isDeviceInteractive(mContext);
-            return (mDefaultDisplayState == Display.STATE_ON)
-                    && mIsDeviceActive;
+            return mDefaultDisplayState == Display.STATE_ON;
         }
 
         private final class LightSensorEventListener implements SensorEventListener {
             final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
             private float mLastSensorData;
             private long mTimestamp;
+            private boolean mLoggingEnabled;
 
             public void dumpLocked(PrintWriter pw) {
                 pw.println("    mLastSensorData: " + mLastSensorData);
                 pw.println("    mTimestamp: " + formatTimestamp(mTimestamp));
             }
 
+
+            public void setLoggingEnabled(boolean loggingEnabled) {
+                if (mLoggingEnabled == loggingEnabled) {
+                    return;
+                }
+                mLoggingEnabled = loggingEnabled;
+            }
+
             @Override
             public void onSensorChanged(SensorEvent event) {
                 mLastSensorData = event.values[0];
-                if (DEBUG) {
+                if (mLoggingEnabled) {
                     Slog.d(TAG, "On sensor changed: " + mLastSensorData);
                 }
 
@@ -2009,8 +2051,6 @@
 
         void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
                 @NonNull ContentObserver observer);
-
-        boolean isDeviceInteractive(@NonNull Context context);
     }
 
     @VisibleForTesting
@@ -2041,11 +2081,6 @@
             cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
                     observer, UserHandle.USER_SYSTEM);
         }
-
-        @Override
-        public boolean isDeviceInteractive(@NonNull Context ctx) {
-            return ctx.getSystemService(PowerManager.class).isInteractive();
-        }
     }
 
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d9ee9a3..2df33652 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -47,6 +47,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.TimeUtils;
@@ -58,6 +59,8 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
+import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings;
@@ -152,6 +155,7 @@
     private final DisplayPowerCallbacks mCallbacks;
 
     // Battery stats.
+    @Nullable
     private final IBatteryStats mBatteryStats;
 
     // The sensor manager.
@@ -170,6 +174,7 @@
     private final int mDisplayId;
 
     // Tracker for brightness changes.
+    @Nullable
     private final BrightnessTracker mBrightnessTracker;
 
     // Tracker for brightness settings changes.
@@ -346,6 +351,9 @@
     @Nullable
     private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
 
+    private final ColorDisplayServiceInternal mCdsi;
+    private final float[] mNitsRange;
+
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
 
@@ -401,30 +409,30 @@
     private ObjectAnimator mColorFadeOffAnimator;
     private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
 
-    // The brightness synchronizer to allow changes in the int brightness value to be reflected in
-    // the float brightness value and vice versa.
-    @Nullable
-    private final BrightnessSynchronizer mBrightnessSynchronizer;
-
     /**
      * Creates the display power controller.
      */
     public DisplayPowerController(Context context,
             DisplayPowerCallbacks callbacks, Handler handler,
             SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay) {
+        mLogicalDisplay = logicalDisplay;
+        mDisplayId = mLogicalDisplay.getDisplayIdLocked();
         mHandler = new DisplayControllerHandler(handler.getLooper());
-        mBrightnessTracker = new BrightnessTracker(context, null);
+
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mBrightnessTracker = new BrightnessTracker(context, null);
+            mBatteryStats = BatteryStatsService.getService();
+        } else {
+            mBrightnessTracker = null;
+            mBatteryStats = null;
+        }
+
         mSettingsObserver = new SettingsObserver(mHandler);
         mCallbacks = callbacks;
-        mBatteryStats = BatteryStatsService.getService();
         mSensorManager = sensorManager;
         mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
         mBlanker = blanker;
         mContext = context;
-        mBrightnessSynchronizer = new BrightnessSynchronizer(context);
-        mBrightnessSynchronizer.startSynchronizing();
-        mLogicalDisplay = logicalDisplay;
-        mDisplayId = mLogicalDisplay.getDisplayIdLocked();
 
         PowerManager pm = context.getSystemService(PowerManager.class);
 
@@ -455,8 +463,12 @@
         mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
                 pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
 
+        // Check the setting, but also verify that it is the default display. Only the default
+        // display has an automatic brightness controller running.
+        // TODO: b/179021925 - Fix to work with multiple displays
         mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
-                com.android.internal.R.bool.config_automatic_brightness_available);
+                com.android.internal.R.bool.config_automatic_brightness_available)
+                && mDisplayId == Display.DEFAULT_DISPLAY;
 
         mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
@@ -552,7 +564,9 @@
         mBrightnessBucketsInDozeConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_displayBrightnessBucketsInDoze);
 
-        if (!DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+        if (mDisplayId == Display.DEFAULT_DISPLAY && !DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+            // TODO: b/178385123 Once there are sensor associations, we can enable proximity for
+            // non-default displays.
             mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
             if (mProximitySensor != null) {
                 mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
@@ -580,6 +594,42 @@
         }
         mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
         mDisplayWhiteBalanceController = displayWhiteBalanceController;
+
+        if (displayDeviceConfig != null && displayDeviceConfig.getNits() != null) {
+            mNitsRange = displayDeviceConfig.getNits();
+        } else {
+            Slog.w(TAG, "Screen brightness nits configuration is unavailable; falling back");
+            mNitsRange = BrightnessMappingStrategy.getFloatArray(context.getResources()
+                    .obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits));
+        }
+        mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
+        boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() {
+            @Override
+            public void onReduceBrightColorsActivationChanged(boolean activated) {
+                applyReduceBrightColorsSplineAdjustment();
+            }
+
+            @Override
+            public void onReduceBrightColorsStrengthChanged(int strength) {
+                applyReduceBrightColorsSplineAdjustment();
+            }
+        });
+        if (active) {
+            applyReduceBrightColorsSplineAdjustment();
+        }
+    }
+
+    private void applyReduceBrightColorsSplineAdjustment() {
+        if (mBrightnessMapper == null) {
+            Log.w(TAG, "No brightness mapping available to recalculate splines");
+            return;
+        }
+
+        float[] adjustedNits = new float[mNitsRange.length];
+        for (int i = 0; i < mNitsRange.length; i++) {
+            adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
+        }
+        mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits);
     }
 
     private Sensor findDisplayLightSensor(String sensorType) {
@@ -607,18 +657,28 @@
      * @param userId userId to fetch data for
      * @param includePackage if false will null out the package name in events
      */
+    @Nullable
     public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
             @UserIdInt int userId, boolean includePackage) {
+        if (mBrightnessTracker == null) {
+            return null;
+        }
         return mBrightnessTracker.getEvents(userId, includePackage);
     }
 
     public void onSwitchUser(@UserIdInt int newUserId) {
         handleSettingsChange(true /* userSwitch */);
-        mBrightnessTracker.onSwitchUser(newUserId);
+        if (mBrightnessTracker != null) {
+            mBrightnessTracker.onSwitchUser(newUserId);
+        }
     }
 
+    @Nullable
     public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
             @UserIdInt int userId) {
+        if (mBrightnessTracker == null) {
+            return null;
+        }
         return mBrightnessTracker.getAmbientBrightnessStats(userId);
     }
 
@@ -626,7 +686,9 @@
      * Persist the brightness slider events and ambient brightness stats to disk.
      */
     public void persistBrightnessTrackerState() {
-        mBrightnessTracker.persistBrightnessTrackerState();
+        if (mBrightnessTracker != null) {
+            mBrightnessTracker.persistBrightnessTrackerState();
+        }
     }
 
     /**
@@ -710,9 +772,9 @@
         }
     }
 
-    private void initialize() {
+    private void initialize(int displayState) {
         mPowerState = new DisplayPowerState(mBlanker,
-                mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId);
+                mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState);
 
         if (mColorFadeEnabled) {
             mColorFadeOnAnimator = ObjectAnimator.ofFloat(
@@ -730,20 +792,16 @@
                 mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT);
         mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
 
-        // Initialize screen state for battery stats.
-        try {
-            mBatteryStats.noteScreenState(mPowerState.getScreenState());
-            mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
-                    mPowerState.getScreenBrightness()));
-        } catch (RemoteException ex) {
-            // same process
-        }
+        noteScreenState(mPowerState.getScreenState());
+        noteScreenBrightness(mPowerState.getScreenBrightness());
+
         // Initialize all of the brightness tracking state
         final float brightness = convertToNits(BrightnessSynchronizer.brightnessFloatToInt(
                 mPowerState.getScreenBrightness()));
         if (brightness >= 0.0f) {
             mBrightnessTracker.start(brightness);
         }
+
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FLOAT),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
@@ -812,11 +870,6 @@
             mustNotify = !mDisplayReadyLocked;
         }
 
-        // Initialize things the first time the power state is changed.
-        if (mustInitialize) {
-            initialize();
-        }
-
         // Compute the basic display state using the policy.
         // We might override this below based on other factors.
         // Initialise brightness as invalid.
@@ -850,6 +903,11 @@
         }
         assert(state != Display.STATE_UNKNOWN);
 
+        // Initialize things the first time the power state is changed.
+        if (mustInitialize) {
+            initialize(state);
+        }
+
         // Apply the proximity sensor.
         if (mProximitySensor != null) {
             if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
@@ -1330,11 +1388,7 @@
                 SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
-                try {
-                    mBatteryStats.noteScreenState(state);
-                } catch (RemoteException ex) {
-                    // same process
-                }
+                noteScreenState(state);
             }
         }
 
@@ -1406,13 +1460,7 @@
             Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
             // TODO(b/153319140) remove when we can get this from the above trace invocation
             SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
-            try {
-                // TODO(brightnessfloat): change BatteryStats to use float
-                mBatteryStats.noteScreenBrightness(
-                        BrightnessSynchronizer.brightnessFloatToInt(target));
-            } catch (RemoteException ex) {
-                // same process
-            }
+            noteScreenBrightness(target);
         }
     }
 
@@ -1731,15 +1779,21 @@
     }
 
     private void putScreenBrightnessSetting(float brightnessValue) {
-        mCurrentScreenBrightnessSetting = brightnessValue;
-        Settings.System.putFloatForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessValue, UserHandle.USER_CURRENT);
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mCurrentScreenBrightnessSetting = brightnessValue;
+            Settings.System.putFloatForUser(mContext.getContentResolver(),
+                    Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessValue,
+                    UserHandle.USER_CURRENT);
+        }
     }
 
     private void putAutoBrightnessAdjustmentSetting(float adjustment) {
-        mAutoBrightnessAdjustment = adjustment;
-        Settings.System.putFloatForUser(mContext.getContentResolver(),
-                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment, UserHandle.USER_CURRENT);
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mAutoBrightnessAdjustment = adjustment;
+            Settings.System.putFloatForUser(mContext.getContentResolver(),
+                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+                    UserHandle.USER_CURRENT);
+        }
     }
 
     private boolean updateAutoBrightnessAdjustment() {
@@ -2020,6 +2074,29 @@
         return MathUtils.constrain(value, -1.0f, 1.0f);
     }
 
+    private void noteScreenState(int screenState) {
+        if (mBatteryStats != null) {
+            try {
+                // TODO(multi-display): make this multi-display
+                mBatteryStats.noteScreenState(screenState);
+            } catch (RemoteException e) {
+                // same process
+            }
+        }
+    }
+
+    private void noteScreenBrightness(float brightness) {
+        if (mBatteryStats != null) {
+            try {
+                // TODO(brightnessfloat): change BatteryStats to use float
+                mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
+                        brightness));
+            } catch (RemoteException e) {
+                // same process
+            }
+        }
+    }
+
     private final class DisplayControllerHandler extends Handler {
         public DisplayControllerHandler(Looper looper) {
             super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 54f30a9..173adce 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -72,7 +72,8 @@
 
     private Runnable mCleanListener;
 
-    public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, int displayId) {
+    DisplayPowerState(
+            DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
         mHandler = new Handler(true /*async*/);
         mChoreographer = Choreographer.getInstance();
         mBlanker = blanker;
@@ -81,14 +82,14 @@
         mPhotonicModulator.start();
         mDisplayId = displayId;
 
-        // At boot time, we know that the screen is on and the electron beam
-        // animation is not playing.  We don't know the screen's brightness though,
+        // At boot time, we don't know the screen's brightness,
         // so prepare to set it to a known state when the state is next applied.
-        // Although we set the brightness to full on here, the display power controller
+        // Although we set the brightness here, the display power controller
         // will reset the brightness to a new level immediately before the changes
         // actually have a chance to be applied.
-        mScreenState = Display.STATE_ON;
-        mScreenBrightness = PowerManager.BRIGHTNESS_MAX;
+        mScreenState = displayState;
+        mScreenBrightness = (displayState != Display.STATE_OFF) ? PowerManager.BRIGHTNESS_MAX
+                : PowerManager.BRIGHTNESS_OFF_FLOAT;
         scheduleScreenUpdate();
 
         mColorFadePrepared = false;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8198e51..7e6a137 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -35,6 +35,7 @@
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
+import android.view.RoundedCorners;
 import android.view.SurfaceControl;
 
 import com.android.internal.BrightnessSynchronizer;
@@ -604,6 +605,8 @@
                     }
                     mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
                             mInfo.width, mInfo.height);
+                    mInfo.roundedCorners = RoundedCorners.fromResources(
+                            res, mInfo.width, mInfo.height);
                 } else {
                     if (!res.getBoolean(
                                 com.android.internal.R.bool.config_localDisplaysMirrorContent)) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 5bf83db..80781d2 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -71,6 +71,9 @@
 
     private final int mDisplayId;
     private final int mLayerStack;
+
+    private int mDisplayGroupId = Display.INVALID_DISPLAY_GROUP;
+
     /**
      * Override information set by the window manager. Will be reported instead of {@link #mInfo}
      * if not null.
@@ -192,6 +195,7 @@
                 info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
                 info.physicalXDpi = mOverrideDisplayInfo.physicalXDpi;
                 info.physicalYDpi = mOverrideDisplayInfo.physicalYDpi;
+                info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
             }
             mInfo.set(info);
         }
@@ -265,6 +269,19 @@
     }
 
     /**
+     * Updates the {@link DisplayGroup} to which the logical display belongs.
+     *
+     * @param groupId Identifier for the {@link DisplayGroup}.
+     */
+    public void updateDisplayGroupIdLocked(int groupId) {
+        if (groupId != mDisplayGroupId) {
+            mDisplayGroupId = groupId;
+            mBaseDisplayInfo.displayGroupId = groupId;
+            mInfo.set(null);
+        }
+    }
+
+    /**
      * Updates the state of the logical display based on the available display devices.
      * The logical display might become invalid if it is attached to a display device
      * that no longer exists.
@@ -365,10 +382,12 @@
                     (deviceInfo.flags & DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT) != 0;
             mBaseDisplayInfo.displayCutout = maskCutout ? null : deviceInfo.displayCutout;
             mBaseDisplayInfo.displayId = mDisplayId;
+            mBaseDisplayInfo.displayGroupId = mDisplayGroupId;
             updateFrameRateOverrides(deviceInfo);
             mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum;
             mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum;
             mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
+            mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo.set(null);
         }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index bb2fbed..16c4b26 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -55,6 +55,10 @@
     public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
     public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
 
+    public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
+    public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
+    public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;
+
     /**
      * Temporary display info, used for comparing display configurations.
      */
@@ -96,14 +100,14 @@
     private final SparseArray<LogicalDisplay> mLogicalDisplays =
             new SparseArray<LogicalDisplay>();
     private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
-    private int mNextNonDefaultGroupId = DisplayGroup.DEFAULT + 1;
+    private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
 
     /** A mapping from logical display id to display group. */
-    private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
+    private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
 
     private final DisplayDeviceRepository mDisplayDeviceRepo;
     private final Listener mListener;
-    private final int mFoldedDeviceState;
+    private final int[] mFoldedDeviceStates;
 
     LogicalDisplayMapper(Context context, DisplayDeviceRepository repo, Listener listener) {
         mDisplayDeviceRepo = repo;
@@ -111,8 +115,8 @@
         mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
         mDisplayDeviceRepo.addListener(this);
 
-        mFoldedDeviceState = context.getResources().getInteger(
-                com.android.internal.R.integer.config_foldedDeviceState);
+        mFoldedDeviceStates = context.getResources().getIntArray(
+                com.android.internal.R.array.config_foldedDeviceStates);
 
         loadFoldedDisplayConfig(context);
     }
@@ -183,7 +187,7 @@
     }
 
     public int getDisplayGroupIdLocked(int displayId) {
-        final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+        final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
         if (displayGroup != null) {
             return displayGroup.getGroupId();
         }
@@ -191,6 +195,18 @@
         return -1;
     }
 
+    public DisplayGroup getDisplayGroupLocked(int groupId) {
+        final int size = mDisplayIdToGroupMap.size();
+        for (int i = 0; i < size; i++) {
+            final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i);
+            if (displayGroup.getGroupId() == groupId) {
+                return displayGroup;
+            }
+        }
+
+        return null;
+    }
+
     public void dumpLocked(PrintWriter pw) {
         pw.println("LogicalDisplayMapper:");
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
@@ -216,7 +232,14 @@
     }
 
     void setDeviceStateLocked(int state) {
-        setDeviceFoldedLocked(state == mFoldedDeviceState);
+        boolean folded = false;
+        for (int i = 0; i < mFoldedDeviceStates.length; i++) {
+            if (state == mFoldedDeviceStates[i]) {
+                folded = true;
+                break;
+            }
+        }
+        setDeviceFoldedLocked(folded);
     }
 
     void setDeviceFoldedLocked(boolean isFolded) {
@@ -313,7 +336,18 @@
         final int displayId = assignDisplayIdLocked(isDefault);
         final int layerStack = assignLayerStackLocked(displayId);
 
+        final DisplayGroup displayGroup;
+        final boolean addNewDisplayGroup =
+                isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0;
+        if (addNewDisplayGroup) {
+            final int groupId = assignDisplayGroupIdLocked(isDefault);
+            displayGroup = new DisplayGroup(groupId);
+        } else {
+            displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY);
+        }
+
         LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+        display.updateDisplayGroupIdLocked(displayGroup.getGroupId());
         display.updateLocked(mDisplayDeviceRepo);
         if (!display.isValidLocked()) {
             // This should never happen currently.
@@ -324,18 +358,23 @@
 
         mLogicalDisplays.put(displayId, display);
 
-        final DisplayGroup displayGroup;
-        if (isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
-            final int groupId = assignDisplayGroupIdLocked(isDefault);
-            displayGroup = new DisplayGroup(groupId);
-        } else {
-            displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY);
+        displayGroup.addDisplayLocked(display);
+        mDisplayIdToGroupMap.append(displayId, displayGroup);
+
+        if (addNewDisplayGroup) {
+            // Group added events happen before Logical Display added events.
+            mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                    LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
         }
-        displayGroup.addDisplay(display);
-        mDisplayGroups.append(displayId, displayGroup);
 
         mListener.onLogicalDisplayEventLocked(display,
                 LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED);
+
+        if (!addNewDisplayGroup) {
+            // Group changed events happen after Logical Display added events.
+            mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                    LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
+        }
     }
 
     /**
@@ -352,31 +391,47 @@
             DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides =
                     display.getFrameRateOverrides();
             display.updateLocked(mDisplayDeviceRepo);
+            final DisplayGroup changedDisplayGroup;
             if (!display.isValidLocked()) {
                 mLogicalDisplays.removeAt(i);
-                mDisplayGroups.removeReturnOld(displayId).removeDisplay(display);
+                final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId);
+                displayGroup.removeDisplayLocked(display);
 
                 mListener.onLogicalDisplayEventLocked(display,
                         LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
+
+                changedDisplayGroup = displayGroup;
             } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
                 final int flags = display.getDisplayInfoLocked().flags;
-                final DisplayGroup defaultDisplayGroup = mDisplayGroups.get(
+                final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get(
                         Display.DEFAULT_DISPLAY);
                 if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
                     // The display should have its own DisplayGroup.
-                    if (defaultDisplayGroup.removeDisplay(display)) {
+                    if (defaultDisplayGroup.removeDisplayLocked(display)) {
                         final int groupId = assignDisplayGroupIdLocked(false);
                         final DisplayGroup displayGroup = new DisplayGroup(groupId);
-                        displayGroup.addDisplay(display);
-                        mDisplayGroups.append(display.getDisplayIdLocked(), displayGroup);
+                        displayGroup.addDisplayLocked(display);
+                        display.updateDisplayGroupIdLocked(groupId);
+                        mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup);
+                        mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                                LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
+                        changedDisplayGroup = defaultDisplayGroup;
+                    } else {
+                        changedDisplayGroup = null;
                     }
                 } else {
                     // The display should be a part of the default DisplayGroup.
-                    final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+                    final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
                     if (displayGroup != defaultDisplayGroup) {
-                        displayGroup.removeDisplay(display);
-                        defaultDisplayGroup.addDisplay(display);
-                        mDisplayGroups.put(displayId, defaultDisplayGroup);
+                        displayGroup.removeDisplayLocked(display);
+                        defaultDisplayGroup.addDisplayLocked(display);
+                        display.updateDisplayGroupIdLocked(defaultDisplayGroup.getGroupId());
+                        mListener.onDisplayGroupEventLocked(defaultDisplayGroup.getGroupId(),
+                                LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
+                        mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup);
+                        changedDisplayGroup = displayGroup;
+                    } else {
+                        changedDisplayGroup = null;
                     }
                 }
 
@@ -388,6 +443,7 @@
             } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
                 mListener.onLogicalDisplayEventLocked(display,
                         LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+                changedDisplayGroup = null;
             } else {
                 // While applications shouldn't know nor care about the non-overridden info, we
                 // still need to let WindowManager know so it can update its own internal state for
@@ -397,6 +453,15 @@
                     mListener.onLogicalDisplayEventLocked(display,
                             LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
                 }
+                changedDisplayGroup = null;
+            }
+
+            // CHANGED and REMOVED DisplayGroup events should always fire after Display events.
+            if (changedDisplayGroup != null) {
+                final int event = changedDisplayGroup.isEmptyLocked()
+                        ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED
+                        : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED;
+                mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event);
             }
         }
     }
@@ -406,7 +471,7 @@
     }
 
     private int assignDisplayGroupIdLocked(boolean isDefault) {
-        return isDefault ? DisplayGroup.DEFAULT : mNextNonDefaultGroupId++;
+        return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++;
     }
 
     private int assignLayerStackLocked(int displayId) {
@@ -432,6 +497,7 @@
 
     public interface Listener {
         void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
+        void onDisplayGroupEventLocked(int groupId, int event);
         void onTraversalRequested();
     }
 }
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 4706edc..88b2668 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -173,6 +173,7 @@
     private ContentObserver mContentObserver;
 
     private DisplayWhiteBalanceListener mDisplayWhiteBalanceListener;
+    private ReduceBrightColorsListener mReduceBrightColorsListener;
 
     private NightDisplayAutoMode mNightDisplayAutoMode;
 
@@ -617,18 +618,24 @@
         if (mCurrentUser == UserHandle.USER_NULL) {
             return;
         }
-        mReduceBrightColorsTintController.setActivated(
-                Secure.getIntForUser(getContext().getContentResolver(),
-                        Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0, mCurrentUser) == 1);
+        final boolean activated = Secure.getIntForUser(getContext().getContentResolver(),
+                Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0, mCurrentUser) == 1;
+        mReduceBrightColorsTintController.setActivated(activated);
+        if (mReduceBrightColorsListener != null) {
+            mReduceBrightColorsListener.onReduceBrightColorsActivationChanged(activated);
+        }
     }
 
     private void onReduceBrightColorsStrengthLevelChanged() {
         if (mCurrentUser == UserHandle.USER_NULL) {
             return;
         }
-        mReduceBrightColorsTintController.setMatrix(
-                Secure.getIntForUser(getContext().getContentResolver(),
-                        Secure.REDUCE_BRIGHT_COLORS_LEVEL, 0, mCurrentUser));
+        final int strength = Secure.getIntForUser(getContext().getContentResolver(),
+                Secure.REDUCE_BRIGHT_COLORS_LEVEL, 0, mCurrentUser);
+        mReduceBrightColorsTintController.setMatrix(strength);
+        if (mReduceBrightColorsListener != null) {
+            mReduceBrightColorsListener.onReduceBrightColorsStrengthChanged(strength);
+        }
     }
 
     /**
@@ -762,6 +769,22 @@
                 mCurrentUser) == 1;
     }
 
+    private boolean setReduceBrightColorsActivatedInternal(boolean activated) {
+        if (mCurrentUser == UserHandle.USER_NULL) {
+            return false;
+        }
+        return Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, activated ? 1 : 0, mCurrentUser);
+    }
+
+    private boolean setReduceBrightColorsStrengthInternal(int strength) {
+        if (mCurrentUser == UserHandle.USER_NULL) {
+            return false;
+        }
+        return Secure.putIntForUser(getContext().getContentResolver(),
+                Secure.REDUCE_BRIGHT_COLORS_LEVEL, strength, mCurrentUser);
+    }
+
     private boolean isDeviceColorManagedInternal() {
         final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
         return dtm.isDeviceColorManaged();
@@ -1469,6 +1492,31 @@
         }
 
         /**
+         * Sets the listener and returns whether reduce bright colors is currently enabled.
+         */
+        public boolean setReduceBrightColorsListener(ReduceBrightColorsListener listener) {
+            mReduceBrightColorsListener = listener;
+            return mReduceBrightColorsTintController.isActivated();
+        }
+
+        /**
+         * Returns whether reduce bright colors is currently active.
+         */
+        public boolean isReduceBrightColorsActivated() {
+            return mReduceBrightColorsTintController.isActivated();
+        }
+
+        /**
+         * Gets the computed brightness, in nits, when the reduce bright colors feature is applied
+         * at the current strength.
+         *
+         * @hide
+         */
+        public float getReduceBrightColorsAdjustedBrightnessNits(float nits) {
+            return mReduceBrightColorsTintController.getAdjustedBrightness(nits);
+        }
+
+        /**
          * Adds a {@link WeakReference<ColorTransformController>} for a newly started activity, and
          * invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed.
          */
@@ -1491,6 +1539,22 @@
         void onDisplayWhiteBalanceStatusChanged(boolean activated);
     }
 
+    /**
+     * Listener for changes in reduce bright colors status.
+     */
+    public interface ReduceBrightColorsListener {
+
+        /**
+         * Notify that the reduce bright colors activation status has changed.
+         */
+        void onReduceBrightColorsActivationChanged(boolean activated);
+
+        /**
+         * Notify that the reduce bright colors strength has changed.
+         */
+        void onReduceBrightColorsStrengthChanged(int strength);
+    }
+
     private final class TintHandler extends Handler {
 
         private TintHandler(Looper looper) {
@@ -1787,6 +1851,62 @@
         }
 
         @Override
+        public boolean isReduceBrightColorsActivated() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mReduceBrightColorsTintController.isActivated();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public boolean setReduceBrightColorsActivated(boolean activated) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set reduce bright colors activation state");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return setReduceBrightColorsActivatedInternal(activated);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public int getReduceBrightColorsStrength() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mReduceBrightColorsTintController.getStrength();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public float getReduceBrightColorsOffsetFactor() {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mReduceBrightColorsTintController.getOffsetFactor();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public boolean setReduceBrightColorsStrength(int strength) {
+            getContext().enforceCallingOrSelfPermission(
+                    Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
+                    "Permission required to set reduce bright colors strength");
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return setReduceBrightColorsStrengthInternal(strength);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
                 return;
diff --git a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
index 7e120c9..cb93cc8 100644
--- a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
+++ b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
@@ -34,7 +34,7 @@
 public class ReduceBrightColorsTintController extends TintController {
 
     private final float[] mMatrix = new float[16];
-    private final float[] mCoefficients = new float[9];
+    private final float[] mCoefficients = new float[3];
 
     private int mStrength;
 
@@ -42,8 +42,8 @@
     public void setUp(Context context, boolean needsLinear) {
         final String[] coefficients = context.getResources().getStringArray(
                 needsLinear ? R.array.config_reduceBrightColorsCoefficients
-                        : R.array.config_reduceBrightColorsCoefficientsNative);
-        for (int i = 0; i < 9 && i < coefficients.length; i++) {
+                        : R.array.config_reduceBrightColorsCoefficientsNonlinear);
+        for (int i = 0; i < 3 && i < coefficients.length; i++) {
             mCoefficients[i] = Float.parseFloat(coefficients[i]);
         }
     }
@@ -67,20 +67,11 @@
 
         Matrix.setIdentityM(mMatrix, 0);
 
-        final float percentageStrength = strengthLevel / 100f;
-        final float squaredPercentageStrength = percentageStrength * percentageStrength;
-        final float red =
-                squaredPercentageStrength * mCoefficients[0] + percentageStrength * mCoefficients[1]
-                        + mCoefficients[2];
-        final float green =
-                squaredPercentageStrength * mCoefficients[3] + percentageStrength * mCoefficients[4]
-                        + mCoefficients[5];
-        final float blue =
-                squaredPercentageStrength * mCoefficients[6] + percentageStrength * mCoefficients[7]
-                        + mCoefficients[8];
-        mMatrix[0] = clamp(red);
-        mMatrix[5] = clamp(green);
-        mMatrix[10] = clamp(blue);
+        // All three (r,g,b) components are equal and calculated with the same formula.
+        final float componentValue = computeComponentValue(strengthLevel);
+        mMatrix[0] = componentValue;
+        mMatrix[5] = componentValue;
+        mMatrix[10] = componentValue;
     }
 
     private float clamp(float value) {
@@ -110,4 +101,26 @@
     public int getStrength() {
         return mStrength;
     }
+
+    /** Returns the offset factor at Ymax. */
+    public float getOffsetFactor() {
+        // Strength terms drop out as strength --> 1, leaving the coefficients.
+        return mCoefficients[0] + mCoefficients[1] + mCoefficients[2];
+    }
+
+    /**
+     * Returns the effective brightness (in nits), which has been adjusted to account for the effect
+     * of the bright color reduction.
+     */
+    public float getAdjustedBrightness(float nits) {
+        return computeComponentValue(mStrength) * nits;
+    }
+
+    private float computeComponentValue(int strengthLevel) {
+        final float percentageStrength = strengthLevel / 100f;
+        final float squaredPercentageStrength = percentageStrength * percentageStrength;
+        return clamp(
+                squaredPercentageStrength * mCoefficients[0] + percentageStrength * mCoefficients[1]
+                        + mCoefficients[2]);
+    }
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java
new file mode 100644
index 0000000..b082b25
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/FontCrashDetector.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A class to detect font-related native crash.
+ *
+ * <p>If a fs-verity protected file is accessed through mmap and corrupted file block is detected,
+ * SIGBUG signal is generated and the process will crash. To find corrupted files and remove them,
+ * we use a marker file to detect crash.
+ * <ol>
+ *     <li>Create a marker file before reading fs-verity protected font files.
+ *     <li>Delete the marker file after reading font files successfully.
+ *     <li>If the marker file is found in the next process startup, it means that the process
+ *         crashed before. We will delete font files to prevent crash loop.
+ * </ol>
+ *
+ * <p>Example usage:
+ * <pre>
+ *     FontCrashDetector detector = new FontCrashDetector(new File("/path/to/marker_file"));
+ *     if (detector.hasCrashed()) {
+ *         // Do cleanup
+ *     }
+ *     try (FontCrashDetector.MonitoredBlock b = detector.start()) {
+ *         // Read files
+ *     }
+ * </pre>
+ *
+ * <p>This class DOES NOT detect Java exceptions. If a Java exception is thrown while monitoring
+ * crash, the marker file will be deleted. Creating and deleting marker files are not lightweight.
+ * Please use this class sparingly with caution.
+ */
+/* package */ final class FontCrashDetector {
+
+    private static final String TAG = "FontCrashDetector";
+
+    @NonNull
+    private final File mMarkerFile;
+
+    /* package */ FontCrashDetector(@NonNull File markerFile) {
+        mMarkerFile = markerFile;
+    }
+
+    /* package */ boolean hasCrashed() {
+        return mMarkerFile.exists();
+    }
+
+    /* package */ void clear() {
+        if (!mMarkerFile.delete()) {
+            Slog.e(TAG, "Could not delete marker file: " + mMarkerFile);
+        }
+    }
+
+    /** Starts crash monitoring. */
+    /* package */ MonitoredBlock start() {
+        try {
+            mMarkerFile.createNewFile();
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not create marker file: " + mMarkerFile, e);
+        }
+        return new MonitoredBlock();
+    }
+
+    /** A helper class to monitor crash with try-with-resources syntax. */
+    /* package */ class MonitoredBlock implements AutoCloseable {
+        /** Ends crash monitoring. */
+        @Override
+        public void close() {
+            clear();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index bda4240..1b27572 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -16,24 +16,29 @@
 
 package com.android.server.graphics.fonts;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Typeface;
 import android.graphics.fonts.FontFamily;
 import android.graphics.fonts.FontFileUtil;
+import android.graphics.fonts.FontManager;
+import android.graphics.fonts.FontUpdateRequest;
 import android.graphics.fonts.SystemFonts;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.SharedMemory;
+import android.os.ShellCallback;
 import android.system.ErrnoException;
 import android.text.FontConfig;
+import android.util.AndroidException;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.graphics.fonts.IFontManager;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.security.FileIntegrityService;
@@ -48,20 +53,58 @@
 import java.nio.NioUtils;
 import java.nio.channels.FileChannel;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /** A service for managing system fonts. */
 // TODO(b/173619554): Add API to update fonts.
 public final class FontManagerService extends IFontManager.Stub {
     private static final String TAG = "FontManagerService";
 
-    // TODO: make this a DeviceConfig flag.
-    private static final boolean ENABLE_FONT_UPDATES = false;
     private static final String FONT_FILES_DIR = "/data/fonts/files";
+    private static final String CRASH_MARKER_FILE = "/data/fonts/config/crash.txt";
 
     @Override
-    public FontConfig getFontConfig() throws RemoteException {
-        return getCurrentFontSettings().getSystemFontConfig();
+    public FontConfig getFontConfig() {
+        return getSystemFontConfig();
+    }
+
+    @Override
+    public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) {
+        Objects.requireNonNull(request);
+        Objects.requireNonNull(request.getFd());
+        Objects.requireNonNull(request.getSignature());
+        Preconditions.checkArgumentNonnegative(baseVersion);
+        getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
+                "UPDATE_FONTS permission required.");
+        try {
+            update(baseVersion, Collections.singletonList(request));
+            return FontManager.RESULT_SUCCESS;
+        } catch (SystemFontException e) {
+            Slog.e(TAG, "Failed to update font file", e);
+            return e.getErrorCode();
+        }
+    }
+
+    /* package */ static class SystemFontException extends AndroidException {
+        private final int mErrorCode;
+
+        SystemFontException(@FontManager.ResultCode int errorCode, String msg, Throwable cause) {
+            super(msg, cause);
+            mErrorCode = errorCode;
+        }
+
+        SystemFontException(int errorCode, String msg) {
+            super(msg);
+            mErrorCode = errorCode;
+        }
+
+        @FontManager.ResultCode
+        int getErrorCode() {
+            return mErrorCode;
+        }
     }
 
     /** Class to manage FontManagerService's lifecycle. */
@@ -83,7 +126,7 @@
                             if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
                                 return null;
                             }
-                            return mService.getCurrentFontSettings().getSerializedSystemFontMap();
+                            return mService.getCurrentFontMap();
                         }
                     });
             publishBinderService(Context.FONT_SERVICE, mService);
@@ -137,21 +180,36 @@
         }
     }
 
+    @NonNull
+    private final Context mContext;
+
+    private final Object mUpdatableFontDirLock = new Object();
+
+    @GuardedBy("mUpdatableFontDirLock")
+    @NonNull
+    private final FontCrashDetector mFontCrashDetector;
+
+    @GuardedBy("mUpdatableFontDirLock")
     @Nullable
     private final UpdatableFontDir mUpdatableFontDir;
 
-    @GuardedBy("FontManagerService.this")
+    // mSerializedFontMapLock can be acquired while holding mUpdatableFontDirLock.
+    // mUpdatableFontDirLock should not be newly acquired while holding mSerializedFontMapLock.
+    private final Object mSerializedFontMapLock = new Object();
+
+    @GuardedBy("mSerializedFontMapLock")
     @Nullable
-    private SystemFontSettings mCurrentFontSettings = null;
+    private SharedMemory mSerializedFontMap = null;
 
     private FontManagerService(Context context) {
         mContext = context;
+        mFontCrashDetector = new FontCrashDetector(new File(CRASH_MARKER_FILE));
         mUpdatableFontDir = createUpdatableFontDir();
+        initialize();
     }
 
     @Nullable
     private static UpdatableFontDir createUpdatableFontDir() {
-        if (!ENABLE_FONT_UPDATES) return null;
         // If apk verity is supported, fs-verity should be available.
         if (!FileIntegrityService.isApkVeritySupported()) return null;
         return new UpdatableFontDir(new File(FONT_FILES_DIR),
@@ -160,37 +218,87 @@
                 new OtfFontFileParser(), new FsverityUtilImpl());
     }
 
-
-    @NonNull
-    private final Context mContext;
+    private void initialize() {
+        synchronized (mUpdatableFontDirLock) {
+            if (mUpdatableFontDir == null) {
+                synchronized (mSerializedFontMapLock) {
+                    try {
+                        mSerializedFontMap = Typeface.serializeFontMap(Typeface.getSystemFontMap());
+                    } catch (IOException | ErrnoException e) {
+                        mSerializedFontMap = null;
+                    }
+                }
+                return;
+            }
+            if (mFontCrashDetector.hasCrashed()) {
+                Slog.i(TAG, "Crash detected. Clearing font updates.");
+                try {
+                    mUpdatableFontDir.clearUpdates();
+                } catch (SystemFontException e) {
+                    Slog.e(TAG, "Failed to clear updates.", e);
+                }
+                mFontCrashDetector.clear();
+            }
+            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+                mUpdatableFontDir.loadFontFileMap();
+                updateSerializedFontMap();
+            }
+        }
+    }
 
     @NonNull
     public Context getContext() {
         return mContext;
     }
 
-    @NonNull /* package */ SystemFontSettings getCurrentFontSettings() {
-        synchronized (FontManagerService.this) {
-            if (mCurrentFontSettings == null) {
-                mCurrentFontSettings = SystemFontSettings.create(mUpdatableFontDir);
-            }
-            return mCurrentFontSettings;
+    @Nullable /* package */ SharedMemory getCurrentFontMap() {
+        synchronized (mSerializedFontMapLock) {
+            return mSerializedFontMap;
         }
     }
 
-    // TODO(b/173619554): Expose as API.
-    private boolean installFontFile(FileDescriptor fd, byte[] pkcs7Signature) {
-        if (mUpdatableFontDir == null) return false;
-        synchronized (FontManagerService.this) {
-            try {
-                mUpdatableFontDir.installFontFile(fd, pkcs7Signature);
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to install font file");
-                return false;
+    /* package */ void update(int baseVersion, List<FontUpdateRequest> requests)
+            throws SystemFontException {
+        if (mUpdatableFontDir == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
+                    "The font updater is disabled.");
+        }
+        synchronized (mUpdatableFontDirLock) {
+            // baseVersion == -1 only happens from shell command. This is filtered and treated as
+            // error from SystemApi call.
+            if (baseVersion != -1 && mUpdatableFontDir.getConfigVersion() != baseVersion) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_VERSION_MISMATCH,
+                        "The base config version is older than current.");
             }
-            // Create updated font map in the next getSerializedSystemFontMap() call.
-            mCurrentFontSettings = null;
-            return true;
+            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+                mUpdatableFontDir.update(requests);
+                updateSerializedFontMap();
+            }
+        }
+    }
+
+    /* package */ void clearUpdates() throws SystemFontException {
+        if (mUpdatableFontDir == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
+                    "The font updater is disabled.");
+        }
+        synchronized (mUpdatableFontDirLock) {
+            try (FontCrashDetector.MonitoredBlock ignored = mFontCrashDetector.start()) {
+                mUpdatableFontDir.clearUpdates();
+                updateSerializedFontMap();
+            }
+        }
+    }
+
+    /* package */ Map<String, File> getFontFileMap() {
+        if (mUpdatableFontDir == null) {
+            return Collections.emptyMap();
+        }
+        synchronized (mUpdatableFontDirLock) {
+            return mUpdatableFontDir.getFontFileMap();
         }
     }
 
@@ -202,76 +310,60 @@
     }
 
     @Override
-    public int handleShellCommand(@NonNull ParcelFileDescriptor in,
-            @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
-            @NonNull String[] args) {
-        return new FontManagerShellCommand(this).exec(this,
-                in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
+    public void onShellCommand(@Nullable FileDescriptor in,
+            @Nullable FileDescriptor out,
+            @Nullable FileDescriptor err,
+            @NonNull String[] args,
+            @Nullable ShellCallback callback,
+            @NonNull ResultReceiver result) {
+        new FontManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
     }
 
-    /* package */ static class SystemFontSettings {
-        private final @NonNull SharedMemory mSerializedSystemFontMap;
-        private final @NonNull FontConfig mSystemFontConfig;
-        private final @NonNull Map<String, FontFamily[]> mSystemFallbackMap;
-        private final @NonNull Map<String, Typeface> mSystemTypefaceMap;
-
-        SystemFontSettings(
-                @NonNull SharedMemory serializedSystemFontMap,
-                @NonNull FontConfig systemFontConfig,
-                @NonNull Map<String, FontFamily[]> systemFallbackMap,
-                @NonNull Map<String, Typeface> systemTypefaceMap) {
-            mSerializedSystemFontMap = serializedSystemFontMap;
-            mSystemFontConfig = systemFontConfig;
-            mSystemFallbackMap = systemFallbackMap;
-            mSystemTypefaceMap = systemTypefaceMap;
+    /**
+     * Returns an active system font configuration.
+     */
+    public @NonNull FontConfig getSystemFontConfig() {
+        if (mUpdatableFontDir == null) {
+            return SystemFonts.getSystemPreinstalledFontConfig();
         }
-
-        public @NonNull SharedMemory getSerializedSystemFontMap() {
-            return mSerializedSystemFontMap;
+        synchronized (mUpdatableFontDirLock) {
+            return mUpdatableFontDir.getSystemFontConfig();
         }
+    }
 
-        public @NonNull FontConfig getSystemFontConfig() {
-            return mSystemFontConfig;
-        }
+    /**
+     * Makes new serialized font map data and updates mSerializedFontMap.
+     */
+    public void updateSerializedFontMap() {
+        try {
+            final FontConfig fontConfig = getSystemFontConfig();
+            final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig);
+            final Map<String, Typeface> typefaceMap =
+                    SystemFonts.buildSystemTypefaces(fontConfig, fallback);
 
-        public @NonNull Map<String, FontFamily[]> getSystemFallbackMap() {
-            return mSystemFallbackMap;
-        }
-
-        public @NonNull Map<String, Typeface> getSystemTypefaceMap() {
-            return mSystemTypefaceMap;
-        }
-
-        public static @Nullable SystemFontSettings create(
-                @Nullable UpdatableFontDir updatableFontDir) {
-            if (updatableFontDir != null) {
-                final FontConfig fontConfig = SystemFonts.getSystemFontConfig(
-                        updatableFontDir.getFontFileMap());
-                final Map<String, FontFamily[]> fallback =
-                        SystemFonts.buildSystemFallback(fontConfig);
-                final Map<String, Typeface> typefaceMap =
-                        SystemFonts.buildSystemTypefaces(fontConfig, fallback);
-
-                try {
-                    final SharedMemory shm = Typeface.serializeFontMap(typefaceMap);
-                    return new SystemFontSettings(shm, fontConfig, fallback, typefaceMap);
-                } catch (IOException | ErrnoException e) {
-                    Slog.w(TAG, "Failed to serialize updatable font map. "
-                            + "Retrying with system image fonts.", e);
-                }
+            SharedMemory serializeFontMap = Typeface.serializeFontMap(typefaceMap);
+            synchronized (mSerializedFontMapLock) {
+                mSerializedFontMap = serializeFontMap;
             }
+            return;
+        } catch (IOException | ErrnoException e) {
+            Slog.w(TAG, "Failed to serialize updatable font map. "
+                    + "Retrying with system image fonts.", e);
+        }
 
+        try {
             final FontConfig fontConfig = SystemFonts.getSystemPreinstalledFontConfig();
             final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig);
             final Map<String, Typeface> typefaceMap =
                     SystemFonts.buildSystemTypefaces(fontConfig, fallback);
-            try {
-                final SharedMemory shm = Typeface.serializeFontMap(typefaceMap);
-                return new SystemFontSettings(shm, fontConfig, fallback, typefaceMap);
-            } catch (IOException | ErrnoException e) {
-                Slog.e(TAG, "Failed to serialize SystemServer system font map", e);
+
+            SharedMemory serializeFontMap = Typeface.serializeFontMap(typefaceMap);
+            synchronized (mSerializedFontMapLock) {
+                mSerializedFontMap = serializeFontMap;
             }
-            return null;
+        } catch (IOException | ErrnoException e) {
+            Slog.e(TAG, "Failed to serialize SystemServer system font map", e);
         }
     }
+
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index acb5826..cf9a79f 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -16,19 +16,36 @@
 
 package com.android.server.graphics.fonts;
 
+import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.fonts.Font;
 import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.FontManager;
+import android.graphics.fonts.FontUpdateRequest;
 import android.graphics.fonts.FontVariationAxis;
+import android.graphics.fonts.SystemFonts;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.ShellCommand;
 import android.text.FontConfig;
 import android.util.IndentingPrintWriter;
+import android.util.Slog;
 
 import com.android.internal.util.DumpUtils;
 
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -38,6 +55,13 @@
 public class FontManagerShellCommand extends ShellCommand {
     private static final String TAG = "FontManagerShellCommand";
 
+    /**
+     * The maximum size of signature file.  This is just to avoid potential abuse.
+     *
+     * This is copied from VerityUtils.java.
+     */
+    private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
+
     @NonNull private final FontManagerService mService;
 
     FontManagerShellCommand(@NonNull FontManagerService service) {
@@ -46,17 +70,36 @@
 
     @Override
     public int onCommand(String cmd) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
+            // Do not change this string since this string is expected in the CTS.
+            getErrPrintWriter().println("Only shell or root user can execute font command.");
+            return 1;
+        }
         return execCommand(this, cmd);
     }
 
     @Override
     public void onHelp() {
-        dumpHelp(getOutPrintWriter());
+        PrintWriter w = getOutPrintWriter();
+        w.println("Font service (font) commands");
+        w.println("help");
+        w.println("    Print this help text.");
+        w.println();
+        w.println("dump [family name]");
+        w.println("    Dump all font files in the specified family name.");
+        w.println("    Dump current system font configuration if no family name was specified.");
+        w.println();
+        w.println("update [font file path] [signature file path]");
+        w.println("    Update installed font files with new font file.");
+        w.println();
+        w.println("clear");
+        w.println("    Remove all installed font files and reset to the initial state.");
     }
 
     /* package */ void dumpAll(@NonNull IndentingPrintWriter w) {
-        final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings();
-        dumpFontConfig(w, settings.getSystemFontConfig());
+        FontConfig fontConfig = mService.getSystemFontConfig();
+        dumpFontConfig(w, fontConfig);
     }
 
     private void dumpSingleFontConfig(
@@ -128,8 +171,9 @@
             sb.append(c++);
             sb.append("]: lang=\"");
             sb.append(family.getLocaleList().toLanguageTags());
+            sb.append("\"");
             if (family.getVariant() != FontConfig.FontFamily.VARIANT_DEFAULT) {
-                sb.append("\", variant=");
+                sb.append(", variant=");
                 switch (family.getVariant()) {
                     case FontConfig.FontFamily.VARIANT_COMPACT:
                         sb.append("Compact");
@@ -165,16 +209,6 @@
         w.decreaseIndent();
     }
 
-    private static void dumpHelp(@NonNull PrintWriter w) {
-        w.println("Font service (font) commands");
-        w.println("help");
-        w.println("    Print this help text.");
-        w.println();
-        w.println("dump [family name]");
-        w.println("    Dump all font files in the specified family name.");
-        w.println("    Dump current system font configuration if no family name was specified.");
-    }
-
     private void dumpFallback(@NonNull IndentingPrintWriter writer,
             @NonNull FontFamily[] families) {
         for (FontFamily family : families) {
@@ -233,37 +267,138 @@
         writer.println(sb.toString());
     }
 
-    private int execCommand(@NonNull ShellCommand shell, @NonNull String cmd) {
+    private void writeCommandResult(ShellCommand shell, SystemFontException e) {
+        // Print short summary to the stderr.
+        PrintWriter pw = shell.getErrPrintWriter();
+        pw.println(e.getErrorCode());
+        pw.println(e.getMessage());
+
+        // Dump full stack trace to logcat.
+
+        Slog.e(TAG, "Command failed: " + Arrays.toString(shell.getAllArgs()), e);
+    }
+
+    private int dump(ShellCommand shell) {
         final Context ctx = mService.getContext();
+
+        if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) {
+            return 1;
+        }
+        final IndentingPrintWriter writer =
+                new IndentingPrintWriter(shell.getOutPrintWriter(), "  ");
+        String nextArg = shell.getNextArg();
+        FontConfig fontConfig = mService.getSystemFontConfig();
+        if (nextArg == null) {
+            dumpFontConfig(writer, fontConfig);
+        } else {
+            final Map<String, FontFamily[]> fallbackMap =
+                    SystemFonts.buildSystemFallback(fontConfig);
+            FontFamily[] families = fallbackMap.get(nextArg);
+            if (families == null) {
+                writer.println("Font Family \"" + nextArg + "\" not found");
+            } else {
+                dumpFallback(writer, families);
+            }
+        }
+        return 0;
+    }
+
+    private int update(ShellCommand shell) throws SystemFontException {
+        String fontPath = shell.getNextArg();
+        if (fontPath == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_SHELL_ARGUMENT,
+                    "Font file path argument is required.");
+        }
+        String signaturePath = shell.getNextArg();
+        if (signaturePath == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_SHELL_ARGUMENT,
+                    "Signature file argument is required.");
+        }
+
+        // TODO: close fontFd and sigFd.
+        ParcelFileDescriptor fontFd = shell.openFileForSystem(fontPath, "r");
+        if (fontFd == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FAILED_TO_OPEN_FONT_FILE,
+                    "Failed to open font file");
+        }
+
+        ParcelFileDescriptor sigFd = shell.openFileForSystem(signaturePath, "r");
+        if (sigFd == null) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FAILED_TO_OPEN_SIGNATURE_FILE,
+                    "Failed to open signature file");
+        }
+
+        try (FileInputStream sigFis = new FileInputStream(sigFd.getFileDescriptor())) {
+            int len = sigFis.available();
+            if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_SIGNATURE_TOO_LARGE,
+                        "Signature file is too large");
+            }
+            byte[] signature = new byte[len];
+            if (sigFis.read(signature, 0, len) != len) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE,
+                        "Invalid read length");
+            }
+            mService.update(
+                    -1, Collections.singletonList(new FontUpdateRequest(fontFd, signature)));
+        } catch (IOException e) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_SIGNATURE_FILE,
+                    "Failed to read signature file.", e);
+        }
+
+        shell.getOutPrintWriter().println("Success");  // TODO: Output more details.
+        return 0;
+    }
+
+    private int clear(ShellCommand shell) throws SystemFontException {
+        mService.clearUpdates();
+        shell.getOutPrintWriter().println("Success");
+        return 0;
+    }
+
+    private int status(ShellCommand shell) throws SystemFontException {
+        final IndentingPrintWriter writer =
+                new IndentingPrintWriter(shell.getOutPrintWriter(), "  ");
+        FontConfig config = mService.getSystemFontConfig();
+
+        writer.println("Current Version: " + config.getConfigVersion());
+        LocalDateTime dt = LocalDateTime.ofEpochSecond(config.getLastModifiedTimeMillis(), 0,
+                ZoneOffset.UTC);
+        writer.println("Last Modified Date: " + dt.format(DateTimeFormatter.ISO_DATE_TIME));
+
+        Map<String, File> fontFileMap = mService.getFontFileMap();
+        writer.println("Number of updated font files: " + fontFileMap.size());
+        return 0;
+    }
+
+    private int execCommand(@NonNull ShellCommand shell, @Nullable String cmd) {
         if (cmd == null) {
             return shell.handleDefaultCommands(null);
         }
 
-        final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings();
-
-        switch (cmd) {
-            case "dump":
-                if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) {
-                    return 1;
-                }
-                final IndentingPrintWriter writer =
-                        new IndentingPrintWriter(shell.getOutPrintWriter(), "  ");
-                String nextArg = shell.getNextArg();
-                if (nextArg == null) {
-                    dumpFontConfig(writer, settings.getSystemFontConfig());
-                } else {
-                    final Map<String, FontFamily[]> fallbackMap = settings.getSystemFallbackMap();
-                    FontFamily[] families = fallbackMap.get(nextArg);
-                    if (families == null) {
-                        writer.println("Font Family \"" + nextArg + "\" not found");
-                    } else {
-                        dumpFallback(writer, families);
-                    }
-                }
-                return 0;
-            default:
-                shell.handleDefaultCommands(cmd);
+        try {
+            switch (cmd) {
+                case "dump":
+                    return dump(shell);
+                case "update":
+                    return update(shell);
+                case "clear":
+                    return clear(shell);
+                case "status":
+                    return status(shell);
+                default:
+                    return shell.handleDefaultCommands(cmd);
+            }
+        } catch (SystemFontException e) {
+            writeCommandResult(shell, e);
+            return 1;
         }
-        return 0;
     }
 }
diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
new file mode 100644
index 0000000..017f11c
--- /dev/null
+++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 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.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Set;
+
+/* package */ class PersistentSystemFontConfig {
+    private static final String TAG = "PersistentSystemFontConfig";
+
+    private static final String TAG_ROOT = "fontConfig";
+    private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate";
+    private static final String TAG_UPDATED_FONT_DIR = "updatedFontDir";
+    private static final String ATTR_VALUE = "value";
+
+    /* package */ static class Config {
+        public long lastModifiedDate;
+        public final Set<String> updatedFontDirs = new ArraySet<>();
+    }
+
+    /**
+     * Read config XML and write to out argument.
+     */
+    public static void loadFromXml(@NonNull InputStream is, @NonNull Config out)
+            throws XmlPullParserException, IOException {
+        TypedXmlPullParser parser = Xml.resolvePullParser(is);
+
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+            final int depth = parser.getDepth();
+            final String tag = parser.getName();
+            if (depth == 1) {
+                if (!TAG_ROOT.equals(tag)) {
+                    Slog.e(TAG, "Invalid root tag: " + tag);
+                    return;
+                }
+            } else if (depth == 2) {
+                switch (tag) {
+                    case TAG_LAST_MODIFIED_DATE:
+                        out.lastModifiedDate = parseLongAttribute(parser, ATTR_VALUE, 0);
+                        break;
+                    case TAG_UPDATED_FONT_DIR:
+                        out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE));
+                        break;
+                    default:
+                        Slog.w(TAG, "Skipping unknown tag: " + tag);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Write config to OutputStream as XML file.
+     */
+    public static void writeToXml(@NonNull OutputStream os, @NonNull Config config)
+            throws IOException {
+        TypedXmlSerializer out = Xml.resolveSerializer(os);
+        out.startDocument(null /* encoding */, true /* standalone */);
+
+        out.startTag(null, TAG_ROOT);
+        out.startTag(null, TAG_LAST_MODIFIED_DATE);
+        out.attribute(null, ATTR_VALUE, Long.toString(config.lastModifiedDate));
+        out.endTag(null, TAG_LAST_MODIFIED_DATE);
+        for (String dir : config.updatedFontDirs) {
+            out.startTag(null, TAG_UPDATED_FONT_DIR);
+            out.attribute(null, ATTR_VALUE, dir);
+            out.endTag(null, TAG_UPDATED_FONT_DIR);
+        }
+        out.endTag(null, TAG_ROOT);
+
+        out.endDocument();
+    }
+
+    private static long parseLongAttribute(TypedXmlPullParser parser, String attr, long defValue) {
+        final String value = parser.getAttributeValue(null /* namespace */, attr);
+        if (TextUtils.isEmpty(value)) {
+            return defValue;
+        }
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            return defValue;
+        }
+    }
+
+    @NonNull
+    private static String getAttribute(TypedXmlPullParser parser, String attr) {
+        final String value = parser.getAttributeValue(null /* namespace */, attr);
+        return value == null ? "" : value;
+    }
+}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 8da579f..dac94f6 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -16,22 +16,38 @@
 
 package com.android.server.graphics.fonts;
 
+import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.fonts.FontManager;
+import android.graphics.fonts.FontUpdateRequest;
+import android.graphics.fonts.SystemFonts;
 import android.os.FileUtils;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.text.FontConfig;
+import android.util.ArrayMap;
 import android.util.Base64;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.security.SecureRandom;
-import java.util.HashMap;
+import java.time.Instant;
 import java.util.List;
 import java.util.Map;
 
+/**
+ * Manages set of updatable font files.
+ *
+ * <p>This class is not thread safe.
+ */
 final class UpdatableFontDir {
 
     private static final String TAG = "UpdatableFontDir";
@@ -39,6 +55,8 @@
     // TODO: Support .otf
     private static final String ALLOWED_EXTENSION = ".ttf";
 
+    private static final String CONFIG_XML_FILE = "/data/fonts/config/config.xml";
+
     /** Interface to mock font file access in tests. */
     interface FontFileParser {
         String getPostScriptName(File file) throws IOException;
@@ -87,49 +105,136 @@
     private final List<File> mPreinstalledFontDirs;
     private final FontFileParser mParser;
     private final FsverityUtil mFsverityUtil;
+    private final File mConfigFile;
+    private final File mTmpConfigFile;
+
+    private long mLastModifiedDate;
+    private int mConfigVersion = 1;
+
     /**
      * A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link
      * FontFileInfo}. All files in this map are validated, and have higher revision numbers than
      * corresponding font files in {@link #mPreinstalledFontDirs}.
      */
-    @GuardedBy("UpdatableFontDir.this")
-    private final Map<String, FontFileInfo> mFontFileInfoMap = new HashMap<>();
+    private final ArrayMap<String, FontFileInfo> mFontFileInfoMap = new ArrayMap<>();
 
     UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
             FsverityUtil fsverityUtil) {
+        this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE));
+    }
+
+    // For unit testing
+    UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
+            FsverityUtil fsverityUtil, File configFile) {
         mFilesDir = filesDir;
         mPreinstalledFontDirs = preinstalledFontDirs;
         mParser = parser;
         mFsverityUtil = fsverityUtil;
-        loadFontFileMap();
+        mConfigFile = configFile;
+        mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
     }
 
-    private void loadFontFileMap() {
-        // TODO: SIGBUS crash protection
-        synchronized (UpdatableFontDir.this) {
-            boolean success = false;
-            mFontFileInfoMap.clear();
-            try {
-                File[] dirs = mFilesDir.listFiles();
-                if (dirs == null) return;
-                for (File dir : dirs) {
-                    if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) return;
-                    File[] files = dir.listFiles();
-                    if (files == null || files.length != 1) return;
-                    FontFileInfo fontFileInfo = validateFontFile(files[0]);
-                    if (fontFileInfo == null) {
-                        Slog.w(TAG, "Broken file is found. Clearing files.");
-                        return;
-                    }
-                    addFileToMapLocked(fontFileInfo, true /* deleteOldFile */);
+    /* package */ void loadFontFileMap() {
+        mFontFileInfoMap.clear();
+        mLastModifiedDate = 0;
+        boolean success = false;
+        try {
+            PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+            try (FileInputStream fis = new FileInputStream(mConfigFile)) {
+                PersistentSystemFontConfig.loadFromXml(fis, config);
+            } catch (IOException | XmlPullParserException e) {
+                Slog.e(TAG, "Failed to load config xml file", e);
+                return;
+            }
+            mLastModifiedDate = config.lastModifiedDate;
+
+            File[] dirs = mFilesDir.listFiles();
+            if (dirs == null) return;
+            for (File dir : dirs) {
+                if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) {
+                    Slog.e(TAG, "Unexpected dir found: " + dir);
+                    return;
                 }
-                success = true;
-            } finally {
-                // Delete all files just in case if we find a problematic file.
-                if (!success) {
-                    mFontFileInfoMap.clear();
-                    FileUtils.deleteContents(mFilesDir);
+                if (!config.updatedFontDirs.contains(dir.getName())) {
+                    Slog.i(TAG, "Deleting obsolete dir: " + dir);
+                    FileUtils.deleteContentsAndDir(dir);
+                    continue;
                 }
+                File[] files = dir.listFiles();
+                if (files == null || files.length != 1) {
+                    Slog.e(TAG, "Unexpected files in dir: " + dir);
+                    return;
+                }
+                FontFileInfo fontFileInfo = validateFontFile(files[0]);
+                addFileToMapIfNewer(fontFileInfo, true /* deleteOldFile */);
+            }
+            success = true;
+        } catch (Throwable t) {
+            // If something happened during loading system fonts, clear all contents in finally
+            // block. Here, just dumping errors.
+            Slog.e(TAG, "Failed to load font mappings.", t);
+        } finally {
+            // Delete all files just in case if we find a problematic file.
+            if (!success) {
+                mFontFileInfoMap.clear();
+                mLastModifiedDate = 0;
+                FileUtils.deleteContents(mFilesDir);
+            }
+        }
+    }
+
+    /* package */ void clearUpdates() throws SystemFontException {
+        mFontFileInfoMap.clear();
+        FileUtils.deleteContents(mFilesDir);
+
+        mLastModifiedDate = Instant.now().getEpochSecond();
+        try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
+            PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+        } catch (Exception e) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
+                    "Failed to write config XML.", e);
+        }
+        mConfigVersion++;
+    }
+
+    /**
+     * Applies multiple {@link FontUpdateRequest}s in transaction.
+     * If one of the request fails, the fonts and config are rolled back to the previous state
+     * before this method is called.
+     */
+    public void update(List<FontUpdateRequest> requests) throws SystemFontException {
+        // Backup the mapping for rollback.
+        ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap);
+        long backupLastModifiedDate = mLastModifiedDate;
+        boolean success = false;
+        try {
+            for (FontUpdateRequest request : requests) {
+                installFontFile(request.getFd().getFileDescriptor(), request.getSignature());
+            }
+
+            // Write config file.
+            mLastModifiedDate = Instant.now().getEpochSecond();
+            try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
+                PersistentSystemFontConfig.writeToXml(fos, getPersistentConfig());
+            } catch (Exception e) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
+                        "Failed to write config XML.", e);
+            }
+
+            if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
+                        "Failed to stage the config file.");
+            }
+            mConfigVersion++;
+            success = true;
+        } finally {
+            if (!success) {
+                mFontFileInfoMap.clear();
+                mFontFileInfoMap.putAll(backupMap);
+                mLastModifiedDate = backupLastModifiedDate;
             }
         }
     }
@@ -143,43 +248,81 @@
      *
      * @param fd             A file descriptor to the font file.
      * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file.
+     * @throws SystemFontException if error occurs.
      */
-    void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws IOException {
-        synchronized (UpdatableFontDir.this) {
-            File newDir = getRandomDir(mFilesDir);
-            if (!newDir.mkdir()) {
-                // TODO: Define and return an error code for API
-                throw new IOException("Failed to create a new dir");
+    private void installFontFile(FileDescriptor fd, byte[] pkcs7Signature)
+            throws SystemFontException {
+        File newDir = getRandomDir(mFilesDir);
+        if (!newDir.mkdir()) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                    "Failed to create font directory.");
+        }
+        try {
+            // Make newDir executable so that apps can access font file inside newDir.
+            Os.chmod(newDir.getAbsolutePath(), 0711);
+        } catch (ErrnoException e) {
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                    "Failed to change mode to 711", e);
+        }
+        boolean success = false;
+        try {
+            File tempNewFontFile = new File(newDir, "font.ttf");
+            try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) {
+                FileUtils.copy(fd, out.getFD());
+            } catch (IOException e) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                        "Failed to write font file to storage.", e);
             }
-            boolean success = false;
             try {
-                File tempNewFontFile = new File(newDir, "font.ttf");
-                try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) {
-                    FileUtils.copy(fd, out.getFD());
-                }
                 // Do not parse font file before setting up fs-verity.
                 // setUpFsverity throws IOException if failed.
-                mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(), pkcs7Signature);
-                String postScriptName = mParser.getPostScriptName(tempNewFontFile);
-                File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION);
-                if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) {
-                    // TODO: Define and return an error code for API
-                    throw new IOException("Failed to rename");
-                }
-                FontFileInfo fontFileInfo = validateFontFile(newFontFile);
-                if (fontFileInfo == null) {
-                    // TODO: Define and return an error code for API
-                    throw new IllegalArgumentException("Invalid file");
-                }
-                if (!addFileToMapLocked(fontFileInfo, false)) {
-                    // TODO: Define and return an error code for API
-                    throw new IllegalArgumentException("Version downgrade");
-                }
-                success = true;
-            } finally {
-                if (!success) {
-                    FileUtils.deleteContentsAndDir(newDir);
-                }
+                mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
+                        pkcs7Signature);
+            } catch (IOException e) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
+                        "Failed to setup fs-verity.", e);
+            }
+            String postScriptName;
+            try {
+                postScriptName = mParser.getPostScriptName(tempNewFontFile);
+            } catch (IOException e) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_INVALID_FONT_FILE,
+                        "Failed to read PostScript name from font file", e);
+            }
+            if (postScriptName == null) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_INVALID_FONT_NAME,
+                        "Failed to read PostScript name from font file");
+            }
+            File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION);
+            if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                        "Failed to move verified font file.");
+            }
+            try {
+                // Make the font file readable by apps.
+                Os.chmod(newFontFile.getAbsolutePath(), 0644);
+            } catch (ErrnoException e) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
+                        "Failed to change mode to 711", e);
+            }
+            FontFileInfo fontFileInfo = validateFontFile(newFontFile);
+            if (!addFileToMapIfNewer(fontFileInfo, false)) {
+                throw new SystemFontException(
+                        FontManager.RESULT_ERROR_DOWNGRADING,
+                        "Downgrading font file is forbidden.");
+            }
+            success = true;
+        } finally {
+            if (!success) {
+                FileUtils.deleteContentsAndDir(newDir);
             }
         }
     }
@@ -207,7 +350,7 @@
      * higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link
      * #mPreinstalledFontDirs}).
      */
-    private boolean addFileToMapLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) {
+    private boolean addFileToMapIfNewer(FontFileInfo fontFileInfo, boolean deleteOldFile) {
         String name = fontFileInfo.getFile().getName();
         FontFileInfo existingInfo = mFontFileInfoMap.get(name);
         final boolean shouldAddToMap;
@@ -224,13 +367,12 @@
                 FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir());
             }
             mFontFileInfoMap.put(name, fontFileInfo);
-            return true;
         } else {
             if (deleteOldFile) {
                 FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir());
             }
-            return false;
         }
+        return shouldAddToMap;
     }
 
     private long getPreinstalledFontRevision(String name) {
@@ -255,20 +397,23 @@
      * returns a {@link FontFileInfo} on success. This method does not check if the font revision
      * is higher than the currently used font.
      */
-    @Nullable
-    private FontFileInfo validateFontFile(File file) {
+    @NonNull
+    private FontFileInfo validateFontFile(File file) throws SystemFontException {
         if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) {
-            Slog.w(TAG, "Font validation failed. Fs-verity is not enabled: " + file);
-            return null;
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
+                    "Font validation failed. Fs-verity is not enabled: " + file);
         }
         if (!validateFontFileName(file)) {
-            Slog.w(TAG, "Font validation failed. Could not validate font file name: " + file);
-            return null;
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_FONT_NAME,
+                    "Font validation failed. Could not validate font file name: " + file);
         }
         long revision = getFontRevision(file);
         if (revision == -1) {
-            Slog.w(TAG, "Font validation failed. Could not read font revision: " + file);
-            return null;
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_INVALID_FONT_FILE,
+                    "Font validation failed. Could not read font revision: " + file);
         }
         return new FontFileInfo(file, revision);
     }
@@ -309,13 +454,28 @@
         }
     }
 
+    private PersistentSystemFontConfig.Config getPersistentConfig() {
+        PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+        config.lastModifiedDate = mLastModifiedDate;
+        for (FontFileInfo info : mFontFileInfoMap.values()) {
+            config.updatedFontDirs.add(info.getRandomizedFontDir().getName());
+        }
+        return config;
+    }
+
     Map<String, File> getFontFileMap() {
-        Map<String, File> map = new HashMap<>();
-        synchronized (UpdatableFontDir.this) {
-            for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) {
-                map.put(entry.getKey(), entry.getValue().getFile());
-            }
+        Map<String, File> map = new ArrayMap<>();
+        for (Map.Entry<String, FontFileInfo> entry : mFontFileInfoMap.entrySet()) {
+            map.put(entry.getKey(), entry.getValue().getFile());
         }
         return map;
     }
+
+    /* package */ FontConfig getSystemFontConfig() {
+        return SystemFonts.getSystemFontConfig(getFontFileMap(), mLastModifiedDate, mConfigVersion);
+    }
+
+    /* package */ int getConfigVersion() {
+        return mConfigVersion;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index d66bf63..6918064 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -303,6 +303,8 @@
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY:
                 return STORAGE_GLOBAL_SETTINGS;
+            case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
+                return STORAGE_GLOBAL_SETTINGS;
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
                 return STORAGE_SHARED_PREFS;
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
@@ -338,6 +340,8 @@
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY:
                 return Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED;
+            case HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP:
+                return Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED;
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV:
                 return setting.getName();
             case HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU:
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index ccce9dc..382f0f9 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -956,8 +956,6 @@
         }
     }
 
-    void setAutoDeviceOff(boolean enabled) {}
-
     /**
      * Called when a hot-plug event issued.
      *
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index b909b16..bf5bf8b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -695,7 +695,6 @@
 
     @ServiceThreadOnly
     void setArcStatus(boolean enabled) {
-        // TODO(shubang): add tests
         assertRunOnServiceThread();
 
         HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index e6cf18b..75b52f9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -24,7 +24,6 @@
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemProperties;
-import android.provider.Settings.Global;
 import android.sysprop.HdmiProperties;
 import android.util.Slog;
 
@@ -56,9 +55,6 @@
     // Lazily initialized - should call getWakeLock() to get the instance.
     private ActiveWakeLock mWakeLock;
 
-    // If true, turn off TV upon standby. False by default.
-    private boolean mAutoTvOff;
-
     // Determines what action should be taken upon receiving Routing Control messages.
     @VisibleForTesting
     protected HdmiProperties.playback_device_action_on_routing_control_values
@@ -68,12 +64,6 @@
 
     HdmiCecLocalDevicePlayback(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
-
-        mAutoTvOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, false);
-
-        // The option is false by default. Update settings db as well to have the right
-        // initial setting on UI.
-        mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, mAutoTvOff);
     }
 
     @Override
@@ -154,7 +144,10 @@
         // Invalidate the internal active source record when goes to standby
         mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
                 "HdmiCecLocalDevicePlayback#onStandby()");
-        if (initiatedByCec || !mAutoTvOff || !wasActiveSource) {
+        boolean mTvSendStandbyOnSleep = mService.getHdmiCecConfig().getIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
+                    == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
+        if (initiatedByCec || !mTvSendStandbyOnSleep || !wasActiveSource) {
             return;
         }
         switch (standbyAction) {
@@ -201,13 +194,6 @@
     }
 
     @Override
-    @ServiceThreadOnly
-    void setAutoDeviceOff(boolean enabled) {
-        assertRunOnServiceThread();
-        mAutoTvOff = enabled;
-    }
-
-    @Override
     @CallSuper
     @ServiceThreadOnly
     @VisibleForTesting
@@ -425,7 +411,6 @@
     protected void dump(final IndentingPrintWriter pw) {
         super.dump(pw);
         pw.println("isActiveSource(): " + isActiveSource());
-        pw.println("mAutoTvOff:" + mAutoTvOff);
     }
 
     // Wrapper interface over PowerManager.WakeLock
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 5ef3738..a3e18d1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -89,9 +89,6 @@
     @GuardedBy("mLock")
     private boolean mSystemAudioMute = false;
 
-    // If true, TV going to standby mode puts other devices also to standby.
-    private boolean mAutoDeviceOff;
-
     private final HdmiCecStandbyModeHandler mStandbyHandler;
 
     // If true, do not do routing control/send active source for internal source.
@@ -156,8 +153,6 @@
     HdmiCecLocalDeviceTv(HdmiControlService service) {
         super(service, HdmiDeviceInfo.DEVICE_TV);
         mPrevPortId = Constants.INVALID_PORT_ID;
-        mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
-                true);
         mSystemAudioControlFeatureEnabled =
                 mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
@@ -1202,13 +1197,6 @@
         }
     }
 
-    @Override
-    @ServiceThreadOnly
-    void setAutoDeviceOff(boolean enabled) {
-        assertRunOnServiceThread();
-        mAutoDeviceOff = enabled;
-    }
-
     @ServiceThreadOnly
     boolean getAutoWakeup() {
         assertRunOnServiceThread();
@@ -1286,7 +1274,11 @@
         if (!mService.isControlEnabled()) {
             return;
         }
-        if (!initiatedByCec && mAutoDeviceOff) {
+        boolean sendStandbyOnSleep =
+                mService.getHdmiCecConfig().getIntValue(
+                    HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP)
+                        == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED;
+        if (!initiatedByCec && sendStandbyOnSleep) {
             mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(
                     mAddress, Constants.ADDR_BROADCAST));
         }
@@ -1545,7 +1537,6 @@
         pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled);
         pw.println("mSystemAudioMute: " + mSystemAudioMute);
         pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
-        pw.println("mAutoDeviceOff: " + mAutoDeviceOff);
         pw.println("mSkipRoutingControl: " + mSkipRoutingControl);
         pw.println("mPrevPortId: " + mPrevPortId);
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 8febd4f..0ae1994 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -661,7 +661,6 @@
         ContentResolver resolver = getContext().getContentResolver();
         String[] settings = new String[] {
                 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
-                Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
                 Global.MHL_INPUT_SWITCHING_ENABLED,
                 Global.MHL_POWER_CHARGE_ENABLED,
@@ -689,15 +688,6 @@
                     setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
                             HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
                     break;
-                case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
-                    for (int type : mLocalDevices) {
-                        HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type);
-                        if (localDevice != null) {
-                            localDevice.setAutoDeviceOff(enabled);
-                        }
-                    }
-                    // No need to propagate to HAL.
-                    break;
                 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
                     if (isTvDeviceEnabled()) {
                         tv().setSystemAudioControlFeatureEnabled(enabled);
diff --git a/services/core/java/com/android/server/hdmi/cec_config.xml b/services/core/java/com/android/server/hdmi/cec_config.xml
index a751fd7..191e725 100644
--- a/services/core/java/com/android/server/hdmi/cec_config.xml
+++ b/services/core/java/com/android/server/hdmi/cec_config.xml
@@ -64,6 +64,15 @@
     </allowed-values>
     <default-value int-value="1" />
   </setting>
+  <setting name="tv_send_standby_on_sleep"
+      value-type="int"
+      user-configurable="true">
+    <allowed-values>
+      <value int-value="0" />
+      <value int-value="1" />
+    </allowed-values>
+    <default-value int-value="1" />
+  </setting>
   <setting name="rc_profile_tv"
       value-type="int"
       user-configurable="false">
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 23c70ee..2e4200c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -289,6 +289,8 @@
     private static native void nativeCancelVibrate(long ptr, int deviceId, int token);
     private static native boolean nativeIsVibrating(long ptr, int deviceId);
     private static native int[] nativeGetVibratorIds(long ptr, int deviceId);
+    private static native int nativeGetBatteryCapacity(long ptr, int deviceId);
+    private static native int nativeGetBatteryStatus(long ptr, int deviceId);
     private static native void nativeReloadKeyboardLayouts(long ptr);
     private static native void nativeReloadDeviceAliases(long ptr);
     private static native String nativeDump(long ptr);
@@ -1818,8 +1820,7 @@
     }
 
     private void updateMaximumObscuringOpacityForTouchFromSettings() {
-        final float opacity = InputManager.getInstance().getMaximumObscuringOpacityForTouch(
-                mContext);
+        final float opacity = InputManager.getInstance().getMaximumObscuringOpacityForTouch();
         if (opacity < 0 || opacity > 1) {
             Log.e(TAG, "Invalid maximum obscuring opacity " + opacity
                     + ", it should be >= 0 and <= 1, rejecting update.");
@@ -2009,6 +2010,18 @@
 
     // Binder call
     @Override
+    public int getBatteryStatus(int deviceId) {
+        return nativeGetBatteryStatus(mPtr, deviceId);
+    }
+
+    // Binder call
+    @Override
+    public int getBatteryCapacity(int deviceId) {
+        return nativeGetBatteryCapacity(mPtr, deviceId);
+    }
+
+    // Binder call
+    @Override
     public void setPointerIconType(int iconId) {
         nativeSetPointerIconType(mPtr, iconId);
     }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 143ec15..6f7f69e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -110,7 +110,6 @@
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -300,45 +299,6 @@
     private static final String ACTION_SHOW_INPUT_METHOD_PICKER =
             "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER";
 
-    /**
-     * Debug flag for overriding runtime {@link SystemProperties}.
-     */
-    @AnyThread
-    private static final class DebugFlag {
-        private static final Object LOCK = new Object();
-        private final String mKey;
-        private final boolean mDefaultValue;
-        @GuardedBy("LOCK")
-        private boolean mValue;
-
-        public DebugFlag(String key, boolean defaultValue) {
-            mKey = key;
-            mDefaultValue = defaultValue;
-            mValue = SystemProperties.getBoolean(key, defaultValue);
-        }
-
-        void refresh() {
-            synchronized (LOCK) {
-                mValue = SystemProperties.getBoolean(mKey, mDefaultValue);
-            }
-        }
-
-        boolean value() {
-            synchronized (LOCK) {
-                return mValue;
-            }
-        }
-    }
-
-    /**
-     * Debug flags that can be overridden using "adb shell setprop <key>"
-     * Note: These flags are cached. To refresh, run "adb shell ime refresh_debug_properties".
-     */
-    private static final class DebugFlags {
-        static final DebugFlag FLAG_OPTIMIZE_START_INPUT =
-                new DebugFlag("debug.optimize_startinput", false);
-    }
-
     @UserIdInt
     private int mLastSwitchUserId;
 
@@ -2586,7 +2546,7 @@
                             + mCurTokenDisplayId);
                 }
                 mIWindowManager.addWindowToken(mCurToken, LayoutParams.TYPE_INPUT_METHOD,
-                        mCurTokenDisplayId);
+                        mCurTokenDisplayId, null /* options */);
             } catch (RemoteException e) {
             }
             return new InputBindResult(
@@ -3687,12 +3647,9 @@
                     }
                     res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
                             startInputFlags, startInputReason);
-                } else if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
-                        || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) {
+                } else {
                     res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
                             startInputFlags, startInputReason);
-                } else {
-                    res = InputBindResult.NO_EDITOR;
                 }
             } else {
                 res = InputBindResult.NULL_EDITOR_INFO;
@@ -3947,58 +3904,61 @@
     }
 
     @Override
-    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
-        // By this IPC call, only a process which shares the same uid with the IME can add
-        // additional input method subtypes to the IME.
-        if (TextUtils.isEmpty(imiId) || subtypes == null) return;
-        final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>();
-        for (InputMethodSubtype subtype : subtypes) {
-            if (!toBeAdded.contains(subtype)) {
-                toBeAdded.add(subtype);
-            } else {
-                Slog.w(TAG, "Duplicated subtype definition found: "
-                        + subtype.getLocale() + ", " + subtype.getMode());
+    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+            IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            // By this IPC call, only a process which shares the same uid with the IME can add
+            // additional input method subtypes to the IME.
+            if (TextUtils.isEmpty(imiId) || subtypes == null) return;
+            final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>();
+            for (InputMethodSubtype subtype : subtypes) {
+                if (!toBeAdded.contains(subtype)) {
+                    toBeAdded.add(subtype);
+                } else {
+                    Slog.w(TAG, "Duplicated subtype definition found: "
+                            + subtype.getLocale() + ", " + subtype.getMode());
+                }
             }
-        }
-        synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked()) {
-                return;
-            }
-            if (!mSystemReady) {
-                return;
-            }
-            final InputMethodInfo imi = mMethodMap.get(imiId);
-            if (imi == null) return;
-            final String[] packageInfos;
-            try {
-                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to get package infos");
-                return;
-            }
-            if (packageInfos != null) {
-                final int packageNum = packageInfos.length;
-                for (int i = 0; i < packageNum; ++i) {
-                    if (packageInfos[i].equals(imi.getPackageName())) {
-                        if (subtypes.length > 0) {
-                            mAdditionalSubtypeMap.put(imi.getId(), toBeAdded);
-                        } else {
-                            mAdditionalSubtypeMap.remove(imi.getId());
+            synchronized (mMethodMap) {
+                if (!calledFromValidUserLocked()) {
+                    return;
+                }
+                if (!mSystemReady) {
+                    return;
+                }
+                final InputMethodInfo imi = mMethodMap.get(imiId);
+                if (imi == null) return;
+                final String[] packageInfos;
+                try {
+                    packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to get package infos");
+                    return;
+                }
+                if (packageInfos != null) {
+                    final int packageNum = packageInfos.length;
+                    for (int i = 0; i < packageNum; ++i) {
+                        if (packageInfos[i].equals(imi.getPackageName())) {
+                            if (subtypes.length > 0) {
+                                mAdditionalSubtypeMap.put(imi.getId(), toBeAdded);
+                            } else {
+                                mAdditionalSubtypeMap.remove(imi.getId());
+                            }
+                            AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap,
+                                    mSettings.getCurrentUserId());
+                            final long ident = Binder.clearCallingIdentity();
+                            try {
+                                buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
+                            } finally {
+                                Binder.restoreCallingIdentity(ident);
+                            }
+                            return;
                         }
-                        AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap,
-                                mSettings.getCurrentUserId());
-                        final long ident = Binder.clearCallingIdentity();
-                        try {
-                            buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
-                        } finally {
-                            Binder.restoreCallingIdentity(ident);
-                        }
-                        return;
                     }
                 }
             }
-        }
-        return;
+            return;
+        });
     }
 
     /**
@@ -4103,16 +4063,21 @@
     }
 
     @Override
-    public void removeImeSurface() {
-        mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null);
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+    public void removeImeSurface(IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null);
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE));
+        });
     }
 
     @Override
-    public void removeImeSurfaceFromWindow(IBinder windowToken) {
-        // No permission check, because we'll only execute the request if the calling window is
-        // also the current IME client.
-        mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
+    public void removeImeSurfaceFromWindow(IBinder windowToken,
+            IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            // No permission check, because we'll only execute the request if the calling window is
+            // also the current IME client.
+            mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
+        });
     }
 
     /**
@@ -4123,48 +4088,52 @@
     @BinderThread
     @Override
     @GuardedBy("mMethodMap")
-    public void startProtoDump(byte[] protoDump, int source, String where) {
-        if (protoDump == null && source != IME_TRACING_FROM_IMMS) {
-            // Dump not triggered from IMMS, but no proto information provided.
-            return;
-        }
-        ImeTracing tracingInstance = ImeTracing.getInstance();
-        if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
-            return;
-        }
-
-        ProtoOutputStream proto = new ProtoOutputStream();
-        switch (source) {
-            case ImeTracing.IME_TRACING_FROM_CLIENT:
-                final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
-                proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
-                        SystemClock.elapsedRealtimeNanos());
-                proto.write(InputMethodClientsTraceProto.WHERE, where);
-                proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
-                proto.end(client_token);
-                break;
-            case ImeTracing.IME_TRACING_FROM_IMS:
-                final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
-                proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
-                        SystemClock.elapsedRealtimeNanos());
-                proto.write(InputMethodServiceTraceProto.WHERE, where);
-                proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
-                proto.end(service_token);
-                break;
-            case IME_TRACING_FROM_IMMS:
-                final long managerservice_token =
-                        proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
-                proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
-                        SystemClock.elapsedRealtimeNanos());
-                proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
-                dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
-                proto.end(managerservice_token);
-                break;
-            default:
-                // Dump triggered by a source not recognised.
+    public void startProtoDump(byte[] protoDump, int source, String where,
+            IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            if (protoDump == null && source != IME_TRACING_FROM_IMMS) {
+                // Dump not triggered from IMMS, but no proto information provided.
                 return;
-        }
-        tracingInstance.addToBuffer(proto, source);
+            }
+            ImeTracing tracingInstance = ImeTracing.getInstance();
+            if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) {
+                return;
+            }
+
+            ProtoOutputStream proto = new ProtoOutputStream();
+            switch (source) {
+                case ImeTracing.IME_TRACING_FROM_CLIENT:
+                    final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY);
+                    proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS,
+                            SystemClock.elapsedRealtimeNanos());
+                    proto.write(InputMethodClientsTraceProto.WHERE, where);
+                    proto.write(InputMethodClientsTraceProto.CLIENT, protoDump);
+                    proto.end(client_token);
+                    break;
+                case ImeTracing.IME_TRACING_FROM_IMS:
+                    final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY);
+                    proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS,
+                            SystemClock.elapsedRealtimeNanos());
+                    proto.write(InputMethodServiceTraceProto.WHERE, where);
+                    proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump);
+                    proto.end(service_token);
+                    break;
+                case IME_TRACING_FROM_IMMS:
+                    final long managerservice_token =
+                            proto.start(InputMethodManagerServiceTraceFileProto.ENTRY);
+                    proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS,
+                            SystemClock.elapsedRealtimeNanos());
+                    proto.write(InputMethodManagerServiceTraceProto.WHERE, where);
+                    dumpDebug(proto,
+                            InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE);
+                    proto.end(managerservice_token);
+                    break;
+                default:
+                    // Dump triggered by a source not recognised.
+                    return;
+            }
+            tracingInstance.addToBuffer(proto, source);
+        });
     }
 
     @BinderThread
@@ -4175,40 +4144,44 @@
 
     @BinderThread
     @Override
-    public void startImeTrace() {
-        ImeTracing.getInstance().startTrace(null /* printwriter */);
-        ArrayMap<IBinder, ClientState> clients;
-        synchronized (mMethodMap) {
-            clients = new ArrayMap<>(mClients);
-        }
-        for (ClientState state : clients.values()) {
-            if (state != null) {
-                try {
-                    state.client.setImeTraceEnabled(true /* enabled */);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error while trying to enable ime trace on client window", e);
+    public void startImeTrace(IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            ImeTracing.getInstance().startTrace(null /* printwriter */);
+            ArrayMap<IBinder, ClientState> clients;
+            synchronized (mMethodMap) {
+                clients = new ArrayMap<>(mClients);
+            }
+            for (ClientState state : clients.values()) {
+                if (state != null) {
+                    try {
+                        state.client.setImeTraceEnabled(true /* enabled */);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error while trying to enable ime trace on client window", e);
+                    }
                 }
             }
-        }
+        });
     }
 
     @BinderThread
     @Override
-    public void stopImeTrace() {
-        ImeTracing.getInstance().stopTrace(null /* printwriter */);
-        ArrayMap<IBinder, ClientState> clients;
-        synchronized (mMethodMap) {
-            clients = new ArrayMap<>(mClients);
-        }
-        for (ClientState state : clients.values()) {
-            if (state != null) {
-                try {
-                    state.client.setImeTraceEnabled(false /* enabled */);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Error while trying to disable ime trace on client window", e);
+    public void stopImeTrace(IVoidResultCallback resultCallback) {
+        CallbackUtils.onResult(resultCallback, () -> {
+            ImeTracing.getInstance().stopTrace(null /* printwriter */);
+            ArrayMap<IBinder, ClientState> clients;
+            synchronized (mMethodMap) {
+                clients = new ArrayMap<>(mClients);
+            }
+            for (ClientState state : clients.values()) {
+                if (state != null) {
+                    try {
+                        state.client.setImeTraceEnabled(false /* enabled */);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error while trying to disable ime trace on client window", e);
+                    }
                 }
             }
-        }
+        });
     }
 
     @GuardedBy("mMethodMap")
@@ -5451,10 +5424,6 @@
         @BinderThread
         @ShellCommandResult
         private int onCommandWithSystemIdentity(@Nullable String cmd) {
-            if ("refresh_debug_properties".equals(cmd)) {
-                return refreshDebugProperties();
-            }
-
             if ("get-last-switch-user-id".equals(cmd)) {
                 return mService.getLastSwitchUserId(this);
             }
@@ -5489,13 +5458,6 @@
         }
 
         @BinderThread
-        @ShellCommandResult
-        private int refreshDebugProperties() {
-            DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
-            return ShellCommandResult.SUCCESS;
-        }
-
-        @BinderThread
         @Override
         public void onHelp() {
             try (PrintWriter pw = getOutPrintWriter()) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 02a36dc..f646d5d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -220,7 +220,8 @@
      */
     @VisibleForTesting
     public Context getSettingsContext(int displayId) {
-        if (mSettingsContext == null) {
+        // TODO(b/178462039): Cover the case when IME is moved to another ImeContainer.
+        if (mSettingsContext == null || mSettingsContext.getDisplayId() != displayId) {
             final Context systemUiContext = ActivityThread.currentActivityThread()
                     .createSystemUiContext(displayId);
             final Context windowContext = systemUiContext.createWindowContext(
@@ -229,11 +230,6 @@
                     windowContext, com.android.internal.R.style.Theme_DeviceDefault_Settings);
             mSwitchingDialogToken = mSettingsContext.getWindowContextToken();
         }
-        // TODO(b/159767464): register the listener to another display again if window token is not
-        // yet created.
-        if (mSettingsContext.getDisplayId() != displayId) {
-            mWindowManagerInternal.moveWindowTokenToDisplay(mSwitchingDialogToken, displayId);
-        }
         return mSettingsContext;
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 2dd7096..1dd3d41 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1309,7 +1309,8 @@
                 final Binder token = new Binder();
                 Binder.withCleanCallingIdentity(
                         PooledLambda.obtainRunnable(WindowManagerInternal::addWindowToken,
-                                mIWindowManagerInternal, token, TYPE_INPUT_METHOD, displayId));
+                                mIWindowManagerInternal, token, TYPE_INPUT_METHOD, displayId,
+                                null /* options */));
                 mPerUserData.mDisplayIdToImeWindowTokenMap.add(new TokenInfo(token, displayId));
                 return token;
             }
@@ -1502,14 +1503,17 @@
 
         @BinderThread
         @Override
-        public void removeImeSurface() {
+        public void removeImeSurface(IVoidResultCallback resultCallback) {
             reportNotSupported();
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
         @Override
-        public void removeImeSurfaceFromWindow(IBinder windowToken) {
+        public void removeImeSurfaceFromWindow(IBinder windowToken,
+                IVoidResultCallback resultCallback) {
             reportNotSupported();
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
@@ -1815,8 +1819,10 @@
 
         @BinderThread
         @Override
-        public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+        public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+                IVoidResultCallback resultCallback) {
             reportNotSupported();
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
@@ -1863,7 +1869,9 @@
 
         @BinderThread
         @Override
-        public void startProtoDump(byte[] clientProtoDump, int source, String where) {
+        public void startProtoDump(byte[] clientProtoDump, int source, String where,
+                IVoidResultCallback resultCallback) {
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
@@ -1874,12 +1882,14 @@
 
         @BinderThread
         @Override
-        public void startImeTrace() {
+        public void startImeTrace(IVoidResultCallback resultCallback) {
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
 
         @BinderThread
         @Override
-        public void stopImeTrace() {
+        public void stopImeTrace(IVoidResultCallback resultCallback) {
+            CallbackUtils.onResult(resultCallback, () -> { });
         }
     }
 }
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index b5b93d6..142f64f 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -74,7 +74,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
@@ -1297,10 +1296,7 @@
                 return null;
             }
 
-            long currentNanos = SystemClock.elapsedRealtimeNanos();
-            long deltaMs = NANOSECONDS.toMillis(
-                    location.getElapsedRealtimeAgeNanos(currentNanos));
-            return new LocationTime(location.getTime() + deltaMs, currentNanos);
+            return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos());
         }
     }
 
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index dc1a26a..785e674 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.location.contexthub;
 
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -320,6 +321,10 @@
             @Override
             public void onNanoAppDisabled(long nanoAppId) {
             }
+
+            @Override
+            public void onClientAuthorizationChanged(long nanoAppId, int authorization) {
+            }
         };
     }
 
@@ -697,6 +702,7 @@
      *
      * @param contextHubId   the ID of the hub this client is attached to
      * @param clientCallback the client interface to register with the service
+     * @param attributionTag an optional attribution tag within the given package
      * @return the generated client interface, null if registration was unsuccessful
      * @throws IllegalArgumentException if contextHubId is not a valid ID
      * @throws IllegalStateException    if max number of clients have already registered
@@ -704,7 +710,8 @@
      */
     @Override
     public IContextHubClient createClient(
-            int contextHubId, IContextHubClientCallback clientCallback) throws RemoteException {
+            int contextHubId, IContextHubClientCallback clientCallback,
+            @Nullable String attributionTag) throws RemoteException {
         checkPermissions();
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
@@ -723,13 +730,15 @@
      * @param contextHubId  the ID of the hub this client is attached to
      * @param pendingIntent the PendingIntent associated with this client
      * @param nanoAppId     the ID of the nanoapp PendingIntent events will be sent for
+     * @param attributionTag an optional attribution tag within the given package
      * @return the generated client interface
      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
      * @throws IllegalStateException    if there were too many registered clients at the service
      */
     @Override
     public IContextHubClient createPendingIntentClient(
-            int contextHubId, PendingIntent pendingIntent, long nanoAppId) throws RemoteException {
+            int contextHubId, PendingIntent pendingIntent, long nanoAppId,
+            @Nullable String attributionTag) throws RemoteException {
         checkPermissions();
         if (!isValidContextHubId(contextHubId)) {
             throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
diff --git a/services/core/java/com/android/server/location/contexthub/OWNERS b/services/core/java/com/android/server/location/contexthub/OWNERS
index d4393d6..90c2330 100644
--- a/services/core/java/com/android/server/location/contexthub/OWNERS
+++ b/services/core/java/com/android/server/location/contexthub/OWNERS
@@ -1,2 +1,3 @@
 arthuri@google.com
 bduddie@google.com
+stange@google.com
diff --git a/services/core/java/com/android/server/utils/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
similarity index 98%
rename from services/core/java/com/android/server/utils/eventlog/LocalEventLog.java
rename to services/core/java/com/android/server/location/eventlog/LocalEventLog.java
index fe51d74..b5746bb 100644
--- a/services/core/java/com/android/server/utils/eventlog/LocalEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.utils.eventlog;
+package com.android.server.location.eventlog;
 
 import android.os.SystemClock;
 import android.util.TimeUtils;
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index d16267f..9216a6b 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -103,7 +103,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
@@ -1489,7 +1488,7 @@
         }
 
         if (locations.length > 0) {
-            reportLocation(LocationResult.create(Arrays.asList(locations)).validate());
+            reportLocation(LocationResult.wrap(locations).validate());
         }
 
         for (Runnable listener : listeners) {
diff --git a/services/core/java/com/android/server/location/injector/LocationEventLog.java b/services/core/java/com/android/server/location/injector/LocationEventLog.java
index b8b54b3..8d73518 100644
--- a/services/core/java/com/android/server/location/injector/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/injector/LocationEventLog.java
@@ -31,7 +31,7 @@
 import android.os.Build;
 import android.os.PowerManager.LocationPowerSaveMode;
 
-import com.android.server.utils.eventlog.LocalEventLog;
+import com.android.server.location.eventlog.LocalEventLog;
 
 /** In memory event log for location events. */
 public class LocationEventLog extends LocalEventLog {
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 06ca9ec..221d4fb 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -20,8 +20,8 @@
 import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.KEY_FLUSH_COMPLETE;
+import static android.location.LocationManager.KEY_LOCATIONS;
 import static android.location.LocationManager.KEY_LOCATION_CHANGED;
-import static android.location.LocationManager.KEY_LOCATION_RESULT;
 import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.os.IPowerManager.LOCATION_MODE_NO_CHANGE;
@@ -187,7 +187,8 @@
         @Override
         public void deliverOnLocationChanged(LocationResult locationResult,
                 @Nullable Runnable onCompleteCallback) throws RemoteException {
-            mListener.onLocationChanged(locationResult, SingleUseCallback.wrap(onCompleteCallback));
+            mListener.onLocationChanged(locationResult.asList(),
+                    SingleUseCallback.wrap(onCompleteCallback));
         }
 
         @Override
@@ -222,12 +223,16 @@
             // allows apps to start a fg service in response to a location PI
             options.setTemporaryAppWhitelistDuration(TEMPORARY_APP_ALLOWLIST_DURATION_MS);
 
+            Intent intent = new Intent().putExtra(KEY_LOCATION_CHANGED,
+                    locationResult.getLastLocation());
+            if (locationResult.size() > 1) {
+                intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
+            }
+
             mPendingIntent.send(
                     mContext,
                     0,
-                    new Intent()
-                            .putExtra(KEY_LOCATION_CHANGED, locationResult.getLastLocation())
-                            .putExtra(KEY_LOCATION_RESULT, locationResult),
+                    intent,
                     onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run()
                             : null,
                     null,
diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
index dce7b08..0d8f643 100644
--- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java
@@ -53,7 +53,7 @@
         Location location = new Location(l);
         location.setIsFromMockProvider(true);
         mLocation = location;
-        reportLocation(LocationResult.wrap(location));
+        reportLocation(LocationResult.wrap(location).validate());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index c274c28..32d637f 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.location.Location;
 import android.location.LocationResult;
 import android.location.provider.ILocationProvider;
 import android.location.provider.ILocationProviderManager;
@@ -40,6 +41,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -260,13 +262,25 @@
 
         // executed on binder thread
         @Override
-        public void onReportLocation(LocationResult locationResult) {
+        public void onReportLocation(Location location) {
             synchronized (mLock) {
                 if (mProxy != this) {
                     return;
                 }
 
-                reportLocation(locationResult.validate());
+                reportLocation(LocationResult.wrap(location).validate());
+            }
+        }
+
+        // executed on binder thread
+        @Override
+        public void onReportLocations(List<Location> locations) {
+            synchronized (mLock) {
+                if (mProxy != this) {
+                    return;
+                }
+
+                reportLocation(LocationResult.wrap(locations).validate());
             }
         }
 
diff --git a/services/core/java/com/android/server/location/timezone/OWNERS b/services/core/java/com/android/server/location/timezone/OWNERS
deleted file mode 100644
index 28aff18..0000000
--- a/services/core/java/com/android/server/location/timezone/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 847766
-nfuller@google.com
-include /core/java/android/app/timedetector/OWNERS
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 7dd961a..b92a83f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -89,6 +89,7 @@
     private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
 
     private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
+    private static final String REBOOT_ESCROW_SERVER_BLOB = "reboot.escrow.server.blob.key";
 
     private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
 
@@ -318,6 +319,22 @@
         deleteFile(getRebootEscrowFile(userId));
     }
 
+    public void writeRebootEscrowServerBlob(byte[] serverBlob) {
+        writeFile(getRebootEscrowServerBlob(), serverBlob);
+    }
+
+    public byte[] readRebootEscrowServerBlob() {
+        return readFile(getRebootEscrowServerBlob());
+    }
+
+    public boolean hasRebootEscrowServerBlob() {
+        return hasFile(getRebootEscrowServerBlob());
+    }
+
+    public void removeRebootEscrowServerBlob() {
+        deleteFile(getRebootEscrowServerBlob());
+    }
+
     public boolean hasPassword(int userId) {
         return hasFile(getLockPasswordFilename(userId));
     }
@@ -446,6 +463,12 @@
         return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE);
     }
 
+    @VisibleForTesting
+    String getRebootEscrowServerBlob() {
+        // There is a single copy of server blob for all users.
+        return getLockCredentialFilePathForUser(UserHandle.USER_SYSTEM, REBOOT_ESCROW_SERVER_BLOB);
+    }
+
     private String getLockCredentialFilePathForUser(int userId, String basename) {
         String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
                         SYSTEM_DIRECTORY;
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index fbec915..06962d4 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -124,26 +124,28 @@
     static class Injector {
         protected Context mContext;
         private final RebootEscrowKeyStoreManager mKeyStoreManager;
-        private final RebootEscrowProviderInterface mRebootEscrowProvider;
+        private final LockSettingsStorage mStorage;
+        private RebootEscrowProviderInterface mRebootEscrowProvider;
 
-        Injector(Context context) {
+        Injector(Context context, LockSettingsStorage storage) {
             mContext = context;
+            mStorage = storage;
             mKeyStoreManager = new RebootEscrowKeyStoreManager();
+        }
 
-            RebootEscrowProviderInterface rebootEscrowProvider = null;
-            // TODO(xunchang) add implementation for server based ror.
+        private RebootEscrowProviderInterface createRebootEscrowProvider() {
+            RebootEscrowProviderInterface rebootEscrowProvider;
             if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
                     "server_based_ror_enabled", false)) {
-                Slog.e(TAG, "Server based ror isn't implemented yet.");
+                rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
             } else {
                 rebootEscrowProvider = new RebootEscrowProviderHalImpl();
             }
 
-            if (rebootEscrowProvider != null && rebootEscrowProvider.hasRebootEscrowSupport()) {
-                mRebootEscrowProvider = rebootEscrowProvider;
-            } else {
-                mRebootEscrowProvider = null;
+            if (rebootEscrowProvider.hasRebootEscrowSupport()) {
+                return rebootEscrowProvider;
             }
+            return null;
         }
 
         public Context getContext() {
@@ -159,6 +161,12 @@
         }
 
         public RebootEscrowProviderInterface getRebootEscrowProvider() {
+            // Initialize for the provider lazily. Because the device_config and service
+            // implementation apps may change when system server is running.
+            if (mRebootEscrowProvider == null) {
+                mRebootEscrowProvider = createRebootEscrowProvider();
+            }
+
             return mRebootEscrowProvider;
         }
 
@@ -177,7 +185,7 @@
     }
 
     RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
-        this(new Injector(context), callbacks, storage);
+        this(new Injector(context, storage), callbacks, storage);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
new file mode 100644
index 0000000..ba1a680
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 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.locksettings;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.RemoteException;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;
+
+import java.io.IOException;
+import java.util.concurrent.TimeoutException;
+
+import javax.crypto.SecretKey;
+
+/**
+ * An implementation of the {@link RebootEscrowProviderInterface} by communicating with server to
+ * encrypt & decrypt the blob.
+ */
+class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
+    private static final String TAG = "RebootEscrowProvider";
+
+    // Timeout for service binding
+    private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;
+
+    /**
+     * Use the default lifetime of 10 minutes. The lifetime covers the following activities:
+     * Server wrap secret -> device reboot -> server unwrap blob.
+     */
+    private static final long DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS = 600_1000;
+
+    private final LockSettingsStorage mStorage;
+
+    private final Injector mInjector;
+
+    static class Injector {
+        private ResumeOnRebootServiceConnection mServiceConnection = null;
+
+        Injector(Context context) {
+            mServiceConnection = new ResumeOnRebootServiceProvider(context).getServiceConnection();
+            if (mServiceConnection == null) {
+                Slog.e(TAG, "Failed to resolve resume on reboot server service.");
+            }
+        }
+
+        Injector(ResumeOnRebootServiceConnection serviceConnection) {
+            mServiceConnection = serviceConnection;
+        }
+
+        @Nullable
+        private ResumeOnRebootServiceConnection getServiceConnection() {
+            return mServiceConnection;
+        }
+
+        long getServiceTimeoutInSeconds() {
+            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA,
+                    "server_based_service_timeout_in_seconds",
+                    DEFAULT_SERVICE_TIMEOUT_IN_SECONDS);
+        }
+
+        long getServerBlobLifetimeInMillis() {
+            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA,
+                    "server_based_server_blob_lifetime_in_millis",
+                    DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS);
+        }
+    }
+
+    RebootEscrowProviderServerBasedImpl(Context context, LockSettingsStorage storage) {
+        this(storage, new Injector(context));
+    }
+
+    @VisibleForTesting
+    RebootEscrowProviderServerBasedImpl(LockSettingsStorage storage, Injector injector) {
+        mStorage = storage;
+        mInjector = injector;
+    }
+
+    @Override
+    public boolean hasRebootEscrowSupport() {
+        return mInjector.getServiceConnection() != null;
+    }
+
+    private byte[] unwrapServerBlob(byte[] serverBlob, SecretKey decryptionKey) throws
+            TimeoutException, RemoteException, IOException {
+        ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection();
+        if (serviceConnection == null) {
+            Slog.w(TAG, "Had reboot escrow data for users, but resume on reboot server"
+                    + " service is unavailable");
+            return null;
+        }
+
+        // Decrypt with k_k from the key store first.
+        byte[] decryptedBlob = AesEncryptionUtil.decrypt(decryptionKey, serverBlob);
+        if (decryptedBlob == null) {
+            Slog.w(TAG, "Decrypted server blob should not be null");
+            return null;
+        }
+
+        // Ask the server connection service to decrypt the inner layer, to get the reboot
+        // escrow key (k_s).
+        serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds());
+        byte[] escrowKeyBytes = serviceConnection.unwrap(decryptedBlob,
+                mInjector.getServiceTimeoutInSeconds());
+        serviceConnection.unbindService();
+
+        return escrowKeyBytes;
+    }
+
+    @Override
+    public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) {
+        byte[] serverBlob = mStorage.readRebootEscrowServerBlob();
+        // Delete the server blob in storage.
+        mStorage.removeRebootEscrowServerBlob();
+        if (serverBlob == null) {
+            Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
+            return null;
+        }
+
+        try {
+            byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey);
+            if (escrowKeyBytes == null) {
+                Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
+                return null;
+            } else if (escrowKeyBytes.length != 32) {
+                Slog.e(TAG, "Decrypted reboot escrow key has incorrect size "
+                        + escrowKeyBytes.length);
+                return null;
+            }
+
+            return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
+        } catch (TimeoutException | RemoteException | IOException e) {
+            Slog.w(TAG, "Failed to decrypt the server blob ", e);
+            return null;
+        }
+    }
+
+    @Override
+    public void clearRebootEscrowKey() {
+        mStorage.removeRebootEscrowServerBlob();
+    }
+
+    private byte[] wrapEscrowKey(byte[] escrowKeyBytes, SecretKey encryptionKey) throws
+            TimeoutException, RemoteException, IOException {
+        ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection();
+        if (serviceConnection == null) {
+            Slog.w(TAG, "Failed to encrypt the reboot escrow key: resume on reboot server"
+                    + " service is unavailable");
+            return null;
+        }
+
+        serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds());
+        // Ask the server connection service to encrypt the reboot escrow key.
+        byte[] serverEncryptedBlob = serviceConnection.wrapBlob(escrowKeyBytes,
+                mInjector.getServerBlobLifetimeInMillis(), mInjector.getServiceTimeoutInSeconds());
+        serviceConnection.unbindService();
+
+        if (serverEncryptedBlob == null) {
+            Slog.w(TAG, "Server encrypted reboot escrow key cannot be null");
+            return null;
+        }
+
+        // Additionally wrap the server blob with a local key.
+        return AesEncryptionUtil.encrypt(encryptionKey, serverEncryptedBlob);
+    }
+
+    @Override
+    public boolean storeRebootEscrowKey(RebootEscrowKey escrowKey, SecretKey encryptionKey) {
+        mStorage.removeRebootEscrowServerBlob();
+        try {
+            byte[] wrappedBlob = wrapEscrowKey(escrowKey.getKeyBytes(), encryptionKey);
+            if (wrappedBlob == null) {
+                Slog.w(TAG, "Failed to encrypt the reboot escrow key");
+                return false;
+            }
+            mStorage.writeRebootEscrowServerBlob(wrappedBlob);
+
+            Slog.i(TAG, "Reboot escrow key encrypted and stored.");
+            return true;
+        } catch (TimeoutException | RemoteException | IOException e) {
+            Slog.w(TAG, "Failed to encrypt the reboot escrow key ", e);
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
index 8399f54..a1e18bd 100644
--- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
+++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java
@@ -106,6 +106,8 @@
         private final Context mContext;
         private final ComponentName mComponentName;
         private IResumeOnRebootService mBinder;
+        @Nullable
+        ServiceConnection mServiceConnection;
 
         private ResumeOnRebootServiceConnection(Context context,
                 @NonNull ComponentName componentName) {
@@ -115,17 +117,9 @@
 
         /** Unbind from the service */
         public void unbindService() {
-            mContext.unbindService(new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    mBinder = null;
-
-                }
-            });
+            if (mServiceConnection != null) {
+                mContext.unbindService(mServiceConnection);
+            }
         }
 
         /** Bind to the service */
@@ -134,17 +128,19 @@
                 CountDownLatch connectionLatch = new CountDownLatch(1);
                 Intent intent = new Intent();
                 intent.setComponent(mComponentName);
-                final boolean success = mContext.bindServiceAsUser(intent, new ServiceConnection() {
-                            @Override
-                            public void onServiceConnected(ComponentName name, IBinder service) {
-                                mBinder = IResumeOnRebootService.Stub.asInterface(service);
-                                connectionLatch.countDown();
-                            }
+                mServiceConnection = new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder service) {
+                        mBinder = IResumeOnRebootService.Stub.asInterface(service);
+                        connectionLatch.countDown();
+                    }
 
-                            @Override
-                            public void onServiceDisconnected(ComponentName name) {
-                            }
-                        },
+                    @Override
+                    public void onServiceDisconnected(ComponentName name) {
+                        mBinder = null;
+                    }
+                };
+                final boolean success = mContext.bindServiceAsUser(intent, mServiceConnection,
                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
                         BackgroundThread.getHandler(), UserHandle.SYSTEM);
 
diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
index 0a4d17f..e2e5046 100644
--- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
+++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java
@@ -141,6 +141,11 @@
                 packageName != null ? packageName : "");
     }
 
+    public static MediaButtonReceiverHolder create(int userId, ComponentName broadcastReceiver) {
+        return new MediaButtonReceiverHolder(userId, null, broadcastReceiver,
+                COMPONENT_TYPE_BROADCAST);
+    }
+
     private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent,
             ComponentName componentName, @ComponentType int componentType) {
         mUserId = userId;
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index ea6e7d7..ae58d4c 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
@@ -858,6 +859,21 @@
         }
 
         @Override
+        public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER)
+                        != 0) {
+                    return;
+                }
+                mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, receiver);
+                mService.onMediaButtonReceiverChanged(MediaSessionRecord.this);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void setLaunchPendingIntent(PendingIntent pi) throws RemoteException {
             mLaunchIntent = pi;
         }
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index ea2788c..6d1c680 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -155,7 +155,7 @@
                 try {
                     // Use the privileged method because Lockdown VPN is initiated by the system, so
                     // no additional permission checks are necessary.
-                    mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, egressProp);
+                    mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, null, egressProp);
                 } catch (IllegalStateException e) {
                     mAcceptedEgressIface = null;
                     Log.e(TAG, "Failed to start VPN", e);
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index f92f3dc..39ed7e8 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -16,8 +16,6 @@
 
 package com.android.server.net;
 
-import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal;
-
 import android.annotation.NonNull;
 import android.net.Network;
 import android.net.NetworkTemplate;
@@ -39,28 +37,6 @@
     public abstract void resetUserState(int userId);
 
     /**
-     * Figure out if networking is blocked for a given set of conditions.
-     *
-     * This is used by ConnectivityService via passing stale copies of conditions, so it must not
-     * take any locks.
-     *
-     * @param uid The target uid.
-     * @param uidRules The uid rules which are obtained from NetworkPolicyManagerService.
-     * @param isNetworkMetered True if the network is metered.
-     * @param isBackgroundRestricted True if data saver is enabled.
-     *
-     * @return true if networking is blocked for the UID under the specified conditions.
-     */
-    public static boolean isUidNetworkingBlocked(int uid, int uidRules, boolean isNetworkMetered,
-            boolean isBackgroundRestricted) {
-        // Log of invoking internal function is disabled because it will be called very
-        // frequently. And metrics are unlikely needed on this method because the callers are
-        // external and this method doesn't take any locks or perform expensive operations.
-        return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered,
-                isBackgroundRestricted, null);
-    }
-
-    /**
      * Informs that an appId has been added or removed from the temp-powersave-allowlist so that
      * that network rules for that appId can be updated.
      *
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 29eaf4f..b99a552 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -243,6 +243,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.StatLogger;
 import com.android.internal.util.XmlUtils;
+import com.android.net.module.util.NetworkIdentityUtils;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -1253,7 +1254,7 @@
                 // identified carrier, which may want to manage their own notifications. This method
                 // should be called every time the carrier config changes anyways, and there's no
                 // reason to alert if there isn't a carrier.
-                return;
+                continue;
             }
 
             final boolean notifyWarning = getBooleanDefeatingNullable(config,
@@ -2163,13 +2164,14 @@
             if (template.matches(probeIdent)) {
                 if (LOGD) {
                     Slog.d(TAG, "Found template " + template + " which matches subscriber "
-                            + NetworkIdentity.scrubSubscriberId(subscriberId));
+                            + NetworkIdentityUtils.scrubSubscriberId(subscriberId));
                 }
                 return false;
             }
         }
 
-        Slog.i(TAG, "No policy for subscriber " + NetworkIdentity.scrubSubscriberId(subscriberId)
+        Slog.i(TAG, "No policy for subscriber "
+                + NetworkIdentityUtils.scrubSubscriberId(subscriberId)
                 + "; generating default policy");
         final NetworkPolicy policy = buildDefaultMobilePolicy(subId, subscriberId);
         addNetworkPolicyAL(policy);
@@ -3614,14 +3616,15 @@
                     final int subId = mSubIdToSubscriberId.keyAt(i);
                     final String subscriberId = mSubIdToSubscriberId.valueAt(i);
 
-                    fout.println(subId + "=" + NetworkIdentity.scrubSubscriberId(subscriberId));
+                    fout.println(subId + "="
+                            + NetworkIdentityUtils.scrubSubscriberId(subscriberId));
                 }
                 fout.decreaseIndent();
 
                 fout.println();
                 for (String[] mergedSubscribers : mMergedSubscriberIds) {
                     fout.println("Merged subscriptions: " + Arrays.toString(
-                            NetworkIdentity.scrubSubscriberId(mergedSubscribers)));
+                            NetworkIdentityUtils.scrubSubscriberIds(mergedSubscribers)));
                 }
 
                 fout.println();
@@ -5399,6 +5402,17 @@
     }
 
     @Override
+    public boolean checkUidNetworkingBlocked(int uid, int uidRules,
+            boolean isNetworkMetered, boolean isBackgroundRestricted) {
+        mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
+        // Log of invoking this function is disabled because it will be called very frequently. And
+        // metrics are unlikely needed on this method because the callers are external and this
+        // method doesn't take any locks or perform expensive operations.
+        return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered,
+                isBackgroundRestricted, null);
+    }
+
+    @Override
     public boolean isUidRestrictedOnMeteredNetworks(int uid) {
         mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
         final int uidRules;
@@ -5407,9 +5421,9 @@
             uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
             isBackgroundRestricted = mRestrictBackground;
         }
-        //TODO(b/177490332): The logic here might not be correct because it doesn't consider
-        // RULE_REJECT_METERED condition. And it could be replaced by
-        // isUidNetworkingBlockedInternal().
+        // TODO(b/177490332): The logic here might not be correct because it doesn't consider
+        //  RULE_REJECT_METERED condition. And it could be replaced by
+        //  isUidNetworkingBlockedInternal().
         return isBackgroundRestricted
                 && !hasRule(uidRules, RULE_ALLOW_METERED)
                 && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED);
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index e9868fd..d042b88 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.net.INetd;
 import android.net.NetworkStats;
+import android.net.UnderlyingNetworkInfo;
 import android.net.util.NetdService;
 import android.os.RemoteException;
 import android.os.StrictMode;
@@ -34,7 +35,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ProcFileReader;
 
@@ -81,7 +81,7 @@
     private final Object mPersistentDataLock = new Object();
 
     /** Set containing info about active VPNs and their underlying networks. */
-    private volatile VpnInfo[] mVpnInfos = new VpnInfo[0];
+    private volatile UnderlyingNetworkInfo[] mUnderlyingNetworkInfos = new UnderlyingNetworkInfo[0];
 
     // A persistent snapshot of cumulative stats since device start
     @GuardedBy("mPersistentDataLock")
@@ -116,8 +116,8 @@
      *
      * @param vpnArray The snapshot of the currently-running VPNs.
      */
-    public void updateVpnInfos(VpnInfo[] vpnArray) {
-        mVpnInfos = vpnArray.clone();
+    public void updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray) {
+        mUnderlyingNetworkInfos = vpnArray.clone();
     }
 
     /**
@@ -319,7 +319,7 @@
         // code that will acquire other locks within the system server. See b/134244752.
         synchronized (mPersistentDataLock) {
             // Take a reference. If this gets swapped out, we still have the old reference.
-            final VpnInfo[] vpnArray = mVpnInfos;
+            final UnderlyingNetworkInfo[] vpnArray = mUnderlyingNetworkInfos;
             // Take a defensive copy. mPersistSnapshot is mutated in some cases below
             final NetworkStats prev = mPersistSnapshot.clone();
 
@@ -369,8 +369,8 @@
     }
 
     @GuardedBy("mPersistentDataLock")
-    private NetworkStats adjustForTunAnd464Xlat(
-            NetworkStats uidDetailStats, NetworkStats previousStats, VpnInfo[] vpnArray) {
+    private NetworkStats adjustForTunAnd464Xlat(NetworkStats uidDetailStats,
+            NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray) {
         // Calculate delta from last snapshot
         final NetworkStats delta = uidDetailStats.subtract(previousStats);
 
@@ -381,8 +381,9 @@
         delta.apply464xlatAdjustments(mStackedIfaces);
 
         // Migrate data usage over a VPN to the TUN network.
-        for (VpnInfo info : vpnArray) {
-            delta.migrateTun(info.ownerUid, info.vpnIface, info.underlyingIfaces);
+        for (UnderlyingNetworkInfo info : vpnArray) {
+            delta.migrateTun(info.ownerUid, info.iface,
+                    info.underlyingIfaces.toArray(new String[0]));
             // Filter out debug entries as that may lead to over counting.
             delta.filterDebugEntries();
         }
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 81a6641..0ab35a9 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -104,6 +104,7 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
+import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.netstats.provider.INetworkStatsProvider;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
@@ -143,7 +144,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FileRotator;
@@ -973,7 +973,7 @@
             Network[] defaultNetworks,
             NetworkState[] networkStates,
             String activeIface,
-            VpnInfo[] vpnInfos) {
+            UnderlyingNetworkInfo[] underlyingNetworkInfos) {
         checkNetworkStackPermission(mContext);
 
         final long token = Binder.clearCallingIdentity();
@@ -986,7 +986,7 @@
         // Update the VPN underlying interfaces only after the poll is made and tun data has been
         // migrated. Otherwise the migration would use the new interfaces instead of the ones that
         // were current when the polled data was transferred.
-        mStatsFactory.updateVpnInfos(vpnInfos);
+        mStatsFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e32c00f..571b693 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -377,7 +377,8 @@
 
     static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
             Adjustment.KEY_CONTEXTUAL_ACTIONS,
-            Adjustment.KEY_TEXT_REPLIES};
+            Adjustment.KEY_TEXT_REPLIES,
+            Adjustment.KEY_NOT_CONVERSATION};
 
     static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
             RoleManager.ROLE_DIALER,
@@ -2313,6 +2314,13 @@
                     } else if ("false".equals(value)) {
                         mAssistants.disallowAdjustmentType(Adjustment.KEY_RANKING_SCORE);
                     }
+                } else if (SystemUiDeviceConfigFlags.ENABLE_NAS_NOT_CONVERSATION.equals(name)) {
+                    String value = properties.getString(name, null);
+                    if ("true".equals(value)) {
+                        mAssistants.allowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
+                    } else if ("false".equals(value)) {
+                        mAssistants.disallowAdjustmentType(Adjustment.KEY_NOT_CONVERSATION);
+                    }
                 }
             }
         };
@@ -3033,7 +3041,8 @@
                         }
 
                         Binder windowToken = new Binder();
-                        mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId);
+                        mWindowManagerInternal.addWindowToken(windowToken, TYPE_TOAST, displayId,
+                                null /* options */);
                         record = getToastRecord(callingUid, callingPid, pkg, isSystemToast, token,
                                 text, callback, duration, windowToken, displayId, textCallback);
                         mToastQueue.add(record);
@@ -9302,21 +9311,30 @@
                 Slog.v(TAG, "onNotificationEnqueuedLocked() called with: r = [" + r + "]");
             }
             final StatusBarNotification sbn = r.getSbn();
-            notifyAssistantLocked(
-                    sbn,
-                    r.getNotificationType(),
-                    true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
-                        try {
-                            if (debug) {
-                                Slog.v(TAG,
-                                        "calling onNotificationEnqueuedWithChannel " + sbnHolder);
-                            }
-                            assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel());
-                        } catch (RemoteException ex) {
-                            Slog.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                boolean sbnVisible = isVisibleToListener(
+                        sbn, r.getNotificationType(), info)
+                        && info.isSameUser(r.getUserId());
+                if (sbnVisible) {
+                    TrimCache trimCache = new TrimCache(sbn);
+                    final INotificationListener assistant = (INotificationListener) info.service;
+                    final StatusBarNotification sbnToPost = trimCache.ForListener(info);
+                    final StatusBarNotificationHolder sbnHolder =
+                            new StatusBarNotificationHolder(sbnToPost);
+                    try {
+                        if (debug) {
+                            Slog.v(TAG,
+                                    "calling onNotificationEnqueuedWithChannel " + sbnHolder);
                         }
-                    });
+                        final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+                        assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel(),
+                                update);
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
+                    }
+                }
+            }
         }
 
         @GuardedBy("mNotificationLock")
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 61c8b17..619fc4e 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -23,8 +23,8 @@
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
 
-import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -1419,8 +1419,10 @@
                             conversation.setPkg(p.pkg);
                             conversation.setUid(p.uid);
                             conversation.setNotificationChannel(nc);
-                            conversation.setParentChannelLabel(
-                                    p.channels.get(nc.getParentChannelId()).getName());
+                            NotificationChannel parent = p.channels.get(nc.getParentChannelId());
+                            conversation.setParentChannelLabel(parent == null
+                                    ? null
+                                    : parent.getName());
                             boolean blockedByGroup = false;
                             if (nc.getGroup() != null) {
                                 NotificationChannelGroup group = p.groups.get(nc.getGroup());
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index f59934f..d7bc3bb 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -523,27 +523,39 @@
             if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
             long timeStartMs = System.currentTimeMillis();
             for (final String handle: mPendingLookups) {
+                final String cacheKey = getCacheKey(mContext.getUserId(), handle);
                 LookupResult lookupResult = null;
-                final Uri uri = Uri.parse(handle);
-                if ("tel".equals(uri.getScheme())) {
-                    if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
-                    lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
-                } else if ("mailto".equals(uri.getScheme())) {
-                    if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
-                    lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
-                } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
-                    if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
-                    lookupResult = searchContacts(mContext, uri);
-                } else {
-                    lookupResult = new LookupResult();  // invalid person for the cache
-                    if (!"name".equals(uri.getScheme())) {
-                        Slog.w(TAG, "unsupported URI " + handle);
+                boolean cacheHit = false;
+                synchronized (mPeopleCache) {
+                    lookupResult = mPeopleCache.get(cacheKey);
+                    if (lookupResult != null && !lookupResult.isExpired()) {
+                        // The name wasn't already added to the cache, no need to retry
+                        cacheHit = true;
+                    }
+                }
+                if (!cacheHit) {
+                    final Uri uri = Uri.parse(handle);
+                    if ("tel".equals(uri.getScheme())) {
+                        if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
+                        lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
+                    } else if ("mailto".equals(uri.getScheme())) {
+                        if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
+                        lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
+                    } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
+                        if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
+                        lookupResult = searchContacts(mContext, uri);
+                    } else {
+                        lookupResult = new LookupResult();  // invalid person for the cache
+                        if (!"name".equals(uri.getScheme())) {
+                            Slog.w(TAG, "unsupported URI " + handle);
+                        }
                     }
                 }
                 if (lookupResult != null) {
-                    synchronized (mPeopleCache) {
-                        final String cacheKey = getCacheKey(mContext.getUserId(), handle);
-                        mPeopleCache.put(cacheKey, lookupResult);
+                    if (!cacheHit) {
+                        synchronized (mPeopleCache) {
+                            mPeopleCache.put(cacheKey, lookupResult);
+                        }
                     }
                     if (DEBUG) {
                         Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
@@ -580,4 +592,3 @@
         }
     }
 }
-
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index de77372..18c689f 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,8 +21,8 @@
 import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
 import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
 import static android.service.notification.DNDModeProto.ROOT_CONFIG;
+import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
 
-import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 
 import android.app.AppOpsManager;
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
new file mode 100644
index 0000000..a83edb7
--- /dev/null
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 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.os;
+
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+
+import android.annotation.AppIdInt;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.FileObserver;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.BootReceiver;
+import com.android.server.ServiceThread;
+import com.android.server.os.TombstoneProtos.Tombstone;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Optional;
+
+/**
+ * A class to manage native tombstones.
+ */
+public final class NativeTombstoneManager {
+    private static final String TAG = NativeTombstoneManager.class.getSimpleName();
+
+    private static final File TOMBSTONE_DIR = new File("/data/tombstones");
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final TombstoneWatcher mWatcher;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final SparseArray<TombstoneFile> mTombstones;
+
+    NativeTombstoneManager(Context context) {
+        mTombstones = new SparseArray<TombstoneFile>();
+        mContext = context;
+
+        final ServiceThread thread = new ServiceThread(TAG + ":tombstoneWatcher",
+                THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
+        thread.start();
+        mHandler = thread.getThreadHandler();
+
+        mWatcher = new TombstoneWatcher();
+        mWatcher.startWatching();
+    }
+
+    void onSystemReady() {
+        // Scan existing tombstones.
+        mHandler.post(() -> {
+            final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
+            for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
+                if (tombstoneFiles[i].isFile()) {
+                    handleTombstone(tombstoneFiles[i]);
+                }
+            }
+        });
+    }
+
+    private void handleTombstone(File path) {
+        final String filename = path.getName();
+        if (!filename.startsWith("tombstone_")) {
+            return;
+        }
+
+        if (filename.endsWith(".pb")) {
+            handleProtoTombstone(path);
+        } else {
+            BootReceiver.addTombstoneToDropBox(mContext, path);
+        }
+    }
+
+    private void handleProtoTombstone(File path) {
+        final String filename = path.getName();
+        if (!filename.endsWith(".pb")) {
+            Slog.w(TAG, "unexpected tombstone name: " + path);
+            return;
+        }
+
+        final String suffix = filename.substring("tombstone_".length());
+        final String numberStr = suffix.substring(0, suffix.length() - 3);
+
+        int number;
+        try {
+            number = Integer.parseInt(numberStr);
+            if (number < 0 || number > 99) {
+                Slog.w(TAG, "unexpected tombstone name: " + path);
+                return;
+            }
+        } catch (NumberFormatException ex) {
+            Slog.w(TAG, "unexpected tombstone name: " + path);
+            return;
+        }
+
+        ParcelFileDescriptor pfd;
+        try {
+            pfd = ParcelFileDescriptor.open(path, MODE_READ_WRITE);
+        } catch (FileNotFoundException ex) {
+            Slog.w(TAG, "failed to open " + path, ex);
+            return;
+        }
+
+        final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
+        if (!parsedTombstone.isPresent()) {
+            IoUtils.closeQuietly(pfd);
+            return;
+        }
+
+        synchronized (mLock) {
+            TombstoneFile previous = mTombstones.get(number);
+            if (previous != null) {
+                previous.dispose();
+            }
+
+            mTombstones.put(number, parsedTombstone.get());
+        }
+    }
+
+    static class TombstoneFile {
+        final ParcelFileDescriptor mPfd;
+
+        final @UserIdInt int mUserId;
+        final @AppIdInt int mAppId;
+
+        boolean mPurged = false;
+
+        TombstoneFile(ParcelFileDescriptor pfd, @UserIdInt int userId, @AppIdInt int appId) {
+            mPfd = pfd;
+            mUserId = userId;
+            mAppId = appId;
+        }
+
+        public boolean matches(Optional<Integer> userId, Optional<Integer> appId) {
+            if (mPurged) {
+                return false;
+            }
+
+            if (userId.isPresent() && userId.get() != mUserId) {
+                return false;
+            }
+
+            if (appId.isPresent() && appId.get() != mAppId) {
+                return false;
+            }
+
+            return true;
+        }
+
+        public void dispose() {
+            IoUtils.closeQuietly(mPfd);
+        }
+
+        static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
+            final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
+            final ProtoInputStream stream = new ProtoInputStream(is);
+
+            int uid = 0;
+            String selinuxLabel = "";
+
+            try {
+                while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                    switch (stream.getFieldNumber()) {
+                        case (int) Tombstone.UID:
+                            uid = stream.readInt(Tombstone.UID);
+                            break;
+
+                        case (int) Tombstone.SELINUX_LABEL:
+                            selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
+                            break;
+
+                        default:
+                            break;
+                    }
+                }
+            } catch (IOException ex) {
+                Slog.e(TAG, "Failed to parse tombstone", ex);
+                return Optional.empty();
+            }
+
+            if (!UserHandle.isApp(uid)) {
+                Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
+                return Optional.empty();
+            }
+
+            final int userId = UserHandle.getUserId(uid);
+            final int appId = UserHandle.getAppId(uid);
+
+            if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
+                Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
+                return Optional.empty();
+            }
+
+            return Optional.of(new TombstoneFile(pfd, userId, appId));
+        }
+    }
+
+    class TombstoneWatcher extends FileObserver {
+        TombstoneWatcher() {
+            // Tombstones can be created either by linking an O_TMPFILE temporary file (CREATE),
+            // or by moving a named temporary file in the same directory on kernels where O_TMPFILE
+            // isn't supported (MOVED_TO).
+            super(TOMBSTONE_DIR, FileObserver.CREATE | FileObserver.MOVED_TO);
+        }
+
+        @Override
+        public void onEvent(int event, @Nullable String path) {
+            mHandler.post(() -> {
+                handleTombstone(new File(TOMBSTONE_DIR, path));
+            });
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManagerService.java b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java
new file mode 100644
index 0000000..cb3c7ff0
--- /dev/null
+++ b/services/core/java/com/android/server/os/NativeTombstoneManagerService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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.os;
+
+import android.content.Context;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+
+/**
+ * Service that tracks and manages native tombstones.
+ *
+ * @hide
+ */
+public class NativeTombstoneManagerService extends SystemService {
+    private static final String TAG = "NativeTombstoneManagerService";
+
+    private NativeTombstoneManager mManager;
+
+    public NativeTombstoneManagerService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        mManager = new NativeTombstoneManager(getContext());
+        LocalServices.addService(NativeTombstoneManager.class, mManager);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+            mManager.onSystemReady();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index a12932a8..de85d9e 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -30,9 +30,9 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.parsing.PackageInfoWithoutStateUtils;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.RemoteException;
@@ -508,7 +508,8 @@
 
             for (ApexInfo ai : allPkgs) {
                 File apexFile = new File(ai.modulePath);
-                parallelPackageParser.submit(apexFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
+                parallelPackageParser.submit(apexFile,
+                        ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
                 parsingApexInfo.put(apexFile, ai);
             }
 
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index ff3a12a..66ea554 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -23,8 +23,7 @@
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
-import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
-import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
+import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
@@ -32,14 +31,15 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
 import android.content.pm.ApkChecksum;
 import android.content.pm.Checksum;
+import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.Signature;
+import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.Handler;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.incremental.IncrementalManager;
 import android.os.incremental.IncrementalStorage;
@@ -171,7 +171,7 @@
      * @throws IllegalArgumentException if the code path is not an .apk.
      */
     public static String buildDigestsPathForApk(String codePath) {
-        if (!PackageParser.isApkPath(codePath)) {
+        if (!ApkLiteParseUtils.isApkPath(codePath)) {
             throw new IllegalStateException("Code path is not an apk " + codePath);
         }
         return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length())
@@ -293,21 +293,21 @@
     /**
      * Fetch or calculate checksums for the collection of files.
      *
-     * @param filesToChecksum       split name, null for base and File to fetch checksums for
-     * @param optional              mask to fetch readily available checksums
-     * @param required              mask to forcefully calculate if not available
-     * @param installerPackageName  package name of the installer of the packages
-     * @param trustedInstallers     array of certificate to trust, two specific cases:
-     *                              null - trust anybody,
-     *                              [] - trust nobody.
-     * @param statusReceiver        to receive the resulting checksums
+     * @param filesToChecksum          split name, null for base and File to fetch checksums for
+     * @param optional                 mask to fetch readily available checksums
+     * @param required                 mask to forcefully calculate if not available
+     * @param installerPackageName     package name of the installer of the packages
+     * @param trustedInstallers        array of certificate to trust, two specific cases:
+     *                                 null - trust anybody,
+     *                                 [] - trust nobody.
+     * @param onChecksumsReadyListener to receive the resulting checksums
      */
     public static void getChecksums(List<Pair<String, File>> filesToChecksum,
             @Checksum.Type int optional,
             @Checksum.Type int required,
             @Nullable String installerPackageName,
             @Nullable Certificate[] trustedInstallers,
-            @NonNull IntentSender statusReceiver,
+            @NonNull IOnChecksumsReadyListener onChecksumsReadyListener,
             @NonNull Injector injector) {
         List<Map<Integer, ApkChecksum>> result = new ArrayList<>(filesToChecksum.size());
         for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
@@ -325,14 +325,14 @@
         }
 
         long startTime = SystemClock.uptimeMillis();
-        processRequiredChecksums(filesToChecksum, result, required, statusReceiver, injector,
-                startTime);
+        processRequiredChecksums(filesToChecksum, result, required, onChecksumsReadyListener,
+                injector, startTime);
     }
 
     private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum,
             List<Map<Integer, ApkChecksum>> result,
             @Checksum.Type int required,
-            @NonNull IntentSender statusReceiver,
+            @NonNull IOnChecksumsReadyListener onChecksumsReadyListener,
             @NonNull Injector injector,
             long startTime) {
         final boolean timeout =
@@ -349,7 +349,7 @@
                         // Not ready, come back later.
                         injector.getHandler().postDelayed(() -> {
                             processRequiredChecksums(filesToChecksum, result, required,
-                                    statusReceiver, injector, startTime);
+                                    onChecksumsReadyListener, injector, startTime);
                         }, PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS);
                         return;
                     }
@@ -362,13 +362,9 @@
             }
         }
 
-        final Intent intent = new Intent();
-        intent.putExtra(EXTRA_CHECKSUMS,
-                allChecksums.toArray(new ApkChecksum[allChecksums.size()]));
-
         try {
-            statusReceiver.sendIntent(injector.getContext(), 1, intent, null, null);
-        } catch (IntentSender.SendIntentException e) {
+            onChecksumsReadyListener.onChecksumsReady(allChecksums);
+        } catch (RemoteException e) {
             Slog.w(TAG, e);
         }
     }
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index f8990c0..5d7c41c 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -588,29 +588,32 @@
      *
      * @param recipientUid the uid gaining visibility of the {@code visibleUid}.
      * @param visibleUid   the uid becoming visible to the {@recipientUid}
+     * @return {@code true} if implicit access was not already granted.
      */
-    public void grantImplicitAccess(int recipientUid, int visibleUid) {
-        if (recipientUid != visibleUid) {
-            final boolean changed = mImplicitlyQueryable.add(recipientUid, visibleUid);
-            if (changed && DEBUG_LOGGING) {
-                Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
-            }
-            synchronized (mCacheLock) {
-                if (mShouldFilterCache != null) {
-                    // update the cache in a one-off manner since we've got all the information we
-                    // need.
-                    SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid);
-                    if (visibleUids == null) {
-                        visibleUids = new SparseBooleanArray();
-                        mShouldFilterCache.put(recipientUid, visibleUids);
-                    }
-                    visibleUids.put(visibleUid, false);
+    public boolean grantImplicitAccess(int recipientUid, int visibleUid) {
+        if (recipientUid == visibleUid) {
+            return false;
+        }
+        final boolean changed = mImplicitlyQueryable.add(recipientUid, visibleUid);
+        if (changed && DEBUG_LOGGING) {
+            Slog.i(TAG, "implicit access granted: " + recipientUid + " -> " + visibleUid);
+        }
+        synchronized (mCacheLock) {
+            if (mShouldFilterCache != null) {
+                // update the cache in a one-off manner since we've got all the information we
+                // need.
+                SparseBooleanArray visibleUids = mShouldFilterCache.get(recipientUid);
+                if (visibleUids == null) {
+                    visibleUids = new SparseBooleanArray();
+                    mShouldFilterCache.put(recipientUid, visibleUids);
                 }
-            }
-            if (changed) {
-                onChanged();
+                visibleUids.put(visibleUid, false);
             }
         }
+        if (changed) {
+            onChanged();
+        }
+        return changed;
     }
 
     public void onSystemReady() {
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index e0b57e4..402f646 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -118,8 +118,8 @@
         // Schedule a one-off job which scans installed packages and updates
         // out-of-date oat files.
         js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
-                    .setMinimumLatency(TimeUnit.MINUTES.toMillis(1))
-                    .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1))
+                    .setMinimumLatency(TimeUnit.MINUTES.toMillis(10))
+                    .setOverrideDeadline(TimeUnit.MINUTES.toMillis(60))
                     .build());
 
         // Schedule a daily job which scans installed packages and compiles
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java
new file mode 100644
index 0000000..a32e107
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFilter.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 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.pm;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
+import android.content.IntentFilter;
+
+import com.android.internal.annotations.Immutable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Representation of an immutable default cross-profile intent filter.
+ */
+@Immutable
+final class DefaultCrossProfileIntentFilter {
+
+    @IntDef({
+            Direction.TO_PARENT,
+            Direction.TO_PROFILE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Direction {
+        int TO_PARENT = 0;
+        int TO_PROFILE = 1;
+    }
+
+    /** The intent filter that's used */
+    public final IntentFilter filter;
+
+    /**
+     * The flags related to the forwarding, e.g.
+     * {@link android.content.pm.PackageManager#SKIP_CURRENT_PROFILE} or
+     * {@link android.content.pm.PackageManager#ONLY_IF_NO_MATCH_FOUND}.
+     */
+    public final int flags;
+
+    /**
+     * The direction of forwarding, can be either {@link Direction#TO_PARENT} or
+     * {@link Direction#TO_PROFILE}.
+     */
+    public final @Direction int direction;
+
+    /**
+     * Whether this cross profile intent filter would allow personal data to be shared into
+     * the work profile. If this is {@code true}, this intent filter should be only added to
+     * the profile if the admin does not enable
+     * {@link android.os.UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}.
+     */
+    public final boolean letsPersonalDataIntoProfile;
+
+    private DefaultCrossProfileIntentFilter(IntentFilter filter, int flags,
+            @Direction int direction, boolean letsPersonalDataIntoProfile) {
+        this.filter = requireNonNull(filter);
+        this.flags = flags;
+        this.direction = direction;
+        this.letsPersonalDataIntoProfile = letsPersonalDataIntoProfile;
+    }
+
+    static final class Builder {
+        private IntentFilter mFilter = new IntentFilter();
+        private int mFlags;
+        private @Direction int mDirection;
+        private boolean mLetsPersonalDataIntoProfile;
+
+        Builder(@Direction int direction, int flags, boolean letsPersonalDataIntoProfile) {
+            mDirection = direction;
+            mFlags = flags;
+            mLetsPersonalDataIntoProfile = letsPersonalDataIntoProfile;
+        }
+
+        Builder addAction(String action) {
+            mFilter.addAction(action);
+            return this;
+        }
+
+        Builder addCategory(String category) {
+            mFilter.addCategory(category);
+            return this;
+        }
+
+        Builder addDataType(String type) {
+            try {
+                mFilter.addDataType(type);
+            } catch (IntentFilter.MalformedMimeTypeException e) {
+                // ignore
+            }
+            return this;
+        }
+
+        Builder addDataScheme(String scheme) {
+            mFilter.addDataScheme(scheme);
+            return this;
+        }
+
+        DefaultCrossProfileIntentFilter build() {
+            return new DefaultCrossProfileIntentFilter(mFilter, mFlags, mDirection,
+                    mLetsPersonalDataIntoProfile);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
new file mode 100644
index 0000000..3019439
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2021 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.pm;
+
+import static android.content.pm.PackageManager.ONLY_IF_NO_MATCH_FOUND;
+import static android.content.pm.PackageManager.SKIP_CURRENT_PROFILE;
+import static android.speech.RecognizerIntent.ACTION_RECOGNIZE_SPEECH;
+
+import android.content.Intent;
+import android.hardware.usb.UsbManager;
+import android.provider.AlarmClock;
+import android.provider.MediaStore;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utility Class for {@link DefaultCrossProfileIntentFilter}.
+ */
+public class DefaultCrossProfileIntentFiltersUtils {
+
+    private DefaultCrossProfileIntentFiltersUtils() {
+    }
+
+    // Intents from profile to parent user
+    /** Emergency call intent with mime type is always resolved by primary user. */
+    private static final DefaultCrossProfileIntentFilter
+            EMERGENCY_CALL_MIME =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    SKIP_CURRENT_PROFILE,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_CALL_EMERGENCY)
+                    .addAction(Intent.ACTION_CALL_PRIVILEGED)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .addDataType("vnd.android.cursor.item/phone")
+                    .addDataType("vnd.android.cursor.item/phone_v2")
+                    .addDataType("vnd.android.cursor.item/person")
+                    .addDataType("vnd.android.cursor.dir/calls")
+                    .addDataType("vnd.android.cursor.item/calls")
+                    .build();
+
+    /** Emergency call intent with data schemes is always resolved by primary user. */
+    private static final DefaultCrossProfileIntentFilter
+            EMERGENCY_CALL_DATA =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    SKIP_CURRENT_PROFILE,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_CALL_EMERGENCY)
+                    .addAction(Intent.ACTION_CALL_PRIVILEGED)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .addDataScheme("tel")
+                    .addDataScheme("sip")
+                    .addDataScheme("voicemail")
+                    .build();
+
+    /** Dial intent with mime type can be handled by either managed profile or its parent user. */
+    private static final DefaultCrossProfileIntentFilter DIAL_MIME =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    ONLY_IF_NO_MATCH_FOUND,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_DIAL)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .addDataType("vnd.android.cursor.item/phone")
+                    .addDataType("vnd.android.cursor.item/phone_v2")
+                    .addDataType("vnd.android.cursor.item/person")
+                    .addDataType("vnd.android.cursor.dir/calls")
+                    .addDataType("vnd.android.cursor.item/calls")
+                    .build();
+
+    /** Dial intent with data scheme can be handled by either managed profile or its parent user. */
+    private static final DefaultCrossProfileIntentFilter DIAL_DATA =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    ONLY_IF_NO_MATCH_FOUND,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_DIAL)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .addDataScheme("tel")
+                    .addDataScheme("sip")
+                    .addDataScheme("voicemail")
+                    .build();
+
+    /**
+     * Dial intent with no data scheme or type can be handled by either managed profile or its
+     * parent user.
+     */
+    private static final DefaultCrossProfileIntentFilter DIAL_RAW =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    ONLY_IF_NO_MATCH_FOUND,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_DIAL)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .build();
+
+    /** Pressing the call button can be handled by either managed profile or its parent user. */
+    private static final DefaultCrossProfileIntentFilter CALL_BUTTON =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    ONLY_IF_NO_MATCH_FOUND,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_CALL_BUTTON)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    /** SMS and MMS are exclusively handled by the primary user. */
+    private static final DefaultCrossProfileIntentFilter SMS_MMS =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    SKIP_CURRENT_PROFILE,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_VIEW)
+                    .addAction(Intent.ACTION_SENDTO)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_BROWSABLE)
+                    .addDataScheme("sms")
+                    .addDataScheme("smsto")
+                    .addDataScheme("mms")
+                    .addDataScheme("mmsto")
+                    .build();
+
+    /** Mobile network settings is always shown in the primary user. */
+    private static final DefaultCrossProfileIntentFilter
+            MOBILE_NETWORK_SETTINGS =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    SKIP_CURRENT_PROFILE,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(android.provider.Settings.ACTION_DATA_ROAMING_SETTINGS)
+                    .addAction(android.provider.Settings.ACTION_NETWORK_OPERATOR_SETTINGS)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    /** HOME intent is always resolved by the primary user. */
+    static final DefaultCrossProfileIntentFilter HOME =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    SKIP_CURRENT_PROFILE,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_HOME)
+                    .build();
+
+    /** Get content can be forwarded to parent user. */
+    private static final DefaultCrossProfileIntentFilter GET_CONTENT =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ true)
+                    .addAction(Intent.ACTION_GET_CONTENT)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_OPENABLE)
+                    .addDataType("*/*")
+                    .build();
+
+    /** Open document intent can be forwarded to parent user. */
+    private static final DefaultCrossProfileIntentFilter OPEN_DOCUMENT =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ true)
+                    .addAction(Intent.ACTION_OPEN_DOCUMENT)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addCategory(Intent.CATEGORY_OPENABLE)
+                    .addDataType("*/*")
+                    .build();
+
+    /** Pick for any data type can be forwarded to parent user. */
+    private static final DefaultCrossProfileIntentFilter ACTION_PICK_DATA =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ true)
+                    .addAction(Intent.ACTION_PICK)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addDataType("*/*")
+                    .build();
+
+    /** Pick without data type can be forwarded to parent user. */
+    private static final DefaultCrossProfileIntentFilter ACTION_PICK_RAW =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ true)
+                    .addAction(Intent.ACTION_PICK)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    /** Speech recognition can be performed by primary user. */
+    private static final DefaultCrossProfileIntentFilter RECOGNIZE_SPEECH =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(ACTION_RECOGNIZE_SPEECH)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    /** Media capture can be performed by primary user. */
+    private static final DefaultCrossProfileIntentFilter MEDIA_CAPTURE =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ true)
+                    .addAction(MediaStore.ACTION_IMAGE_CAPTURE)
+                    .addAction(MediaStore.ACTION_IMAGE_CAPTURE_SECURE)
+                    .addAction(MediaStore.ACTION_VIDEO_CAPTURE)
+                    .addAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION)
+                    .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)
+                    .addAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
+                    .addAction(MediaStore.INTENT_ACTION_VIDEO_CAMERA)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    /** Alarm setting can be performed by primary user. */
+    private static final DefaultCrossProfileIntentFilter SET_ALARM =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(AlarmClock.ACTION_SET_ALARM)
+                    .addAction(AlarmClock.ACTION_SHOW_ALARMS)
+                    .addAction(AlarmClock.ACTION_SET_TIMER)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    // Intents from parent to profile user
+
+    /** ACTION_SEND can be forwarded to the managed profile on user's choice. */
+    private static final DefaultCrossProfileIntentFilter ACTION_SEND =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ true)
+                    .addAction(Intent.ACTION_SEND)
+                    .addAction(Intent.ACTION_SEND_MULTIPLE)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .addDataType("*/*")
+                    .build();
+
+    /** USB devices attached can get forwarded to the profile. */
+    private static final DefaultCrossProfileIntentFilter
+            USB_DEVICE_ATTACHED =
+            new DefaultCrossProfileIntentFilter.Builder(
+                    DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+                    /* flags= */0,
+                    /* letsPersonalDataIntoProfile= */ false)
+                    .addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
+                    .addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)
+                    .addCategory(Intent.CATEGORY_DEFAULT)
+                    .build();
+
+    public static List<DefaultCrossProfileIntentFilter> getDefaultManagedProfileFilters() {
+        return Arrays.asList(
+                EMERGENCY_CALL_MIME,
+                EMERGENCY_CALL_DATA,
+                DIAL_MIME,
+                DIAL_DATA,
+                DIAL_RAW,
+                CALL_BUTTON,
+                SMS_MMS,
+                SET_ALARM,
+                MEDIA_CAPTURE,
+                RECOGNIZE_SPEECH,
+                ACTION_PICK_RAW,
+                ACTION_PICK_DATA,
+                OPEN_DOCUMENT,
+                GET_CONTENT,
+                USB_DEVICE_ATTACHED,
+                ACTION_SEND,
+                HOME,
+                MOBILE_NETWORK_SETTINGS);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 4f986bd..2a1fc87 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -33,7 +33,7 @@
     public static final int DUMP_KEYSETS = 1 << 14;
     public static final int DUMP_VERSION = 1 << 15;
     public static final int DUMP_INSTALLS = 1 << 16;
-    public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+    public static final int DUMP_DOMAIN_VERIFIER = 1 << 17;
     public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
     public static final int DUMP_FROZEN = 1 << 19;
     public static final int DUMP_DEXOPT = 1 << 20;
diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index da65fe2..c65c2b1 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -234,7 +234,7 @@
 
                 List<EventLog.Event> events = new ArrayList<>();
                 EventLog.readEvents(tags, events);
-
+                Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher("");
                 for (int i = 0; i < events.size(); ++i) {
                     if (mAuditWatchingStopRequested) {
                         Log.w(TAG, "Stopping AuditWatchingJob run at scheduler request");
@@ -259,7 +259,9 @@
 
                     // And then use a regular expression to verify it's one of the messages we're
                     // interested in and to extract the path of the file being loaded.
-                    Matcher matcher = EXECUTE_NATIVE_AUDIT_PATTERN.matcher(message);
+                    // Reuse the Matcher to avoid unnecessary string garbage caused by libcore's
+                    // regex matching.
+                    matcher.reset(message);
                     if (!matcher.matches()) {
                         continue;
                     }
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
index 780c522..ecafdfd 100644
--- a/services/core/java/com/android/server/pm/IncrementalStates.java
+++ b/services/core/java/com/android/server/pm/IncrementalStates.java
@@ -61,12 +61,12 @@
 
     public IncrementalStates() {
         // By default the package is not startable and not fully loaded (i.e., is loading)
-        this(false, true);
+        this(false, true, 0);
     }
 
-    public IncrementalStates(boolean isStartable, boolean isLoading) {
+    public IncrementalStates(boolean isStartable, boolean isLoading, float loadingProgress) {
         mStartableState = new StartableState(isStartable);
-        mLoadingState = new LoadingState(isLoading);
+        mLoadingState = new LoadingState(isLoading, loadingProgress);
         mStatusConsumer = new StatusConsumer();
     }
 
@@ -249,24 +249,6 @@
     }
 
     /**
-     * @return the current startable state.
-     */
-    public boolean isStartable() {
-        synchronized (mLock) {
-            return mStartableState.isStartable();
-        }
-    }
-
-    /**
-     * @return Whether the package is still being loaded or has been fully loaded.
-     */
-    public boolean isLoading() {
-        synchronized (mLock) {
-            return mLoadingState.isLoading();
-        }
-    }
-
-    /**
      * @return all current states in a Parcelable.
      */
     public IncrementalStatesInfo getIncrementalStatesInfo() {
@@ -405,9 +387,10 @@
         private boolean mIsLoading;
         private float mProgress;
 
-        LoadingState(boolean isLoading) {
+        LoadingState(boolean isLoading, float loadingProgress) {
             mIsLoading = isLoading;
-            mProgress = isLoading ? 0 : 1;
+            // loading progress is reset to 1 if loading has finished
+            mProgress = isLoading ? loadingProgress : 1;
         }
 
         public boolean isLoading() {
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index c3bca28..15e1d52 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -470,23 +470,34 @@
         return instantGrantList.get(instantAppId);
     }
 
+    /**
+     * Allows an app to see an instant app.
+     *
+     * @param userId the userId in which this access is being granted
+     * @param intent when provided, this serves as the intent that caused
+     *               this access to be granted
+     * @param recipientUid the uid of the app receiving visibility
+     * @param instantAppId the app ID of the instant app being made visible
+     *                      to the recipient
+     * @return {@code true} if access is granted.
+     */
     @GuardedBy("mService.mLock")
-    public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
+    public boolean grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
             int recipientUid, int instantAppId) {
         if (mInstalledInstantAppUids == null) {
-            return;     // no instant apps installed; no need to grant
+            return false;     // no instant apps installed; no need to grant
         }
         WatchedSparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
         if (instantAppList == null || !instantAppList.get(instantAppId)) {
-            return;     // instant app id isn't installed; no need to grant
+            return false;     // instant app id isn't installed; no need to grant
         }
         if (instantAppList.get(recipientUid)) {
-            return;     // target app id is an instant app; no need to grant
+            return false;     // target app id is an instant app; no need to grant
         }
         if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
             final Set<String> categories = intent.getCategories();
             if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
-                return;  // launched via VIEW/BROWSABLE intent; no need to grant
+                return false;  // launched via VIEW/BROWSABLE intent; no need to grant
             }
         }
         WatchedSparseArray<WatchedSparseBooleanArray> targetAppList = mInstantGrants.get(userId);
@@ -500,6 +511,7 @@
             targetAppList.put(recipientUid, instantGrantList);
         }
         instantGrantList.put(instantAppId, true /*granted*/);
+        return true;
     }
 
     @GuardedBy("mService.mLock")
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c4a23f9..f240d85 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1316,10 +1316,6 @@
                 } finally {
                     mListeners.finishBroadcast();
                 }
-                PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-                pmi.registerInstalledLoadingProgressCallback(packageName,
-                        new PackageLoadingProgressCallback(packageName, user),
-                        user.getIdentifier());
                 super.onPackageAdded(packageName, uid);
             }
 
@@ -1542,38 +1538,5 @@
                 checkCallbackCount();
             }
         }
-
-        class PackageLoadingProgressCallback extends
-                PackageManagerInternal.InstalledLoadingProgressCallback {
-            private String mPackageName;
-            private UserHandle mUser;
-
-            PackageLoadingProgressCallback(String packageName, UserHandle user) {
-                super(mCallbackHandler);
-                mPackageName = packageName;
-                mUser = user;
-            }
-
-            @Override
-            public void onLoadingProgressChanged(float progress) {
-                final int n = mListeners.beginBroadcast();
-                try {
-                    for (int i = 0; i < n; i++) {
-                        IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
-                        BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
-                        if (!isEnabledProfileOf(cookie.user, mUser, "onLoadingProgressChanged")) {
-                            continue;
-                        }
-                        try {
-                            listener.onPackageLoadingProgressChanged(mUser, mPackageName, progress);
-                        } catch (RemoteException re) {
-                            Slog.d(TAG, "Callback failed ", re);
-                        }
-                    }
-                } finally {
-                    mListeners.finishBroadcast();
-                }
-            }
-        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 71b99bd..6485c0c 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -16,8 +16,9 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-import static android.content.pm.PackageParser.isApkFile;
+import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.incremental.IncrementalManager.isIncrementalPath;
 
@@ -407,8 +408,14 @@
                 boolean needsRenderScriptOverride = false;
                 if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null
                         && NativeLibraryHelper.hasRenderscriptBitcode(handle)) {
-                    abiList = Build.SUPPORTED_32_BIT_ABIS;
-                    needsRenderScriptOverride = true;
+                    if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+                        abiList = Build.SUPPORTED_32_BIT_ABIS;
+                        needsRenderScriptOverride = true;
+                    } else {
+                        throw new PackageManagerException(
+                                INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
+                                "Apks with renderscript are not supported on 64-bit only devices");
+                    }
                 }
 
                 final int copyRet;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index e143bd0..7c42569 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -70,7 +70,6 @@
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.IPackageInstallerSession;
 import android.content.pm.IPackageInstallerSessionFileSystemConnector;
-import android.content.pm.IPackageLoadingProgressCallback;
 import android.content.pm.InstallationFile;
 import android.content.pm.InstallationFileParcel;
 import android.content.pm.PackageInfo;
@@ -81,11 +80,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.dex.DexMetadataHelper;
+import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.graphics.Bitmap;
@@ -321,8 +321,6 @@
     private float mProgress = 0;
     @GuardedBy("mLock")
     private float mReportedProgress = -1;
-    @GuardedBy("mLock")
-    private float mIncrementalProgress = 0;
 
     /** State of the session. */
     @GuardedBy("mLock")
@@ -1201,12 +1199,7 @@
 
     @GuardedBy("mLock")
     private void computeProgressLocked(boolean forcePublish) {
-        // This method is triggered when the client progress is updated or the incremental progress
-        // is updated. For incremental installs, ignore the progress values reported from client.
-        // Instead, only use the progress reported by IncFs as the percentage of loading completion.
-        final float loadingProgress =
-                isIncrementalInstallation() ? mIncrementalProgress : mClientProgress;
-        mProgress = MathUtils.constrain(loadingProgress * 0.8f, 0f, 0.8f)
+        mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
                 + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
 
         // Only publish when meaningful change
@@ -2671,16 +2664,17 @@
 
         // Populate package name of the apex session
         mPackageName = null;
-        final ApkLite apk;
-        try {
-            apk = PackageParser.parseApkLite(
-                    mResolvedBaseFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
-        } catch (PackageParserException e) {
-            throw PackageManagerException.from(e);
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(),
+                mResolvedBaseFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
+        if (ret.isError()) {
+            throw new PackageManagerException(ret.getErrorCode(), ret.getErrorMessage(),
+                    ret.getException());
         }
+        final ApkLite apk = ret.getResult();
 
         if (mPackageName == null) {
-            mPackageName = apk.packageName;
+            mPackageName = apk.getPackageName();
             mVersionCode = apk.getLongVersionCode();
         }
     }
@@ -2745,29 +2739,29 @@
 
         // Verify that all staged packages are internally consistent
         final ArraySet<String> stagedSplits = new ArraySet<>();
-        final ArrayMap<String, PackageParser.ApkLite> splitApks = new ArrayMap<>();
-        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ArrayMap<String, ApkLite> splitApks = new ArrayMap<>();
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
         for (File addedFile : addedFiles) {
-            ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
-                    addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
+            final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
+                    addedFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
             if (result.isError()) {
                 throw new PackageManagerException(result.getErrorCode(),
                         result.getErrorMessage(), result.getException());
             }
 
             final ApkLite apk = result.getResult();
-            if (!stagedSplits.add(apk.splitName)) {
+            if (!stagedSplits.add(apk.getSplitName())) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                        "Split " + apk.splitName + " was defined multiple times");
+                        "Split " + apk.getSplitName() + " was defined multiple times");
             }
 
             // Use first package to define unknown values
             if (mPackageName == null) {
-                mPackageName = apk.packageName;
+                mPackageName = apk.getPackageName();
                 mVersionCode = apk.getLongVersionCode();
             }
             if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
-                mSigningDetails = apk.signingDetails;
+                mSigningDetails = apk.getSigningDetails();
             }
 
             assertApkConsistentLocked(String.valueOf(addedFile), apk);
@@ -2780,10 +2774,10 @@
             }
 
             // Yell loudly if installers drop attribute installLocation when apps explicitly set.
-            if (apk.installLocation != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {
+            if (apk.getInstallLocation() != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {
                 final String installerPackageName = getInstallerPackageName();
                 if (installerPackageName != null
-                        && (params.installLocation != apk.installLocation)) {
+                        && (params.installLocation != apk.getInstallLocation())) {
                     Slog.wtf(TAG, installerPackageName
                             + " drops manifest attribute android:installLocation in " + targetName
                             + " for " + mPackageName);
@@ -2791,14 +2785,14 @@
             }
 
             final File targetFile = new File(stageDir, targetName);
-            resolveAndStageFileLocked(addedFile, targetFile, apk.splitName);
+            resolveAndStageFileLocked(addedFile, targetFile, apk.getSplitName());
 
             // Base is coming from session
-            if (apk.splitName == null) {
+            if (apk.getSplitName() == null) {
                 mResolvedBaseFile = targetFile;
                 baseApk = apk;
             } else {
-                splitApks.put(apk.splitName, apk);
+                splitApks.put(apk.getSplitName(), apk);
             }
         }
 
@@ -2854,7 +2848,7 @@
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Full install must include a base package");
             }
-            if (baseApk.isSplitRequired && stagedSplits.size() <= 1) {
+            if (baseApk.isSplitRequired() && stagedSplits.size() <= 1) {
                 throw new PackageManagerException(INSTALL_FAILED_MISSING_SPLIT,
                         "Missing split for " + mPackageName);
             }
@@ -2879,10 +2873,10 @@
             }
             final PackageLite existing = pkgLiteResult.getResult();
             packageLite = existing;
-            assertPackageConsistentLocked("Existing", existing.packageName,
+            assertPackageConsistentLocked("Existing", existing.getPackageName(),
                     existing.getLongVersionCode());
             final PackageParser.SigningDetails signingDetails =
-                    unsafeGetCertsWithoutVerification(existing.baseCodePath);
+                    unsafeGetCertsWithoutVerification(existing.getBaseApkPath());
             if (!mSigningDetails.signaturesMatchExactly(signingDetails)) {
                 throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                         "Existing signatures are inconsistent");
@@ -2895,10 +2889,10 @@
             }
 
             // Inherit splits if not overridden.
-            if (!ArrayUtils.isEmpty(existing.splitNames)) {
-                for (int i = 0; i < existing.splitNames.length; i++) {
-                    final String splitName = existing.splitNames[i];
-                    final File splitFile = new File(existing.splitCodePaths[i]);
+            if (!ArrayUtils.isEmpty(existing.getSplitNames())) {
+                for (int i = 0; i < existing.getSplitNames().length; i++) {
+                    final String splitName = existing.getSplitNames()[i];
+                    final File splitFile = new File(existing.getSplitApkPaths()[i]);
                     final boolean splitRemoved = removeSplitList.contains(splitName);
                     if (!stagedSplits.contains(splitName) && !splitRemoved) {
                         inheritFileLocked(splitFile);
@@ -2978,8 +2972,8 @@
                 }
             }
             // For the case of split required, failed if no splits existed
-            if (packageLite.isSplitRequired) {
-                final int existingSplits = ArrayUtils.size(existing.splitNames);
+            if (packageLite.isSplitRequired()) {
+                final int existingSplits = ArrayUtils.size(existing.getSplitNames());
                 final boolean allSplitsRemoved = (existingSplits == removeSplitList.size());
                 final boolean onlyBaseFileStaged = (stagedSplits.size() == 1
                         && stagedSplits.contains(null));
@@ -2989,7 +2983,7 @@
                 }
             }
         }
-        if (packageLite.useEmbeddedDex) {
+        if (packageLite.isUseEmbeddedDex()) {
             for (File file : mResolvedStagedFiles) {
                 if (file.getName().endsWith(".apk")
                         && !DexManager.auditUncompressedDexInApk(file.getPath())) {
@@ -3002,7 +2996,7 @@
 
         final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID);
         if (isInstallerShell && isIncrementalInstallation() && mIncrementalFileStorages != null) {
-            if (!packageLite.debuggable && !packageLite.profilableByShell) {
+            if (!packageLite.isDebuggable() && !packageLite.isProfileableByShell()) {
                 mIncrementalFileStorages.disallowReadLogs();
             }
         }
@@ -3174,8 +3168,8 @@
     @GuardedBy("mLock")
     private void assertApkConsistentLocked(String tag, ApkLite apk)
             throws PackageManagerException {
-        assertPackageConsistentLocked(tag, apk.packageName, apk.getLongVersionCode());
-        if (!mSigningDetails.signaturesMatchExactly(apk.signingDetails)) {
+        assertPackageConsistentLocked(tag, apk.getPackageName(), apk.getLongVersionCode());
+        if (!mSigningDetails.signaturesMatchExactly(apk.getSigningDetails())) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     tag + " signatures are inconsistent");
         }
@@ -3305,17 +3299,28 @@
         }
     }
 
+    private void linkFile(String relativePath, String fromBase, String toBase) throws IOException {
+        try {
+            // Try
+            if (mIncrementalFileStorages != null && mIncrementalFileStorages.makeLink(relativePath,
+                    fromBase, toBase)) {
+                return;
+            }
+            mPm.mInstaller.linkFile(relativePath, fromBase, toBase);
+        } catch (InstallerException | IOException e) {
+            throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
+                    + fromBase + ", " + toBase + ")", e);
+        }
+    }
+
     private void linkFiles(List<File> fromFiles, File toDir, File fromDir)
             throws IOException {
         for (File fromFile : fromFiles) {
             final String relativePath = getRelativePath(fromFile, fromDir);
-            try {
-                mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(),
-                        toDir.getAbsolutePath());
-            } catch (InstallerException e) {
-                throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
-                        + fromDir + ", " + toDir + ")", e);
-            }
+            final String fromBase = fromDir.getAbsolutePath();
+            final String toBase = toDir.getAbsolutePath();
+
+            linkFile(relativePath, fromBase, toBase);
         }
 
         Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
@@ -3583,12 +3588,6 @@
 
         // Retrying commit.
         if (mIncrementalFileStorages != null) {
-            try {
-                mIncrementalFileStorages.startLoading();
-            } catch (IOException e) {
-                throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
-                        e.getCause());
-            }
             return false;
         }
 
@@ -3763,18 +3762,15 @@
             };
 
             try {
+                final PackageInfo pkgInfo = mPm.getPackageInfo(this.params.appPackageName, 0,
+                        userId);
+                final File inheritedDir =
+                        (pkgInfo != null && pkgInfo.applicationInfo != null) ? new File(
+                                pkgInfo.applicationInfo.getCodePath()).getParentFile() : null;
+
                 mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir,
-                        params, statusListener, healthCheckParams, healthListener, addedFiles,
-                        perUidReadTimeouts,
-                        new IPackageLoadingProgressCallback.Stub() {
-                            @Override
-                            public void onPackageLoadingProgressChanged(float progress) {
-                                synchronized (mLock) {
-                                    mIncrementalProgress = progress;
-                                    computeProgressLocked(true);
-                                }
-                            }
-                        });
+                        inheritedDir, params, statusListener, healthCheckParams, healthListener,
+                        addedFiles, perUidReadTimeouts);
                 return false;
             } catch (IOException e) {
                 throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 68b0698..1b444f8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24,7 +24,6 @@
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.content.Intent.ACTION_MAIN;
-import static android.content.Intent.CATEGORY_BROWSABLE;
 import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
@@ -68,11 +67,7 @@
 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
 import static android.content.pm.PackageManager.INSTALL_STAGED;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
 import static android.content.pm.PackageManager.MATCH_ALL;
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.MATCH_APEX;
@@ -101,7 +96,7 @@
 import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE;
 import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
-import static android.content.pm.PackageParser.isApkFile;
+import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.incremental.IncrementalManager.isIncrementalPath;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
@@ -177,6 +172,7 @@
 import android.content.pm.FallbackCategoryProvider;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IDexModuleRegisterCallback;
+import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageChangeObserver;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
@@ -211,9 +207,7 @@
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser.ParseFlags;
 import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.PackagePartitions;
@@ -241,7 +235,9 @@
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.IArtManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedIntentInfo;
@@ -347,6 +343,7 @@
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.AttributeCache;
 import com.android.internal.telephony.CarrierAppUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
@@ -355,7 +352,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.permission.persistence.RuntimePermissionsPersistence;
-import com.android.server.AttributeCache;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.EventLogTags;
 import com.android.server.FgThread;
@@ -390,6 +386,11 @@
 import com.android.server.pm.permission.Permission;
 import com.android.server.pm.permission.PermissionManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationService;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2;
 import com.android.server.rollback.RollbackManagerInternal;
 import com.android.server.security.VerityUtils;
 import com.android.server.storage.DeviceStorageMonitorInternal;
@@ -518,8 +519,7 @@
     public static final boolean DEBUG_PERMISSIONS = false;
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
     public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
-    public static final boolean DEBUG_CACHES = false;
-    public static final boolean TRACE_CACHES = false;
+    public static final boolean TRACE_SNAPSHOTS = false;
     private static final boolean DEBUG_PER_UID_READ_TIMEOUTS = false;
 
     // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
@@ -1064,6 +1064,9 @@
         private final ServiceProducer mGetLocalServiceProducer;
         private final ServiceProducer mGetSystemServiceProducer;
         private final Singleton<ModuleInfoProvider> mModuleInfoProviderProducer;
+        private final Singleton<DomainVerificationManagerInternal>
+                mDomainVerificationManagerInternalProducer;
+        private final Singleton<Handler> mHandlerProducer;
 
         Injector(Context context, Object lock, Installer installer,
                 Object installLock, PackageAbiHelper abiHelper,
@@ -1092,6 +1095,9 @@
                         instantAppResolverConnectionProducer,
                 Producer<ModuleInfoProvider> moduleInfoProviderProducer,
                 Producer<LegacyPermissionManagerInternal> legacyPermissionManagerInternalProducer,
+                Producer<DomainVerificationManagerInternal>
+                        domainVerificationManagerInternalProducer,
+                Producer<Handler> handlerProducer,
                 SystemWrapper systemWrapper,
                 ServiceProducer getLocalServiceProducer,
                 ServiceProducer getSystemServiceProducer) {
@@ -1129,6 +1135,9 @@
             mSystemWrapper = systemWrapper;
             mGetLocalServiceProducer = getLocalServiceProducer;
             mGetSystemServiceProducer = getSystemServiceProducer;
+            mDomainVerificationManagerInternalProducer =
+                    new Singleton<>(domainVerificationManagerInternalProducer);
+            mHandlerProducer = new Singleton<>(handlerProducer);
         }
 
         /**
@@ -1274,6 +1283,14 @@
         public LegacyPermissionManagerInternal getLegacyPermissionManagerInternal() {
             return mLegacyPermissionManagerInternalProducer.get(this, mPackageManager);
         }
+
+        public DomainVerificationManagerInternal getDomainVerificationManagerInternal() {
+            return mDomainVerificationManagerInternalProducer.get(this, mPackageManager);
+        }
+
+        public Handler getHandler() {
+            return mHandlerProducer.get(this, mPackageManager);
+        }
     }
 
     /** Provides an abstraction to static access to system state. */
@@ -1328,8 +1345,6 @@
         public InstantAppRegistry instantAppRegistry;
         public InstantAppResolverConnection instantAppResolverConnection;
         public ComponentName instantAppResolverSettingsComponent;
-        public @Nullable IntentFilterVerifier<ParsedIntentInfo> intentFilterVerifier;
-        public @Nullable ComponentName intentFilterVerifierComponent;
         public boolean isPreNmr1Upgrade;
         public boolean isPreNupgrade;
         public boolean isPreQupgrade;
@@ -1452,10 +1467,8 @@
 
     boolean mResolverReplaced = false;
 
-    private final @Nullable ComponentName mIntentFilterVerifierComponent;
-    private final @Nullable IntentFilterVerifier<ParsedIntentInfo> mIntentFilterVerifier;
-
-    private int mIntentFilterVerificationToken = 0;
+    @NonNull
+    private final DomainVerificationManagerInternal mDomainVerificationManager;
 
     /** The service connection to the ephemeral resolver */
     final InstantAppResolverConnection mInstantAppResolverConnection;
@@ -1471,9 +1484,6 @@
     private final Map<String, Pair<PackageInstalledInfo, IPackageInstallObserver2>>
             mNoKillInstallObservers = Collections.synchronizedMap(new HashMap<>());
 
-    final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
-            = new SparseArray<>();
-
     // Internal interface for permission manager
     private final PermissionManagerServiceInternal mPermissionManager;
 
@@ -1493,262 +1503,6 @@
 
     private final PackageProperty mPackageProperty = new PackageProperty();
 
-    private static class IFVerificationParams {
-        String packageName;
-        boolean hasDomainUrls;
-        List<ParsedActivity> activities;
-        boolean replacing;
-        int userId;
-        int verifierUid;
-
-        public IFVerificationParams(String packageName, boolean hasDomainUrls,
-                List<ParsedActivity> activities, boolean _replacing,
-                int _userId, int _verifierUid) {
-            this.packageName = packageName;
-            this.hasDomainUrls = hasDomainUrls;
-            this.activities = activities;
-            replacing = _replacing;
-            userId = _userId;
-            verifierUid = _verifierUid;
-        }
-    }
-
-    private interface IntentFilterVerifier<T extends IntentFilter> {
-        boolean addOneIntentFilterVerification(int verifierId, int userId, int verificationId,
-                                               T filter, String packageName);
-        void startVerifications(int userId);
-        void receiveVerificationResponse(int verificationId);
-    }
-
-    private class IntentVerifierProxy implements IntentFilterVerifier<ParsedIntentInfo> {
-        private Context mContext;
-        private ComponentName mIntentFilterVerifierComponent;
-        private ArrayList<Integer> mCurrentIntentFilterVerifications = new ArrayList<>();
-
-        public IntentVerifierProxy(Context context, ComponentName verifierComponent) {
-            mContext = context;
-            mIntentFilterVerifierComponent = verifierComponent;
-        }
-
-        private String getDefaultScheme() {
-            return IntentFilter.SCHEME_HTTPS;
-        }
-
-        @Override
-        public void startVerifications(int userId) {
-            // Launch verifications requests
-            int count = mCurrentIntentFilterVerifications.size();
-            for (int n=0; n<count; n++) {
-                int verificationId = mCurrentIntentFilterVerifications.get(n);
-                final IntentFilterVerificationState ivs =
-                        mIntentFilterVerificationStates.get(verificationId);
-
-                String packageName = ivs.getPackageName();
-
-                ArrayList<ParsedIntentInfo> filters = ivs.getFilters();
-                final int filterCount = filters.size();
-                ArraySet<String> domainsSet = new ArraySet<>();
-                for (int m=0; m<filterCount; m++) {
-                    ParsedIntentInfo filter = filters.get(m);
-                    domainsSet.addAll(filter.getHostsList());
-                }
-                synchronized (mLock) {
-                    if (mSettings.createIntentFilterVerificationIfNeededLPw(
-                            packageName, domainsSet) != null) {
-                        scheduleWriteSettingsLocked();
-                    }
-                }
-                sendVerificationRequest(verificationId, ivs);
-            }
-            mCurrentIntentFilterVerifications.clear();
-        }
-
-        private void sendVerificationRequest(int verificationId, IntentFilterVerificationState ivs) {
-            Intent verificationIntent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION);
-            verificationIntent.putExtra(
-                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
-                    verificationId);
-            verificationIntent.putExtra(
-                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
-                    getDefaultScheme());
-            verificationIntent.putExtra(
-                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
-                    ivs.getHostsString());
-            verificationIntent.putExtra(
-                    PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
-                    ivs.getPackageName());
-            verificationIntent.setComponent(mIntentFilterVerifierComponent);
-            verificationIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-
-            final long whitelistTimeout = getVerificationTimeout();
-            final BroadcastOptions options = BroadcastOptions.makeBasic();
-            options.setTemporaryAppWhitelistDuration(whitelistTimeout);
-
-            DeviceIdleInternal idleController =
-                    mInjector.getLocalService(DeviceIdleInternal.class);
-            idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
-                    mIntentFilterVerifierComponent.getPackageName(), whitelistTimeout,
-                    UserHandle.USER_SYSTEM, true, "intent filter verifier");
-
-            mContext.sendBroadcastAsUser(verificationIntent, UserHandle.SYSTEM,
-                    null, options.toBundle());
-            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                    "Sending IntentFilter verification broadcast");
-        }
-
-        public void receiveVerificationResponse(int verificationId) {
-            IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
-
-            final boolean verified = ivs.isVerified();
-
-            ArrayList<ParsedIntentInfo> filters = ivs.getFilters();
-            final int count = filters.size();
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.i(TAG, "Received verification response " + verificationId
-                        + " for " + count + " filters, verified=" + verified);
-            }
-            for (int n=0; n<count; n++) {
-                ParsedIntentInfo filter = filters.get(n);
-                filter.setVerified(verified);
-
-                if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "IntentFilter " + filter.toString()
-                        + " verified with result:" + verified + " and hosts:"
-                        + ivs.getHostsString());
-            }
-
-            mIntentFilterVerificationStates.remove(verificationId);
-
-            final String packageName = ivs.getPackageName();
-            IntentFilterVerificationInfo ivi;
-
-            synchronized (mLock) {
-                ivi = mSettings.getIntentFilterVerificationLPr(packageName);
-            }
-            if (ivi == null) {
-                Slog.w(TAG, "IntentFilterVerificationInfo not found for verificationId:"
-                        + verificationId + " packageName:" + packageName);
-                return;
-            }
-
-            synchronized (mLock) {
-                if (verified) {
-                    ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
-                } else {
-                    ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK);
-                }
-                scheduleWriteSettingsLocked();
-
-                final int userId = ivs.getUserId();
-                if (userId != UserHandle.USER_ALL) {
-                    final int userStatus =
-                            mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
-
-                    int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-                    boolean needUpdate = false;
-
-                    // In a success case, we promote from undefined or ASK to ALWAYS.  This
-                    // supports a flow where the app fails validation but then ships an updated
-                    // APK that passes, and therefore deserves to be in ALWAYS.
-                    //
-                    // If validation failed, the undefined state winds up in the basic ASK behavior,
-                    // but apps that previously passed and became ALWAYS are *demoted* out of
-                    // that state, since they would not deserve the ALWAYS behavior in case of a
-                    // clean install.
-                    switch (userStatus) {
-                        case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
-                            if (!verified) {
-                                // Don't demote if sysconfig says 'always'
-                                SystemConfig systemConfig = mInjector.getSystemConfig();
-                                ArraySet<String> packages = systemConfig.getLinkedApps();
-                                if (!packages.contains(packageName)) {
-                                    // updatedStatus is already UNDEFINED
-                                    needUpdate = true;
-
-                                    if (DEBUG_DOMAIN_VERIFICATION) {
-                                        Slog.d(TAG, "Formerly validated but now failing; demoting");
-                                    }
-                                } else {
-                                    if (DEBUG_DOMAIN_VERIFICATION) {
-                                        Slog.d(TAG, "Updating bundled package " + packageName
-                                                + " failed autoVerify, but sysconfig supersedes");
-                                    }
-                                    // leave needUpdate == false here intentionally
-                                }
-                            }
-                            break;
-
-                        case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
-                            // Stay in 'undefined' on verification failure
-                            if (verified) {
-                                updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-                            }
-                            needUpdate = true;
-                            if (DEBUG_DOMAIN_VERIFICATION) {
-                                Slog.d(TAG, "Applying update; old=" + userStatus
-                                        + " new=" + updatedStatus);
-                            }
-                            break;
-
-                        case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
-                            // Keep in 'ask' on failure
-                            if (verified) {
-                                updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-                                needUpdate = true;
-                            }
-                            break;
-
-                        default:
-                            // Nothing to do
-                    }
-
-                    if (needUpdate) {
-                        mSettings.updateIntentFilterVerificationStatusLPw(
-                                packageName, updatedStatus, userId);
-                        scheduleWritePackageRestrictionsLocked(userId);
-                    }
-                } else {
-                    Slog.i(TAG, "autoVerify ignored when installing for all users");
-                }
-            }
-        }
-
-        @Override
-        public boolean addOneIntentFilterVerification(int verifierUid, int userId, int verificationId,
-                ParsedIntentInfo filter, String packageName) {
-            if (!hasValidDomains(filter)) {
-                return false;
-            }
-            IntentFilterVerificationState ivs = mIntentFilterVerificationStates.get(verificationId);
-            if (ivs == null) {
-                ivs = createDomainVerificationState(verifierUid, userId, verificationId,
-                        packageName);
-            }
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.d(TAG, "Adding verification filter for " + packageName + ": " + filter);
-            }
-            ivs.addFilter(filter);
-            return true;
-        }
-
-        private IntentFilterVerificationState createDomainVerificationState(int verifierUid,
-                int userId, int verificationId, String packageName) {
-            IntentFilterVerificationState ivs = new IntentFilterVerificationState(
-                    verifierUid, userId, packageName);
-            ivs.setPendingState();
-            synchronized (mLock) {
-                mIntentFilterVerificationStates.append(verificationId, ivs);
-                mCurrentIntentFilterVerifications.add(verificationId);
-            }
-            return ivs;
-        }
-    }
-
-    private static boolean hasValidDomains(ParsedIntentInfo filter) {
-        return filter.hasCategory(Intent.CATEGORY_BROWSABLE)
-                && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
-                        filter.hasDataScheme(IntentFilter.SCHEME_HTTPS));
-    }
-
     // Set of pending broadcasts for aggregating enable/disable of components.
     @VisibleForTesting(visibility = Visibility.PACKAGE)
     public static class PendingPackageBroadcasts {
@@ -1823,8 +1577,8 @@
     static final int WRITE_PACKAGE_RESTRICTIONS = 14;
     static final int PACKAGE_VERIFIED = 15;
     static final int CHECK_PENDING_VERIFICATION = 16;
-    static final int START_INTENT_FILTER_VERIFICATIONS = 17;
-    static final int INTENT_FILTER_VERIFIED = 18;
+    // public static final int UNUSED = 17;
+    // public static final int UNUSED = 18;
     static final int WRITE_PACKAGE_LIST = 19;
     static final int INSTANT_APP_RESOLUTION_PHASE_TWO = 20;
     static final int ENABLE_ROLLBACK_STATUS = 21;
@@ -1833,6 +1587,7 @@
     static final int DEFERRED_NO_KILL_INSTALL_OBSERVER = 24;
     static final int INTEGRITY_VERIFICATION_COMPLETE = 25;
     static final int CHECK_PENDING_INTEGRITY_VERIFICATION = 26;
+    static final int DOMAIN_VERIFICATION = 27;
 
     static final int DEFERRED_NO_KILL_POST_DELETE_DELAY_MS = 3 * 1000;
     static final int DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS = 500;
@@ -1926,6 +1681,74 @@
     private final PackageUsage mPackageUsage = new PackageUsage();
     private final CompilerStats mCompilerStats = new CompilerStats();
 
+    private final DomainVerificationConnection mDomainVerificationConnection =
+            new DomainVerificationConnection();
+
+    private class DomainVerificationConnection implements
+            DomainVerificationService.Connection, DomainVerificationProxyV1.Connection,
+            DomainVerificationProxyV2.Connection {
+
+        @Override
+        public void scheduleWriteSettings() {
+            synchronized (mLock) {
+                PackageManagerService.this.scheduleWriteSettingsLocked();
+            }
+        }
+
+        @Override
+        public int getCallingUid() {
+            return Binder.getCallingUid();
+        }
+
+        @UserIdInt
+        @Override
+        public int getCallingUserId() {
+            return UserHandle.getCallingUserId();
+        }
+
+        @Override
+        public void schedule(int code, @Nullable Object object) {
+            Message message = mHandler.obtainMessage(DOMAIN_VERIFICATION);
+            message.arg1 = code;
+            message.obj = object;
+            mHandler.sendMessage(message);
+        }
+
+        @Override
+        public long getPowerSaveTempWhitelistAppDuration() {
+            return PackageManagerService.this.getVerificationTimeout();
+        }
+
+        @Override
+        public DeviceIdleInternal getDeviceIdleInternal() {
+            return mInjector.getLocalService(DeviceIdleInternal.class);
+        }
+
+        @Override
+        public boolean isCallerPackage(int callingUid, @NonNull String packageName) {
+            final int callingUserId = UserHandle.getUserId(callingUid);
+            return callingUid == getPackageUid(packageName, 0, callingUserId);
+        }
+
+        @Nullable
+        @Override
+        public PackageSetting getPackageSettingLocked(@NonNull String pkgName) {
+            return PackageManagerService.this.getPackageSetting(pkgName);
+        }
+
+        @Nullable
+        @Override
+        public AndroidPackage getPackageLocked(@NonNull String pkgName) {
+            return PackageManagerService.this.getPackage(pkgName);
+        }
+
+        @Nullable
+        @Override
+        public AndroidPackage getPackage(@NonNull String packageName) {
+            return getPackageLocked(packageName);
+        }
+    }
+
     /**
      * Invalidate the package info cache, which includes updating the cached computer.
      * @hide
@@ -2015,7 +1838,7 @@
     }
 
     /**
-     * A computer provides the functional interface to the cache
+     * A computer provides the functional interface to the snapshot.
      */
     private interface Computer {
 
@@ -2146,7 +1969,6 @@
                 boolean isImplicitImageCaptureIntentAndNotSetByDpc);
         int updateFlagsForResolve(int flags, int userId, int callingUid, boolean wantInstantApps,
                 boolean onlyExposedExplicitly, boolean isImplicitImageCaptureIntentAndNotSetByDpc);
-        long getDomainVerificationStatusLPr(PackageSetting ps, int userId);
         void enforceCrossUserOrProfilePermission(int callingUid, @UserIdInt int userId,
                 boolean requireFullPermission, boolean checkShell, String message);
         void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
@@ -2154,6 +1976,10 @@
         void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
                 boolean requireFullPermission, boolean checkShell,
                 boolean requirePermissionWhenSameUser, String message);
+        SigningDetails getSigningDetails(@NonNull String packageName);
+        SigningDetails getSigningDetails(int uid);
+        boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId);
+        boolean filterAppAccess(String packageName, int callingUid, int userId);
     }
 
     /**
@@ -2181,13 +2007,11 @@
         private final ResolveInfo mInstantAppInstallerInfo;
         private final InstantAppRegistry mInstantAppRegistry;
         private final ApplicationInfo mLocalAndroidApplication;
+        private final AppsFilter mAppsFilter;
 
         // Immutable service attribute
         private final String mAppPredictionServicePackage;
 
-        // TODO: create cache copies of the following attributes
-        private final AppsFilter mAppsFilter;
-
         // The following are not cloned since changes to these have never
         // been guarded by the PMS lock.
         private final Context mContext;
@@ -2198,6 +2022,7 @@
         private final ComponentResolver mComponentResolver;
         private final InstantAppResolverConnection mInstantAppResolverConnection;
         private final DefaultAppProvider mDefaultAppProvider;
+        private final DomainVerificationManagerInternal mDomainVerificationManager;
 
         // PackageManagerService attributes that are primitives are referenced through the
         // pms object directly.  Primitives are the only attributes so referenced.
@@ -2243,6 +2068,7 @@
             mComponentResolver = args.service.mComponentResolver;
             mInstantAppResolverConnection = args.service.mInstantAppResolverConnection;
             mDefaultAppProvider = args.service.mDefaultAppProvider;
+            mDomainVerificationManager = args.service.mDomainVerificationManager;
 
             // Used to reference PMS attributes that are primitives and which are not
             // updated under control of the PMS lock.
@@ -2754,8 +2580,6 @@
             final ArrayList<ResolveInfo> result = new ArrayList<>();
             final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
             final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
-            final ArrayList<ResolveInfo> alwaysAskList = new ArrayList<>();
-            final ArrayList<ResolveInfo> neverList = new ArrayList<>();
             final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
             final int count = candidates.size();
             // First, try to use linked apps. Partition the candidates into four lists:
@@ -2771,43 +2595,15 @@
                         matchAllList.add(info);
                         continue;
                     }
-                    // Try to get the status from User settings first
-                    long packedStatus = getDomainVerificationStatusLPr(ps, userId);
-                    int status = (int)(packedStatus >> 32);
-                    int linkGeneration = (int)(packedStatus & 0xFFFFFFFF);
-                    if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
-                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
-                            Slog.i(TAG, "  + always: " + info.activityInfo.packageName
-                                    + " : linkgen=" + linkGeneration);
-                        }
 
-                        if (!intent.hasCategory(CATEGORY_BROWSABLE)
-                                || !intent.hasCategory(CATEGORY_DEFAULT)) {
-                            undefinedList.add(info);
-                            continue;
-                        }
-
-                        // Use link-enabled generation as preferredOrder, i.e.
-                        // prefer newly-enabled over earlier-enabled.
-                        info.preferredOrder = linkGeneration;
+                    boolean isAlways = mDomainVerificationManager
+                            .isApprovedForDomain(ps, intent, userId);
+                    if (isAlways) {
                         alwaysList.add(info);
-                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
-                            Slog.i(TAG, "  + never: " + info.activityInfo.packageName);
-                        }
-                        neverList.add(info);
-                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
-                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
-                            Slog.i(TAG, "  + always-ask: " + info.activityInfo.packageName);
-                        }
-                        alwaysAskList.add(info);
-                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED ||
-                            status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK) {
-                        if (DEBUG_DOMAIN_VERIFICATION || debug) {
-                            Slog.i(TAG, "  + ask: " + info.activityInfo.packageName);
-                        }
+                    } else {
                         undefinedList.add(info);
                     }
+                    continue;
                 }
             }
 
@@ -2821,25 +2617,12 @@
                 // Add all undefined apps as we want them to appear in the disambiguation dialog.
                 result.addAll(undefinedList);
                 // Maybe add one for the other profile.
-                if (xpDomainInfo != null && (
-                        xpDomainInfo.bestDomainVerificationStatus
-                        != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)) {
+                if (xpDomainInfo != null && xpDomainInfo.wereAnyDomainsVerificationApproved) {
                     result.add(xpDomainInfo.resolveInfo);
                 }
                 includeBrowser = true;
             }
 
-            // The presence of any 'always ask' alternatives means we'll also offer browsers.
-            // If there were 'always' entries their preferred order has been set, so we also
-            // back that off to make the alternatives equivalent
-            if (alwaysAskList.size() > 0) {
-                for (ResolveInfo i : result) {
-                    i.preferredOrder = 0;
-                }
-                result.addAll(alwaysAskList);
-                includeBrowser = true;
-            }
-
             if (includeBrowser) {
                 // Also add browsers (all of them or only the default one)
                 if (DEBUG_DOMAIN_VERIFICATION) {
@@ -2890,7 +2673,6 @@
                 // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
                 if (result.size() == 0) {
                     result.addAll(candidates);
-                    result.removeAll(neverList);
                 }
             }
             return result;
@@ -2984,21 +2766,16 @@
                 if (ps == null) {
                     continue;
                 }
-                long verificationState = getDomainVerificationStatusLPr(ps, parentUserId);
-                int status = (int)(verificationState >> 32);
                 if (result == null) {
                     result = new CrossProfileDomainInfo();
                     result.resolveInfo = createForwardingResolveInfoUnchecked(new IntentFilter(),
                             sourceUserId, parentUserId);
-                    result.bestDomainVerificationStatus = status;
-                } else {
-                    result.bestDomainVerificationStatus = bestDomainVerificationStatus(status,
-                            result.bestDomainVerificationStatus);
                 }
+
+                result.wereAnyDomainsVerificationApproved |= mDomainVerificationManager
+                        .isApprovedForDomain(ps, intent, riTargetUser.targetUserId);
             }
-            // Don't consider matches with status NEVER across profiles.
-            if (result != null && result.bestDomainVerificationStatus
-                    == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+            if (result != null && !result.wereAnyDomainsVerificationApproved) {
                 return null;
             }
             return result;
@@ -3241,26 +3018,20 @@
                     final String packageName = info.activityInfo.packageName;
                     final PackageSetting ps = mSettings.getPackageLPr(packageName);
                     if (ps.getInstantApp(userId)) {
-                        final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
-                        final int status = (int)(packedStatus >> 32);
-                        if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
-                            // there's a local instant application installed, but, the user has
-                            // chosen to never use it; skip resolution and don't acknowledge
-                            // an instant application is even available
+                        if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
                             if (DEBUG_INSTANT) {
-                                Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName);
-                            }
-                            blockResolution = true;
-                            break;
-                        } else {
-                            // we have a locally installed instant application; skip resolution
-                            // but acknowledge there's an instant application available
-                            if (DEBUG_INSTANT) {
-                                Slog.v(TAG, "Found installed instant app; pkg: " + packageName);
+                                Slog.v(TAG, "Instant app approvd for intent; pkg: "
+                                        + packageName);
                             }
                             localInstantApp = info;
-                            break;
+                        } else {
+                            if (DEBUG_INSTANT) {
+                                Slog.v(TAG, "Instant app not approved for intent; pkg: "
+                                        + packageName);
+                            }
+                            blockResolution = true;
                         }
+                        break;
                     }
                 }
             }
@@ -3862,7 +3633,8 @@
                 int i = 0;
                 for (int index = 0; index < N; index++) {
                     final PackageSetting ps = sus.packages.valueAt(index);
-                    if (ps.getInstalled(userId)) {
+                    if (ps.getInstalled(userId)
+                            && !shouldFilterApplicationLocked(ps, callingUid, userId)) {
                         res[i++] = ps.name;
                     }
                 }
@@ -4173,14 +3945,10 @@
                 if (ps != null) {
                     // only check domain verification status if the app is not a browser
                     if (!info.handleAllWebDataURI) {
-                        // Try to get the status from User settings first
-                        final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
-                        final int status = (int) (packedStatus >> 32);
-                        if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
-                                || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+                        if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
                             if (DEBUG_INSTANT) {
-                                Slog.v(TAG, "DENY instant app;"
-                                        + " pkg: " + packageName + ", status: " + status);
+                                Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName
+                                        + ", approved");
                             }
                             return false;
                         }
@@ -4473,21 +4241,6 @@
             return updateFlagsForComponent(flags, userId);
         }
 
-        // Returns a packed value as a long:
-        //
-        // high 'int'-sized word: link status: undefined/ask/never/always.
-        // low 'int'-sized word: relative priority among 'always' results.
-        public long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
-            long result = ps.getDomainVerificationStatusForUser(userId);
-            // if none available, get the status
-            if (result >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
-                if (ps.getIntentFilterVerificationInfo() != null) {
-                    result = ((long)ps.getIntentFilterVerificationInfo().getStatus()) << 32;
-                }
-            }
-            return result;
-        }
-
         /**
          * Checks if the request is from the system or an app that has the appropriate cross-user
          * permissions defined as follows:
@@ -4581,6 +4334,40 @@
             throw new SecurityException(errorMessage);
         }
 
+        public SigningDetails getSigningDetails(@NonNull String packageName) {
+            AndroidPackage p = mPackages.get(packageName);
+            if (p == null) {
+                return null;
+            }
+            return p.getSigningDetails();
+        }
+
+        public SigningDetails getSigningDetails(int uid) {
+            final int appId = UserHandle.getAppId(uid);
+            final Object obj = mSettings.getSettingLPr(appId);
+            if (obj != null) {
+                if (obj instanceof SharedUserSetting) {
+                    return ((SharedUserSetting) obj).signatures.mSigningDetails;
+                } else if (obj instanceof PackageSetting) {
+                    final PackageSetting ps = (PackageSetting) obj;
+                    return ps.signatures.mSigningDetails;
+                }
+            }
+            return SigningDetails.UNKNOWN;
+        }
+
+        public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+            PackageSetting ps = getPackageSetting(pkg.getPackageName());
+            return shouldFilterApplicationLocked(ps, callingUid,
+                    userId);
+        }
+
+        public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+            PackageSetting ps = getPackageSetting(packageName);
+            return shouldFilterApplicationLocked(ps, callingUid,
+                    userId);
+        }
+
     }
 
     /**
@@ -4731,6 +4518,26 @@
                 return super.getPackageUidInternal(packageName, flags, userId, callingUid);
             }
         }
+        public SigningDetails getSigningDetails(@NonNull String packageName) {
+            synchronized (mLock) {
+                return super.getSigningDetails(packageName);
+            }
+        }
+        public SigningDetails getSigningDetails(int uid) {
+            synchronized (mLock) {
+                return super.getSigningDetails(uid);
+            }
+        }
+        public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+            synchronized (mLock) {
+                return super.filterAppAccess(pkg, callingUid, userId);
+            }
+        }
+        public boolean filterAppAccess(String packageName, int callingUid, int userId) {
+            synchronized (mLock) {
+                return super.filterAppAccess(packageName, callingUid, userId);
+            }
+        }
     }
 
 
@@ -4738,11 +4545,11 @@
     private final Computer mLiveComputer;
     // A lock-free cache for frequently called functions.
     private volatile Computer mSnapshotComputer;
-    // If true, the cached computer object is invalid (the cache is stale).
-    // The attribute is static since it may be set from outside classes.
+    // If true, the snapshot is invalid (stale).  The attribute is static since it may be
+    // set from outside classes.
     private static volatile boolean sSnapshotInvalid = true;
-    // If true, the cache is corked.  Do not create a new cache but continue to use the
-    // existing one.  This throttles cache creation during periods of churn in Package
+    // If true, the snapshot is corked.  Do not create a new snapshot but use the live
+    // computer.  This throttles snapshot creation during periods of churn in Package
     // Manager.
     private static volatile boolean sSnapshotCorked = false;
 
@@ -4754,13 +4561,121 @@
      */
     private final Object mSnapshotLock = new Object();
 
-    // A counter of all queries that hit the cache.
-    private AtomicInteger mSnapshotHits = new AtomicInteger(0);
+    // A counter of all queries that hit the current snapshot.
+    @GuardedBy("mSnapshotLock")
+    private int mSnapshotHits = 0;
 
-    // The number of queries at the last miss.  This is updated when the cache is rebuilt
-    // (guarded by mLock) and is used to report the hit run-length.
+    // A class to record snapshot statistics.
+    private static class SnapshotStatistics {
+        // A build time is "big" if it takes longer than 5ms.
+        private static final long SNAPSHOT_BIG_BUILD_TIME_NS = TimeUnit.MILLISECONDS.toNanos(5);
+
+        // A snapshot is in quick succession to the previous snapshot if it less than
+        // 100ms since the previous snapshot.
+        private static final long SNAPSHOT_QUICK_REBUILD_INTERVAL_NS =
+                TimeUnit.MILLISECONDS.toNanos(100);
+
+        // The interval between snapshot statistics logging, in ns.
+        private static final long SNAPSHOT_LOG_INTERVAL_NS = TimeUnit.MINUTES.toNanos(10);
+
+        // The throttle parameters for big build reporting.  Do not report more than this
+        // many events in a single log interval.
+        private static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10;
+
+        // The time the snapshot statistics were last logged.
+        private long mStatisticsSent = 0;
+
+        // The number of build events logged since the last periodic log.
+        private int mLoggedBuilds = 0;
+
+        // The time of the last build.
+        private long mLastBuildTime = 0;
+
+        // The number of times the snapshot has been rebuilt since the statistics were
+        // last logged.
+        private int mRebuilds = 0;
+
+        // The number of times the snapshot has been used since it was rebuilt.
+        private int mReused = 0;
+
+        // The number of "big" build times since the last log.  "Big" is defined by
+        // SNAPSHOT_BIG_BUILD_TIME.
+        private int mBigBuilds = 0;
+
+        // The number of quick rebuilds.  "Quick" is defined by
+        // SNAPSHOT_QUICK_REBUILD_INTERVAL_NS.
+        private int mQuickRebuilds = 0;
+
+        // The time take to build a snapshot.  This is cumulative over the rebuilds recorded
+        // in mRebuilds, so the average time to build a snapshot is given by
+        // mBuildTimeNs/mRebuilds.
+        private int mBuildTimeNs = 0;
+
+        // The maximum build time since the last log.
+        private long mMaxBuildTimeNs = 0;
+
+        // The constant that converts ns to ms.  This is the divisor.
+        private final long NS_TO_MS = TimeUnit.MILLISECONDS.toNanos(1);
+
+        // Convert ns to an int ms.  The maximum range of this method is about 24 days.
+        // There is no expectation that an event will take longer than that.
+        private int nsToMs(long ns) {
+            return (int) (ns / NS_TO_MS);
+        }
+
+        // The single method records a rebuild.  The "now" parameter is passed in because
+        // the caller needed it to computer the duration, so pass it in to avoid
+        // recomputing it.
+        private void rebuild(long now, long done, int hits) {
+            if (mStatisticsSent == 0) {
+                mStatisticsSent = now;
+            }
+            final long elapsed = now - mLastBuildTime;
+            final long duration = done - now;
+            mLastBuildTime = now;
+
+            if (mMaxBuildTimeNs < duration) {
+                mMaxBuildTimeNs = duration;
+            }
+            mRebuilds++;
+            mReused += hits;
+            mBuildTimeNs += duration;
+
+            boolean log_build = false;
+            if (duration > SNAPSHOT_BIG_BUILD_TIME_NS) {
+                log_build = true;
+                mBigBuilds++;
+            }
+            if (elapsed < SNAPSHOT_QUICK_REBUILD_INTERVAL_NS) {
+                log_build = true;
+                mQuickRebuilds++;
+            }
+            if (log_build && mLoggedBuilds < SNAPSHOT_BUILD_REPORT_LIMIT) {
+                EventLogTags.writePmSnapshotRebuild(nsToMs(duration), nsToMs(elapsed));
+                mLoggedBuilds++;
+            }
+
+            final long log_interval = now - mStatisticsSent;
+            if (log_interval >= SNAPSHOT_LOG_INTERVAL_NS) {
+                EventLogTags.writePmSnapshotStats(mRebuilds, mReused,
+                                                  mBigBuilds, mQuickRebuilds,
+                                                  nsToMs(mMaxBuildTimeNs),
+                                                  nsToMs(mBuildTimeNs));
+                mStatisticsSent = now;
+                mRebuilds = 0;
+                mReused = 0;
+                mBuildTimeNs = 0;
+                mMaxBuildTimeNs = 0;
+                mBigBuilds = 0;
+                mQuickRebuilds = 0;
+                mLoggedBuilds = 0;
+            }
+        }
+    }
+
+    // Snapshot statistics.
     @GuardedBy("mLock")
-    private int mSnapshotRebuilt = 0;
+    private final SnapshotStatistics mSnapshotStatistics = new SnapshotStatistics();
 
     // The snapshot disable/enable switch.  An image with the flag set true uses snapshots
     // and an image with the flag set false does not use snapshots.
@@ -4786,23 +4701,21 @@
             // yet invalidated the snapshot.  Always give the thread the live computer.
             return mLiveComputer;
         }
-        int hits = 0;
-        if (TRACE_CACHES) {
-            hits = mSnapshotHits.incrementAndGet();
-        }
         synchronized (mSnapshotLock) {
             Computer c = mSnapshotComputer;
             if (sSnapshotCorked && (c != null)) {
                 // Snapshots are corked, which means new ones should not be built right now.
                 return c;
             }
+            // Deliberately capture the value pre-increment
+            final int hits = mSnapshotHits++;
             if (sSnapshotInvalid || (c == null)) {
                 // The snapshot is invalid if it is marked as invalid or if it is null.  If it
                 // is null, then it is currently being rebuilt by rebuildSnapshot().
                 synchronized (mLock) {
                     // Rebuild the snapshot if it is invalid.  Note that the snapshot might be
                     // invalidated as it is rebuilt.  However, the snapshot is still
-                    // self-consistent (the lock is being held)and is current as of the time
+                    // self-consistent (the lock is being held) and is current as of the time
                     // this function is entered.
                     if (sSnapshotInvalid) {
                         rebuildSnapshot(hits);
@@ -4820,22 +4733,20 @@
     }
 
     /**
-     * Rebuild the cached computer.  mSnapshotComputer is temporarily set to null to block
-     * other threads from using the invalid computer until it is rebuilt.
+     * Rebuild the cached computer.  mSnapshotComputer is temporarily set to null to block other
+     * threads from using the invalid computer until it is rebuilt.
      */
     @GuardedBy("mLock")
     private void rebuildSnapshot(int hits) {
+        final long now = System.nanoTime();
         mSnapshotComputer = null;
         sSnapshotInvalid = false;
         final Snapshot args = new Snapshot(Snapshot.SNAPPED);
         mSnapshotComputer = new ComputerEngine(args);
+        final long done = System.nanoTime();
 
-        // Still guarded by mLock
-        final int run = hits - mSnapshotRebuilt;
-        mSnapshotRebuilt = hits;
-        if (TRACE_CACHES) {
-            Log.w(TAG, "computer: rebuild after " + run + " hits");
-        }
+        mSnapshotStatistics.rebuild(now, done, hits);
+        mSnapshotHits = 0;
     }
 
     /**
@@ -4852,8 +4763,8 @@
      * @hide
      */
     public static void onChange(@Nullable Watchable what) {
-        if (TRACE_CACHES) {
-            Log.e(TAG, "computer: onChange(" + what + ")");
+        if (TRACE_SNAPSHOTS) {
+            Log.e(TAG, "snapshot: onChange(" + what + ")");
         }
         sSnapshotInvalid = true;
     }
@@ -5179,55 +5090,6 @@
                     params.handleIntegrityVerificationFinished();
                     break;
                 }
-                case START_INTENT_FILTER_VERIFICATIONS: {
-                    IFVerificationParams params = (IFVerificationParams) msg.obj;
-                    verifyIntentFiltersIfNeeded(params.userId, params.verifierUid, params.replacing,
-                            params.packageName, params.hasDomainUrls, params.activities);
-                    break;
-                }
-                case INTENT_FILTER_VERIFIED: {
-                    final int verificationId = msg.arg1;
-
-                    final IntentFilterVerificationState state = mIntentFilterVerificationStates.get(
-                            verificationId);
-                    if (state == null) {
-                        Slog.w(TAG, "Invalid IntentFilter verification token "
-                                + verificationId + " received");
-                        break;
-                    }
-
-                    final int userId = state.getUserId();
-
-                    if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                            "Processing IntentFilter verification with token:"
-                            + verificationId + " and userId:" + userId);
-
-                    final IntentFilterVerificationResponse response =
-                            (IntentFilterVerificationResponse) msg.obj;
-
-                    state.setVerifierResponse(response.callerUid, response.code);
-
-                    if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                            "IntentFilter verification with token:" + verificationId
-                            + " and userId:" + userId
-                            + " is settings verifier response with response code:"
-                            + response.code);
-
-                    if (response.code == PackageManager.INTENT_FILTER_VERIFICATION_FAILURE) {
-                        if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Domains failing verification: "
-                                + response.getFailedDomainsString());
-                    }
-
-                    if (state.isVerificationComplete()) {
-                        mIntentFilterVerifier.receiveVerificationResponse(verificationId);
-                    } else {
-                        if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                                "IntentFilter verification with token:" + verificationId
-                                + " was not said to be complete");
-                    }
-
-                    break;
-                }
                 case INSTANT_APP_RESOLUTION_PHASE_TWO: {
                     InstantAppResolver.doInstantAppResolutionPhaseTwo(mContext,
                             mInstantAppResolverConnection,
@@ -5288,6 +5150,12 @@
                     }
                     break;
                 }
+                case DOMAIN_VERIFICATION: {
+                    int messageCode = msg.arg1;
+                    Object object = msg.obj;
+                    mDomainVerificationManager.runMessage(messageCode, object);
+                    break;
+                }
             }
         }
     }
@@ -5647,19 +5515,19 @@
     public void requestChecksums(@NonNull String packageName, boolean includeSplits,
             @Checksum.Type int optional,
             @Checksum.Type int required, @Nullable List trustedInstallers,
-            @NonNull IntentSender statusReceiver, int userId) {
+            @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId) {
         requestChecksumsInternal(packageName, includeSplits, optional, required, trustedInstallers,
-                statusReceiver, userId, mInjector.getBackgroundExecutor(),
+                onChecksumsReadyListener, userId, mInjector.getBackgroundExecutor(),
                 mInjector.getBackgroundHandler());
     }
 
     private void requestChecksumsInternal(@NonNull String packageName, boolean includeSplits,
-            @Checksum.Type int optional,
-            @Checksum.Type int required, @Nullable List trustedInstallers,
-            @NonNull IntentSender statusReceiver, int userId, @NonNull Executor executor,
-            @NonNull Handler handler) {
+            @Checksum.Type int optional, @Checksum.Type int required,
+            @Nullable List trustedInstallers,
+            @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId,
+            @NonNull Executor executor, @NonNull Handler handler) {
         Objects.requireNonNull(packageName);
-        Objects.requireNonNull(statusReceiver);
+        Objects.requireNonNull(onChecksumsReadyListener);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(handler);
 
@@ -5695,7 +5563,7 @@
                     () -> mInjector.getIncrementalManager(),
                     () -> mPmInternal);
             ApkChecksums.getChecksums(filesToChecksum, optional, required, installerPackageName,
-                    trustedCerts, statusReceiver, injector);
+                    trustedCerts, onChecksumsReadyListener, injector);
         });
     }
 
@@ -5854,7 +5722,8 @@
     }
 
     public static PackageManagerService main(Context context, Installer installer,
-            boolean factoryTest, boolean onlyCore) {
+            @NonNull DomainVerificationService domainVerificationService, boolean factoryTest,
+            boolean onlyCore) {
         // Self-check for initial settings.
         PackageManagerServiceCompilerMapping.checkProperties();
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
@@ -5877,7 +5746,8 @@
                         lock),
                 (i, pm) -> new Settings(Environment.getDataDirectory(),
                         RuntimePermissionsPersistence.createInstance(),
-                        i.getPermissionManagerServiceInternal(), lock),
+                        i.getPermissionManagerServiceInternal(),
+                        domainVerificationService, lock),
                 (i, pm) -> AppsFilter.create(pm.mPmInternal, i),
                 (i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"),
                 (i, pm) -> SystemConfig.getInstance(),
@@ -5910,6 +5780,13 @@
                         i.getContext(), cn, Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE),
                 (i, pm) -> new ModuleInfoProvider(i.getContext(), pm),
                 (i, pm) -> LegacyPermissionManagerService.create(i.getContext()),
+                (i, pm) -> domainVerificationService,
+                (i, pm) -> {
+                    HandlerThread thread = new ServiceThread(TAG,
+                            Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+                    thread.start();
+                    return pm.new PackageHandler(thread.getLooper());
+                },
                 new DefaultSystemWrapper(),
                 LocalServices::getService,
                 context::getSystemService);
@@ -6081,6 +5958,9 @@
         mPermissionManager = injector.getPermissionManagerServiceInternal();
         mSettings = injector.getSettings();
         mUserManager = injector.getUserManagerService();
+        mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
+        mHandler = injector.getHandler();
+
         mApexManager = testParams.apexManager;
         mArtManagerService = testParams.artManagerService;
         mAvailableFeatures = testParams.availableFeatures;
@@ -6090,14 +5970,11 @@
         mDexManager = testParams.dexManager;
         mDirsToScanAsSystem = testParams.dirsToScanAsSystem;
         mFactoryTest = testParams.factoryTest;
-        mHandler = testParams.handler;
         mIncrementalManager = testParams.incrementalManager;
         mInstallerService = testParams.installerService;
         mInstantAppRegistry = testParams.instantAppRegistry;
         mInstantAppResolverConnection = testParams.instantAppResolverConnection;
         mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
-        mIntentFilterVerifier = testParams.intentFilterVerifier;
-        mIntentFilterVerifierComponent = testParams.intentFilterVerifierComponent;
         mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
         mIsPreNUpgrade = testParams.isPreNupgrade;
         mIsPreQUpgrade = testParams.isPreQupgrade;
@@ -6251,7 +6128,7 @@
 
         if (separateProcesses != null && separateProcesses.length() > 0) {
             if ("*".equals(separateProcesses)) {
-                mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
+                mDefParseFlags = ParsingPackageUtils.PARSE_IGNORE_PROCESSES;
                 mSeparateProcesses = null;
                 Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
             } else {
@@ -6303,6 +6180,9 @@
         mAppInstallDir = new File(Environment.getDataDirectory(), "app");
         mAppLib32InstallDir = getAppLib32InstallDir();
 
+        mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
+        mDomainVerificationManager.setConnection(mDomainVerificationConnection);
+
         // Link up the watchers
         mPackages.registerObserver(mWatcher);
         mSharedLibraries.registerObserver(mWatcher);
@@ -6325,10 +6205,7 @@
         synchronized (mInstallLock) {
         // writer
         synchronized (mLock) {
-            HandlerThread handlerThread = new ServiceThread(TAG,
-                    Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
-            handlerThread.start();
-            mHandler = new PackageHandler(handlerThread.getLooper());
+            mHandler = injector.getHandler();
             mProcessLoggingHandler = new ProcessLoggingHandler();
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
 
@@ -6451,7 +6328,7 @@
                 scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
             }
 
-            final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
+            final int systemParseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
             final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
 
             PackageParser2 packageParser = injector.getScanningCachingPackageParser();
@@ -6813,7 +6690,6 @@
             if (!mOnlyCore && (mPromoteSystemApps || mFirstBoot)) {
                 for (UserInfo user : mInjector.getUserManagerInternal().getUsers(true)) {
                     mSettings.applyDefaultPreferredAppsLPw(user.id);
-                    primeDomainVerificationsLPw(user.id);
                 }
             }
 
@@ -6920,13 +6796,18 @@
                 mRequiredVerifierPackage = getRequiredButNotReallyRequiredVerifierLPr();
                 mRequiredInstallerPackage = getRequiredInstallerLPr();
                 mRequiredUninstallerPackage = getRequiredUninstallerLPr();
-                mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
-                if (mIntentFilterVerifierComponent != null) {
-                    mIntentFilterVerifier = new IntentVerifierProxy(mContext,
-                            mIntentFilterVerifierComponent);
-                } else {
-                    mIntentFilterVerifier = null;
-                }
+                ComponentName intentFilterVerifierComponent =
+                        getIntentFilterVerifierComponentNameLPr();
+                ComponentName domainVerificationAgent =
+                        getDomainVerificationAgentComponentNameLPr();
+
+                DomainVerificationProxy domainVerificationProxy = DomainVerificationProxy.makeProxy(
+                        intentFilterVerifierComponent, domainVerificationAgent, mContext,
+                        mDomainVerificationManager, mDomainVerificationManager.getCollector(),
+                        mDomainVerificationConnection);
+
+                mDomainVerificationManager.setProxy(domainVerificationProxy);
+
                 mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr();
                 mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
                         PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
@@ -6935,8 +6816,6 @@
                 mRequiredVerifierPackage = null;
                 mRequiredInstallerPackage = null;
                 mRequiredUninstallerPackage = null;
-                mIntentFilterVerifierComponent = null;
-                mIntentFilterVerifier = null;
                 mServicesExtensionPackageName = null;
                 mSharedSystemSharedLibraryPackageName = null;
             }
@@ -7091,7 +6970,7 @@
      */
     private boolean enableCompressedPackage(AndroidPackage stubPkg,
             @NonNull PackageSetting stubPkgSetting) {
-        final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
+        final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_CHATTY
                 | PackageParser.PARSE_ENFORCE_CODE;
         synchronized (mInstallLock) {
             final AndroidPackage pkg;
@@ -7471,6 +7350,40 @@
         return null;
     }
 
+    @Nullable
+    private ComponentName getDomainVerificationAgentComponentNameLPr() {
+        Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION);
+        List<ResolveInfo> matches = queryIntentReceiversInternal(intent, null,
+                MATCH_SYSTEM_ONLY | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                UserHandle.USER_SYSTEM, false /*allowDynamicSplits*/);
+        ResolveInfo best = null;
+        final int N = matches.size();
+        for (int i = 0; i < N; i++) {
+            final ResolveInfo cur = matches.get(i);
+            final String packageName = cur.getComponentInfo().packageName;
+            if (checkPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+                    packageName, UserHandle.USER_SYSTEM) != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Domain verification agent found but does not hold permission: "
+                        + packageName);
+                continue;
+            }
+
+            if (best == null || cur.priority > best.priority) {
+                if (cur.getComponentInfo().enabled) {
+                    best = cur;
+                } else {
+                    Slog.w(TAG, "Domain verification agent found but not enabled");
+                }
+            }
+        }
+
+        if (best != null) {
+            return best.getComponentInfo().getComponentName();
+        }
+        Slog.w(TAG, "Domain verification agent not found");
+        return null;
+    }
+
     @Override
     public @Nullable ComponentName getInstantAppResolverComponent() {
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
@@ -7604,60 +7517,6 @@
         return matches.get(0).getComponentInfo().getComponentName();
     }
 
-    @GuardedBy("mLock")
-    private void primeDomainVerificationsLPw(int userId) {
-        if (DEBUG_DOMAIN_VERIFICATION) {
-            Slog.d(TAG, "Priming domain verifications in user " + userId);
-        }
-
-        SystemConfig systemConfig = mInjector.getSystemConfig();
-        ArraySet<String> packages = systemConfig.getLinkedApps();
-
-        for (String packageName : packages) {
-            AndroidPackage pkg = mPackages.get(packageName);
-            if (pkg != null) {
-                if (!pkg.isSystem()) {
-                    Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>");
-                    continue;
-                }
-
-                ArraySet<String> domains = null;
-                for (ParsedActivity a : pkg.getActivities()) {
-                    for (ParsedIntentInfo filter : a.getIntents()) {
-                        if (hasValidDomains(filter)) {
-                            if (domains == null) {
-                                domains = new ArraySet<>();
-                            }
-                            domains.addAll(filter.getHostsList());
-                        }
-                    }
-                }
-
-                if (domains != null && domains.size() > 0) {
-                    if (DEBUG_DOMAIN_VERIFICATION) {
-                        Slog.v(TAG, "      + " + packageName);
-                    }
-                    // 'Undefined' in the global IntentFilterVerificationInfo, i.e. the usual
-                    // state w.r.t. the formal app-linkage "no verification attempted" state;
-                    // and then 'always' in the per-user state actually used for intent resolution.
-                    final IntentFilterVerificationInfo ivi;
-                    ivi = mSettings.createIntentFilterVerificationIfNeededLPw(packageName, domains);
-                    ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
-                    mSettings.updateIntentFilterVerificationStatusLPw(packageName,
-                            INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, userId);
-                } else {
-                    Slog.w(TAG, "Sysconfig <app-link> package '" + packageName
-                            + "' does not handle web links");
-                }
-            } else {
-                Slog.w(TAG, "Unknown package " + packageName + " in sysconfig <app-link>");
-            }
-        }
-
-        scheduleWritePackageRestrictionsLocked(userId);
-        scheduleWriteSettingsLocked();
-    }
-
     private boolean packageIsBrowser(String packageName, int userId) {
         List<ResolveInfo> list = queryIntentActivitiesInternal(sBrowserIntent, null,
                 PackageManager.MATCH_ALL, userId);
@@ -9081,6 +8940,7 @@
 
     @Override
     public List<String> getAllPackages() {
+        enforceSystemOrRootOrShell("getAllPackages is limited to privileged callers");
         final int callingUid = Binder.getCallingUid();
         final int callingUserId = UserHandle.getUserId(callingUid);
         synchronized (mLock) {
@@ -9503,9 +9363,8 @@
                     if (ri.activityInfo.applicationInfo.isInstantApp()) {
                         final String packageName = ri.activityInfo.packageName;
                         final PackageSetting ps = mSettings.getPackageLPr(packageName);
-                        final long packedStatus = getDomainVerificationStatusLPr(ps, userId);
-                        final int status = (int)(packedStatus >> 32);
-                        if (status != INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+                        if (ps != null && mDomainVerificationManager
+                                .isApprovedForDomain(ps, intent, userId)) {
                             return ri;
                         }
                     }
@@ -9997,8 +9856,7 @@
     private static class CrossProfileDomainInfo {
         /* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
         ResolveInfo resolveInfo;
-        /* Best domain verification status of the activities found in the other profile */
-        int bestDomainVerificationStatus;
+        boolean wereAnyDomainsVerificationApproved;
     }
 
     private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
@@ -10084,13 +9942,6 @@
             xpDomainInfo, userId, debug);
     }
 
-    // Returns a packed value as a long:
-    //
-    // high 'int'-sized word: link status: undefined/ask/never/always.
-    // low 'int'-sized word: relative priority among 'always' results.
-    private long getDomainVerificationStatusLPr(PackageSetting ps, int userId) {
-        return liveComputer().getDomainVerificationStatusLPr(ps, userId);
-    }
 
     private ResolveInfo querySkipCurrentProfileIntents(
             List<CrossProfileIntentFilter> matchingFilters, Intent intent, String resolvedType,
@@ -11317,7 +11168,8 @@
             @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user)
                     throws PackageManagerException {
-        final boolean scanSystemPartition = (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0;
+        final boolean scanSystemPartition =
+                (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
         final String renamedPkgName;
         final PackageSetting disabledPkgSetting;
         final boolean isSystemPkgUpdated;
@@ -11360,7 +11212,7 @@
                             0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
                     : null;
             if (DEBUG_PACKAGE_SCANNING
-                    && (parseFlags & PackageParser.PARSE_CHATTY) != 0
+                    && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0
                     && sharedUserSetting != null) {
                 Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
                         + " (uid=" + sharedUserSetting.userId + "):"
@@ -13179,10 +13031,11 @@
                 sharedUserSetting = mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
                         0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
                 if (DEBUG_PACKAGE_SCANNING) {
-                    if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
+                    if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
                         Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
                                 + " (uid=" + sharedUserSetting.userId + "):"
                                 + " packages=" + sharedUserSetting.packages);
+                    }
                 }
             }
             String platformPackageName = mPlatformPackage == null
@@ -13338,8 +13191,8 @@
 
         final int userId = user == null ? 0 : user.getIdentifier();
         // Modify state for the given package setting
-        commitPackageSettings(pkg, oldPkg, pkgSetting, scanFlags,
-                (parseFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
+        commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
+                (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
         if (pkgSetting.getInstantApp(userId)) {
             mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
         }
@@ -13548,8 +13401,9 @@
         List<String> changedAbiCodePath = null;
 
         if (DEBUG_PACKAGE_SCANNING) {
-            if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
+            if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
                 Log.d(TAG, "Scanning package " + parsedPackage.getPackageName());
+            }
         }
 
         // Initialize package source and resource directories
@@ -13594,6 +13448,9 @@
             usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
             parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
         }
+
+        final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();
+
         // TODO(b/135203078): Remove appInfoFlag usage in favor of individually assigned booleans
         //  to avoid adding something that's unsupported due to lack of state, since it's called
         //  with null.
@@ -13617,7 +13474,8 @@
                     parsedPackage.getVersionCode(), pkgFlags, pkgPrivateFlags, user,
                     true /*allowInstall*/, instantApp, virtualPreload,
                     UserManagerService.getInstance(), usesStaticLibraries,
-                    parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups());
+                    parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
+                    newDomainSetId);
         } else {
             // make a deep copy to avoid modifying any existing system state.
             pkgSetting = new PackageSetting(pkgSetting);
@@ -13636,7 +13494,7 @@
                     PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
                     UserManagerService.getInstance(),
                     usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
-                    parsedPackage.getMimeGroups());
+                    parsedPackage.getMimeGroups(), newDomainSetId);
         }
         if (createNewPackage && originalPkgSetting != null) {
             // This is the initial transition from the original package, so,
@@ -13816,7 +13674,7 @@
         } else if (pkgSetting.firstInstallTime == 0) {
             // We need *something*.  Take time time stamp of the file.
             pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
-        } else if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+        } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) {
             if (scanFileTime != pkgSetting.timeStamp) {
                 // A package on the system image has changed; consider this
                 // to be an update.
@@ -14013,7 +13871,7 @@
     private void assertPackageIsValid(AndroidPackage pkg, final @ParseFlags int parseFlags,
             final @ScanFlags int scanFlags)
                     throws PackageManagerException {
-        if ((parseFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
+        if ((parseFlags & ParsingPackageUtils.PARSE_ENFORCE_CODE) != 0) {
             assertCodePolicy(pkg);
         }
 
@@ -14270,7 +14128,7 @@
                 if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
                     // We are scanning a system overlay. This can be the first scan of the
                     // system/vendor/oem partition, or an update to the system overlay.
-                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         // This must be an update to a system overlay. Immutable overlays cannot be
                         // upgraded.
                         Objects.requireNonNull(mOverlayConfig,
@@ -14350,7 +14208,7 @@
 
             // If the package is not on a system partition ensure it is signed with at least the
             // minimum signature scheme version required for its target SDK.
-            if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+            if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                 int minSignatureSchemeVersion =
                         ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
                                 pkg.getTargetSdkVersion());
@@ -14481,8 +14339,8 @@
      * Adds a scanned package to the system. When this method is finished, the package will
      * be available for query, resolution, etc...
      */
-    private void commitPackageSettings(AndroidPackage pkg,
-            @Nullable AndroidPackage oldPkg, PackageSetting pkgSetting,
+    private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
+            @NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
             final @ScanFlags int scanFlags, boolean chatty, ReconciledPackage reconciledPkg) {
         final String pkgName = pkg.getPackageName();
         if (mCustomResolverComponentName != null &&
@@ -14605,6 +14463,12 @@
             mAppsFilter.addPackage(pkgSetting, isReplace);
             mPackageProperty.addAllProperties(pkg);
 
+            if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
+                mDomainVerificationManager.addPackage(pkgSetting);
+            } else {
+                mDomainVerificationManager.migrateState(oldPkgSetting, pkgSetting);
+            }
+
             int collectionSize = ArrayUtils.size(pkg.getInstrumentations());
             StringBuilder r = null;
             int i;
@@ -16277,77 +16141,31 @@
         return DEFAULT_INTEGRITY_VERIFY_ENABLE;
     }
 
+    @Deprecated
     @Override
-    public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains)
-            throws RemoteException {
-        mContext.enforceCallingOrSelfPermission(
-                Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
-                "Only intentfilter verification agents can verify applications");
-
-        final Message msg = mHandler.obtainMessage(INTENT_FILTER_VERIFIED);
-        final IntentFilterVerificationResponse response = new IntentFilterVerificationResponse(
-                Binder.getCallingUid(), verificationCode, failedDomains);
-        msg.arg1 = id;
-        msg.obj = response;
-        mHandler.sendMessage(msg);
+    public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) {
+        DomainVerificationProxyV1.queueLegacyVerifyResult(mContext, mDomainVerificationConnection,
+                id, verificationCode, failedDomains, Binder.getCallingUid());
     }
 
+    @Deprecated
     @Override
     public int getIntentVerificationStatus(String packageName, int userId) {
-        final int callingUid = Binder.getCallingUid();
-        if (UserHandle.getUserId(callingUid) != userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "getIntentVerificationStatus" + userId);
-        }
-        if (getInstantAppPackageName(callingUid) != null) {
-            return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-        }
-        synchronized (mLock) {
-            final PackageSetting ps = mSettings.getPackageLPr(packageName);
-            if (ps == null
-                    || shouldFilterApplicationLocked(
-                    ps, callingUid, UserHandle.getUserId(callingUid))) {
-                return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-            }
-            return mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
-        }
+        return mDomainVerificationManager.getLegacyState(packageName, userId);
     }
 
+    @Deprecated
     @Override
     public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
-
-        boolean result = false;
-        synchronized (mLock) {
-            final PackageSetting ps = mSettings.getPackageLPr(packageName);
-            if (shouldFilterApplicationLocked(
-                    ps, Binder.getCallingUid(), UserHandle.getCallingUserId())) {
-                return false;
-            }
-            result = mSettings.updateIntentFilterVerificationStatusLPw(packageName, status, userId);
-        }
-        if (result) {
-            scheduleWritePackageRestrictionsLocked(userId);
-        }
-        return result;
+        mDomainVerificationManager.setLegacyUserState(packageName, userId, status);
+        return true;
     }
 
+    @Deprecated
     @Override
     public @NonNull ParceledListSlice<IntentFilterVerificationInfo> getIntentFilterVerifications(
             String packageName) {
-        final int callingUid = Binder.getCallingUid();
-        if (getInstantAppPackageName(callingUid) != null) {
-            return ParceledListSlice.emptyList();
-        }
-        synchronized (mLock) {
-            final PackageSetting ps = mSettings.getPackageLPr(packageName);
-            if (shouldFilterApplicationLocked(ps, callingUid, UserHandle.getUserId(callingUid))) {
-                return ParceledListSlice.emptyList();
-            }
-            return new ParceledListSlice<>(mSettings.getIntentFilterVerificationsLPr(packageName));
-        }
+        return ParceledListSlice.emptyList();
     }
 
     @Override
@@ -18001,13 +17819,15 @@
                 return false;
             }
 
-            String codePath = codeFile.getAbsolutePath();
-            if (mIncrementalManager != null && isIncrementalPath(codePath)) {
-                mIncrementalManager.onPackageRemoved(codePath);
-            }
+            final boolean isIncremental = (mIncrementalManager != null && isIncrementalPath(
+                    codeFile.getAbsolutePath()));
 
             removeCodePathLI(codeFile);
 
+            if (isIncremental) {
+                mIncrementalManager.onPackageRemoved(codeFile);
+            }
+
             return true;
         }
 
@@ -18015,11 +17835,12 @@
             // Try enumerating all code paths before deleting
             List<String> allCodePaths = Collections.EMPTY_LIST;
             if (codeFile != null && codeFile.exists()) {
-                try {
-                    final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0);
-                    allCodePaths = pkg.getAllCodePaths();
-                } catch (PackageParserException e) {
-                    // Ignored; we tried our best
+                final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+                final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                        input.reset(), codeFile, /* flags */ 0);
+                if (result.isSuccess()) {
+                    // Ignore error; we tried our best
+                    allCodePaths = result.getResult().getAllApkPaths();
                 }
             }
 
@@ -18637,7 +18458,7 @@
                     // We just determined the app is signed correctly, so bring
                     // over the latest parsed certs.
                 } else {
-                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                 "Package " + parsedPackage.getPackageName()
                                         + " upgrade keys do not match the previously installed"
@@ -18654,9 +18475,11 @@
                     final VersionInfo versionInfo = request.versionInfos.get(installPackageName);
                     final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
                     final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
+                    final boolean isRollback = installArgs != null
+                            && installArgs.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
                     final boolean compatMatch = verifySignatures(signatureCheckPs,
                             disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
-                            compareRecover);
+                            compareRecover, isRollback);
                     // The new KeySets will be re-added later in the scanning process.
                     if (compatMatch) {
                         removeAppKeySetData = true;
@@ -18687,7 +18510,7 @@
                         }
                     }
                 } catch (PackageManagerException e) {
-                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw new ReconcileFailure(e);
                     }
                     signingDetails = parsedPackage.getSigningDetails();
@@ -18766,7 +18589,8 @@
             // apps are scanned to avoid dependency based scanning.
             final ScanResult scanResult = scannedPackages.get(installPackageName);
             if ((scanResult.request.scanFlags & SCAN_BOOTING) != 0
-                    || (scanResult.request.parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+                    || (scanResult.request.parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+                    != 0) {
                 continue;
             }
             try {
@@ -19337,6 +19161,8 @@
                     extras, 0 /*flags*/,
                     null /*targetPackage*/, null /*finishedReceiver*/,
                     mInstalledUserIds, null /* instantUserIds */, newBroadcastAllowList, null);
+            // Unregister progress listener
+            mIncrementalManager.unregisterLoadingProgressCallbacks(codePath);
             // Unregister health listener as it will always be healthy from now
             mIncrementalManager.unregisterHealthListener(codePath);
         }
@@ -19625,6 +19451,7 @@
         final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0);
         final boolean virtualPreload =
                 ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
+        final boolean isRollback = args.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
         @ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
         if (args.move != null) {
             // moving a complete application; perform an initial scan on the new install location
@@ -19652,9 +19479,9 @@
         }
 
         // Retrieve PackageSettings and parse package
-        @ParseFlags final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
-                | PackageParser.PARSE_ENFORCE_CODE
-                | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
+        @ParseFlags final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_CHATTY
+                | ParsingPackageUtils.PARSE_ENFORCE_CODE
+                | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0);
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
         final ParsedPackage parsedPackage;
@@ -19805,7 +19632,8 @@
                                 parsedPackage);
                         // We don't care about disabledPkgSetting on install for now.
                         final boolean compatMatch = verifySignatures(signatureCheckPs, null,
-                                parsedPackage.getSigningDetails(), compareCompat, compareRecover);
+                                parsedPackage.getSigningDetails(), compareCompat, compareRecover,
+                                isRollback);
                         // The new KeySets will be re-added later in the scanning process.
                         if (compatMatch) {
                             synchronized (mLock) {
@@ -20022,13 +19850,6 @@
                     "Failed to set up verity: " + e);
         }
 
-        if (!instantApp) {
-            startIntentFilterVerifications(args.user.getIdentifier(), replace, parsedPackage);
-        } else {
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.d(TAG, "Not verifying instant app install for app links: " + pkgName);
-            }
-        }
         final PackageFreezer freezer =
                 freezePackageForInstall(pkgName, installFlags, "installPackageLI");
         boolean shouldCloseFreezerBeforeReturn = true;
@@ -20082,15 +19903,23 @@
                                             + pkgName11);
                         }
                     } else {
+                        SigningDetails parsedPkgSigningDetails = parsedPackage.getSigningDetails();
+                        SigningDetails oldPkgSigningDetails = oldPackage.getSigningDetails();
                         // default to original signature matching
-                        if (!parsedPackage.getSigningDetails().checkCapability(
-                                oldPackage.getSigningDetails(),
+                        if (!parsedPkgSigningDetails.checkCapability(oldPkgSigningDetails,
                                 SigningDetails.CertCapabilities.INSTALLED_DATA)
-                                && !oldPackage.getSigningDetails().checkCapability(
-                                parsedPackage.getSigningDetails(),
+                                && !oldPkgSigningDetails.checkCapability(parsedPkgSigningDetails,
                                 SigningDetails.CertCapabilities.ROLLBACK)) {
-                            throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                    "New package has a different signature: " + pkgName11);
+                            // Allow the update to proceed if this is a rollback and the parsed
+                            // package's current signing key is the current signer or in the lineage
+                            // of the old package; this allows a rollback to a previously installed
+                            // version after an app's signing key has been rotated without requiring
+                            // the rollback capability on the previous signing key.
+                            if (!isRollback || !oldPkgSigningDetails.hasAncestorOrSelf(
+                                    parsedPkgSigningDetails)) {
+                                throw new PrepareFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                        "New package has a different signature: " + pkgName11);
+                            }
                         }
                     }
 
@@ -20354,190 +20183,6 @@
         }
     }
 
-    private void startIntentFilterVerifications(int userId, boolean replacing, AndroidPackage pkg) {
-        if (mIntentFilterVerifierComponent == null) {
-            Slog.w(TAG, "No IntentFilter verification will not be done as "
-                    + "there is no IntentFilterVerifier available!");
-            return;
-        }
-
-        final int verifierUid = getPackageUid(
-                mIntentFilterVerifierComponent.getPackageName(),
-                MATCH_DEBUG_TRIAGED_MISSING,
-                (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
-
-        Message msg = mHandler.obtainMessage(START_INTENT_FILTER_VERIFICATIONS);
-        msg.obj = new IFVerificationParams(
-                pkg.getPackageName(),
-                pkg.isHasDomainUrls(),
-                pkg.getActivities(),
-                replacing,
-                userId,
-                verifierUid
-        );
-        mHandler.sendMessage(msg);
-    }
-
-    private void verifyIntentFiltersIfNeeded(int userId, int verifierUid, boolean replacing,
-            String packageName,
-            boolean hasDomainUrls,
-            List<ParsedActivity> activities) {
-        int size = activities.size();
-        if (size == 0) {
-            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                    "No activity, so no need to verify any IntentFilter!");
-            return;
-        }
-
-        if (!hasDomainUrls) {
-            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                    "No domain URLs, so no need to verify any IntentFilter!");
-            return;
-        }
-
-        if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Checking for userId:" + userId
-                + " if any IntentFilter from the " + size
-                + " Activities needs verification ...");
-
-        int count = 0;
-        boolean handlesWebUris = false;
-        ArraySet<String> domains = new ArraySet<>();
-        final boolean previouslyVerified;
-        boolean hostSetExpanded = false;
-        boolean needToRunVerify = false;
-        synchronized (mLock) {
-            // If this is a new install and we see that we've already run verification for this
-            // package, we have nothing to do: it means the state was restored from backup.
-            IntentFilterVerificationInfo ivi =
-                    mSettings.getIntentFilterVerificationLPr(packageName);
-            previouslyVerified = (ivi != null);
-            if (!replacing && previouslyVerified) {
-                if (DEBUG_DOMAIN_VERIFICATION) {
-                    Slog.i(TAG, "Package " + packageName + " already verified: status="
-                            + ivi.getStatusString());
-                }
-                return;
-            }
-
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.i(TAG, "    Previous verified hosts: "
-                        + (ivi == null ? "[none]" : ivi.getDomainsString()));
-            }
-
-            // If any filters need to be verified, then all need to be.  In addition, we need to
-            // know whether an updating app has any web navigation intent filters, to re-
-            // examine handling policy even if not re-verifying.
-            final boolean needsVerification = needsNetworkVerificationLPr(packageName);
-            for (ParsedActivity a : activities) {
-                for (ParsedIntentInfo filter : a.getIntents()) {
-                    if (filter.handlesWebUris(true)) {
-                        handlesWebUris = true;
-                    }
-                    if (needsVerification && filter.needsVerification()) {
-                        if (DEBUG_DOMAIN_VERIFICATION) {
-                            Slog.d(TAG, "autoVerify requested, processing all filters");
-                        }
-                        needToRunVerify = true;
-                        // It's safe to break out here because filter.needsVerification()
-                        // can only be true if filter.handlesWebUris(true) returned true, so
-                        // we've already noted that.
-                        break;
-                    }
-                }
-            }
-
-            // Compare the new set of recognized hosts if the app is either requesting
-            // autoVerify or has previously used autoVerify but no longer does.
-            if (needToRunVerify || previouslyVerified) {
-                final int verificationId = mIntentFilterVerificationToken++;
-                for (ParsedActivity a : activities) {
-                    for (ParsedIntentInfo filter : a.getIntents()) {
-                        // Run verification against hosts mentioned in any web-nav intent filter,
-                        // even if the filter matches non-web schemes as well
-                        if (filter.handlesWebUris(false /*onlyWebSchemes*/)) {
-                            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
-                                    "Verification needed for IntentFilter:" + filter.toString());
-                            mIntentFilterVerifier.addOneIntentFilterVerification(
-                                    verifierUid, userId, verificationId, filter, packageName);
-                            domains.addAll(filter.getHostsList());
-                            count++;
-                        }
-                    }
-                }
-            }
-
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.i(TAG, "    Update published hosts: " + domains.toString());
-            }
-
-            // If we've previously verified this same host set (or a subset), we can trust that
-            // a current ALWAYS policy is still applicable.  If this is the case, we're done.
-            // (If we aren't in ALWAYS, we want to reverify to allow for apps that had failing
-            // hosts in their intent filters, then pushed a new apk that removed them and now
-            // passes.)
-            //
-            // Cases:
-            //   + still autoVerify (needToRunVerify):
-            //      - preserve current state if all of: unexpanded, in always
-            //      - otherwise rerun as usual (fall through)
-            //   + no longer autoVerify (alreadyVerified && !needToRunVerify)
-            //      - wipe verification history always
-            //      - preserve current state if all of: unexpanded, in always
-            hostSetExpanded = !previouslyVerified
-                    || (ivi != null && !ivi.getDomains().containsAll(domains));
-            final int currentPolicy =
-                    mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
-            final boolean keepCurState = !hostSetExpanded
-                    && currentPolicy == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-
-            if (needToRunVerify && keepCurState) {
-                if (DEBUG_DOMAIN_VERIFICATION) {
-                    Slog.i(TAG, "Host set not expanding + ALWAYS -> no need to reverify");
-                }
-                ivi.setDomains(domains);
-                scheduleWriteSettingsLocked();
-                return;
-            } else if (previouslyVerified && !needToRunVerify) {
-                // Prior autoVerify state but not requesting it now.  Clear autoVerify history,
-                // and preserve the always policy iff the host set is not expanding.
-                clearIntentFilterVerificationsLPw(packageName, userId, !keepCurState);
-                return;
-            }
-        }
-
-        if (needToRunVerify && count > 0) {
-            // app requested autoVerify and has at least one matching intent filter
-            if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Starting " + count
-                    + " IntentFilter verification" + (count > 1 ? "s" : "")
-                    +  " for userId:" + userId);
-            mIntentFilterVerifier.startVerifications(userId);
-        } else {
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.d(TAG, "No web filters or no new host policy for " + packageName);
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private boolean needsNetworkVerificationLPr(String packageName) {
-        IntentFilterVerificationInfo ivi = mSettings.getIntentFilterVerificationLPr(
-                packageName);
-        if (ivi == null) {
-            return true;
-        }
-        int status = ivi.getStatus();
-        switch (status) {
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
-                return true;
-
-            default:
-                // Nothing to do
-                return false;
-        }
-    }
-
     private static boolean isExternal(PackageSetting ps) {
         return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
@@ -21207,7 +20852,7 @@
             if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
                 final SparseBooleanArray changedUsers = new SparseBooleanArray();
                 synchronized (mLock) {
-                    clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL, true);
+                    mDomainVerificationManager.clearPackage(deletedPs.name);
                     clearDefaultBrowserIfNeeded(packageName);
                     mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
                     mAppsFilter.removePackage(getPackageSetting(packageName));
@@ -21384,8 +21029,8 @@
         final File codePath = new File(codePathString);
         @ParseFlags int parseFlags =
                 mDefParseFlags
-                | PackageParser.PARSE_MUST_BE_APK
-                | PackageParser.PARSE_IS_SYSTEM_DIR;
+                | ParsingPackageUtils.PARSE_MUST_BE_APK
+                | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
         @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
         for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
             ScanPartition partition = mDirsToScanAsSystem.get(i);
@@ -21734,8 +21379,6 @@
                     null /*lastDisableAppCaller*/,
                     null /*enabledComponents*/,
                     null /*disabledComponents*/,
-                    ps.readUserState(nextUserId).domainVerificationStatus,
-                    0 /*linkGeneration*/,
                     PackageManager.INSTALL_REASON_UNKNOWN,
                     PackageManager.UNINSTALL_REASON_UNKNOWN,
                     null /*harmfulAppWarning*/);
@@ -22285,40 +21928,6 @@
         mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId);
     }
 
-    /** This method takes a specific user id as well as UserHandle.USER_ALL. */
-    @GuardedBy("mLock")
-    private void clearIntentFilterVerificationsLPw(int userId) {
-        final int packageCount = mPackages.size();
-        for (int i = 0; i < packageCount; i++) {
-            AndroidPackage pkg = mPackages.valueAt(i);
-            clearIntentFilterVerificationsLPw(pkg.getPackageName(), userId, true);
-        }
-    }
-
-    /** This method takes a specific user id as well as UserHandle.USER_ALL. */
-    @GuardedBy("mLock")
-    void clearIntentFilterVerificationsLPw(String packageName, int userId,
-            boolean alsoResetStatus) {
-        if (SystemConfig.getInstance().getLinkedApps().contains(packageName)) {
-            // Nope, need to preserve the system configuration approval for this app
-            return;
-        }
-
-        if (userId == UserHandle.USER_ALL) {
-            if (mSettings.removeIntentFilterVerificationLPw(packageName,
-                    mUserManager.getUserIds())) {
-                for (int oneUserId : mUserManager.getUserIds()) {
-                    scheduleWritePackageRestrictionsLocked(oneUserId);
-                }
-            }
-        } else {
-            if (mSettings.removeIntentFilterVerificationLPw(packageName, userId,
-                    alsoResetStatus)) {
-                scheduleWritePackageRestrictionsLocked(userId);
-            }
-        }
-    }
-
     /** Clears state for all users, and touches intent filter verification policy */
     void clearDefaultBrowserIfNeeded(String packageName) {
         for (int oneUserId : mUserManager.getUserIds()) {
@@ -22374,8 +21983,7 @@
             }
             synchronized (mLock) {
                 mSettings.applyDefaultPreferredAppsLPw(userId);
-                clearIntentFilterVerificationsLPw(userId);
-                primeDomainVerificationsLPw(userId);
+                mDomainVerificationManager.clearUser(userId);
                 final int numPackages = mPackages.size();
                 for (int i = 0; i < numPackages; i++) {
                     final AndroidPackage pkg = mPackages.valueAt(i);
@@ -22637,28 +22245,8 @@
             throw new SecurityException("Only the system may call getIntentFilterVerificationBackup()");
         }
 
-        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
-        try {
-            final TypedXmlSerializer serializer = Xml.newFastSerializer();
-            serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
-            serializer.startDocument(null, true);
-            serializer.startTag(null, TAG_INTENT_FILTER_VERIFICATION);
-
-            synchronized (mLock) {
-                mSettings.writeAllDomainVerificationsLPr(serializer, userId);
-            }
-
-            serializer.endTag(null, TAG_INTENT_FILTER_VERIFICATION);
-            serializer.endDocument();
-            serializer.flush();
-        } catch (Exception e) {
-            if (DEBUG_BACKUP) {
-                Slog.e(TAG, "Unable to write default apps for backup", e);
-            }
-            return null;
-        }
-
-        return dataStream.toByteArray();
+        // TODO(b/170746586)
+        return null;
     }
 
     @Override
@@ -22666,22 +22254,7 @@
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
             throw new SecurityException("Only the system may call restorePreferredActivities()");
         }
-
-        try {
-            final TypedXmlPullParser parser = Xml.newFastPullParser();
-            parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
-            restoreFromXml(parser, userId, TAG_INTENT_FILTER_VERIFICATION,
-                    (parser1, userId1) -> {
-                        synchronized (mLock) {
-                            mSettings.readAllDomainVerificationsLPr(parser1, userId1);
-                            writeSettingsLPrTEMP();
-                        }
-                    });
-        } catch (Exception e) {
-            if (DEBUG_BACKUP) {
-                Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
-            }
-        }
+        // TODO(b/170746586)
     }
 
     @Override
@@ -23934,8 +23507,8 @@
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
             ResultReceiver resultReceiver) {
-        (new PackageManagerShellCommand(this, mContext)).exec(
-                this, in, out, err, args, callback, resultReceiver);
+        (new PackageManagerShellCommand(this, mContext,mDomainVerificationManager.getShell()))
+                .exec(this, in, out, err, args, callback, resultReceiver);
     }
 
     @SuppressWarnings("resource")
@@ -24117,9 +23690,8 @@
                 dumpState.setDump(DumpState.DUMP_MESSAGES);
             } else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_VERIFIERS);
-            } else if ("i".equals(cmd) || "ifv".equals(cmd)
-                    || "intent-filter-verifiers".equals(cmd)) {
-                dumpState.setDump(DumpState.DUMP_INTENT_FILTER_VERIFIERS);
+            } else if ("dv".equals(cmd) || "domain-verifier".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_DOMAIN_VERIFIER);
             } else if ("version".equals(cmd)) {
                 dumpState.setDump(DumpState.DUMP_VERSION);
             } else if ("k".equals(cmd) || "keysets".equals(cmd)) {
@@ -24213,14 +23785,16 @@
                 }
             }
 
-            if (dumpState.isDumping(DumpState.DUMP_INTENT_FILTER_VERIFIERS) &&
+            if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER) &&
                     packageName == null) {
-                if (mIntentFilterVerifierComponent != null) {
-                    String verifierPackageName = mIntentFilterVerifierComponent.getPackageName();
+                DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
+                ComponentName verifierComponent = proxy.getComponentName();
+                if (verifierComponent != null) {
+                    String verifierPackageName = verifierComponent.getPackageName();
                     if (!checkin) {
                         if (dumpState.onTitlePrinted())
                             pw.println();
-                        pw.println("Intent Filter Verifier:");
+                        pw.println("Domain Verifier:");
                         pw.print("  Using: ");
                         pw.print(verifierPackageName);
                         pw.print(" (uid=");
@@ -24228,14 +23802,14 @@
                                 UserHandle.USER_SYSTEM));
                         pw.println(")");
                     } else if (verifierPackageName != null) {
-                        pw.print("ifv,"); pw.print(verifierPackageName);
+                        pw.print("dv,"); pw.print(verifierPackageName);
                         pw.print(",");
                         pw.println(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING,
                                 UserHandle.USER_SYSTEM));
                     }
                 } else {
                     pw.println();
-                    pw.println("No Intent Filter Verifier available!");
+                    pw.println("No Domain Verifier available!");
                 }
             }
 
@@ -24352,63 +23926,20 @@
                 }
             }
 
-            if (!checkin
-                    && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)
-                    && packageName == null) {
-                pw.println();
-                int count = mSettings.getPackagesLocked().size();
-                if (count == 0) {
-                    pw.println("No applications!");
-                    pw.println();
-                } else {
-                    final String prefix = "  ";
-                    Collection<PackageSetting> allPackageSettings =
-                            mSettings.getPackagesLocked().values();
-                    if (allPackageSettings.size() == 0) {
-                        pw.println("No domain preferred apps!");
-                        pw.println();
-                    } else {
-                        pw.println("App verification status:");
-                        pw.println();
-                        count = 0;
-                        for (PackageSetting ps : allPackageSettings) {
-                            IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
-                            if (ivi == null || ivi.getPackageName() == null) continue;
-                            pw.println(prefix + "Package: " + ivi.getPackageName());
-                            pw.println(prefix + "Domains: " + ivi.getDomainsString());
-                            pw.println(prefix + "Status:  " + ivi.getStatusString());
-                            pw.println();
-                            count++;
-                        }
-                        if (count == 0) {
-                            pw.println(prefix + "No app verification established.");
-                            pw.println();
-                        }
-                        for (int userId : mUserManager.getUserIds()) {
-                            pw.println("App linkages for user " + userId + ":");
-                            pw.println();
-                            count = 0;
-                            for (PackageSetting ps : allPackageSettings) {
-                                final long status = ps.getDomainVerificationStatusForUser(userId);
-                                if (status >> 32 == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
-                                        && !DEBUG_DOMAIN_VERIFICATION) {
-                                    continue;
-                                }
-                                pw.println(prefix + "Package: " + ps.name);
-                                pw.println(prefix + "Domains: " + dumpDomainString(ps.name));
-                                String statusStr = IntentFilterVerificationInfo.
-                                        getStatusStringFromValue(status);
-                                pw.println(prefix + "Status:  " + statusStr);
-                                pw.println();
-                                count++;
-                            }
-                            if (count == 0) {
-                                pw.println(prefix + "No configured app linkages.");
-                                pw.println();
-                            }
-                        }
-                    }
+            if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) {
+                android.util.IndentingPrintWriter writer =
+                        new android.util.IndentingPrintWriter(pw);
+                if (dumpState.onTitlePrinted()) pw.println();
+
+                writer.println("Domain verification status:");
+                writer.increaseIndent();
+                try {
+                    mDomainVerificationManager.printState(writer, packageName, UserHandle.USER_ALL);
+                } catch (PackageManager.NameNotFoundException e) {
+                    pw.println("Failure printing domain verification information");
+                    Slog.e(TAG, "Failure printing domain verification information", e);
                 }
+                writer.decreaseIndent();
             }
 
             if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
@@ -24597,8 +24128,10 @@
                             UserHandle.USER_SYSTEM));
             proto.end(requiredVerifierPackageToken);
 
-            if (mIntentFilterVerifierComponent != null) {
-                String verifierPackageName = mIntentFilterVerifierComponent.getPackageName();
+            DomainVerificationProxy proxy = mDomainVerificationManager.getProxy();
+            ComponentName verifierComponent = proxy.getComponentName();
+            if (verifierComponent != null) {
+                String verifierPackageName = verifierComponent.getPackageName();
                 final long verifierPackageToken =
                         proto.start(PackageServiceDumpProto.VERIFIER_PACKAGE);
                 proto.write(PackageServiceDumpProto.PackageShortProto.NAME, verifierPackageName);
@@ -24723,35 +24256,6 @@
         }
     }
 
-    private String dumpDomainString(String packageName) {
-        List<IntentFilterVerificationInfo> iviList = getIntentFilterVerifications(packageName)
-                .getList();
-        List<IntentFilter> filters = getAllIntentFilters(packageName).getList();
-
-        ArraySet<String> result = new ArraySet<>();
-        if (iviList.size() > 0) {
-            for (IntentFilterVerificationInfo ivi : iviList) {
-                result.addAll(ivi.getDomains());
-            }
-        }
-        if (filters != null && filters.size() > 0) {
-            for (IntentFilter filter : filters) {
-                if (filter.hasCategory(Intent.CATEGORY_BROWSABLE)
-                        && (filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
-                                filter.hasDataScheme(IntentFilter.SCHEME_HTTPS))) {
-                    result.addAll(filter.getHostsList());
-                }
-            }
-        }
-
-        StringBuilder sb = new StringBuilder(result.size() * 16);
-        for (String domain : result) {
-            if (sb.length() > 0) sb.append(" ");
-            sb.append(domain);
-        }
-        return sb.toString();
-    }
-
     // ------- apps on sdcard specific code -------
     static final boolean DEBUG_SD_INSTALL = false;
 
@@ -24837,7 +24341,7 @@
 
         final ArrayList<PackageFreezer> freezers = new ArrayList<>();
         final ArrayList<AndroidPackage> loaded = new ArrayList<>();
-        final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE;
+        final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_EXTERNAL_STORAGE;
 
         final VersionInfo ver;
         final List<PackageSetting> packages;
@@ -25947,7 +25451,6 @@
         synchronized (mLock) {
             scheduleWritePackageRestrictionsLocked(userId);
             scheduleWritePackageListLocked(userId);
-            primeDomainVerificationsLPw(userId);
             mAppsFilter.onUsersChanged();
         }
     }
@@ -26454,6 +25957,22 @@
         return snapshotComputer().getPackage(uid);
     }
 
+    private SigningDetails getSigningDetails(@NonNull String packageName) {
+        return snapshotComputer().getSigningDetails(packageName);
+    }
+
+    private SigningDetails getSigningDetails(int uid) {
+        return snapshotComputer().getSigningDetails(uid);
+    }
+
+    private boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
+        return snapshotComputer().filterAppAccess(pkg, callingUid, userId);
+    }
+
+    private boolean filterAppAccess(String packageName, int callingUid, int userId) {
+        return snapshotComputer().filterAppAccess(packageName, callingUid, userId);
+    }
+
     private class PackageManagerInternalImpl extends PackageManagerInternal {
         @Override
         public List<ApplicationInfo> getInstalledApplications(int flags, int userId,
@@ -26509,29 +26028,11 @@
         }
 
         private SigningDetails getSigningDetails(@NonNull String packageName) {
-            synchronized (mLock) {
-                AndroidPackage p = mPackages.get(packageName);
-                if (p == null) {
-                    return null;
-                }
-                return p.getSigningDetails();
-            }
+            return PackageManagerService.this.getSigningDetails(packageName);
         }
 
         private SigningDetails getSigningDetails(int uid) {
-            synchronized (mLock) {
-                final int appId = UserHandle.getAppId(uid);
-                final Object obj = mSettings.getSettingLPr(appId);
-                if (obj != null) {
-                    if (obj instanceof SharedUserSetting) {
-                        return ((SharedUserSetting) obj).signatures.mSigningDetails;
-                    } else if (obj instanceof PackageSetting) {
-                        final PackageSetting ps = (PackageSetting) obj;
-                        return ps.signatures.mSigningDetails;
-                    }
-                }
-                return SigningDetails.UNKNOWN;
-            }
+            return PackageManagerService.this.getSigningDetails(uid);
         }
 
         @Override
@@ -26546,20 +26047,12 @@
 
         @Override
         public boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId) {
-            synchronized (mLock) {
-                PackageSetting ps = getPackageSetting(pkg.getPackageName());
-                return PackageManagerService.this.shouldFilterApplicationLocked(ps, callingUid,
-                        userId);
-            }
+            return PackageManagerService.this.filterAppAccess(pkg, callingUid, userId);
         }
 
         @Override
         public boolean filterAppAccess(String packageName, int callingUid, int userId) {
-            synchronized (mLock) {
-                PackageSetting ps = getPackageSetting(packageName);
-                return PackageManagerService.this.shouldFilterApplicationLocked(ps, callingUid,
-                        userId);
-            }
+            return PackageManagerService.this.filterAppAccess(packageName, callingUid, userId);
         }
 
         @Override
@@ -27019,6 +26512,7 @@
 
                 final boolean instantApp =
                         isInstantAppInternal(visiblePackage.getPackageName(), userId, visibleUid);
+                final boolean accessGranted;
                 if (instantApp) {
                     if (!direct) {
                         // if the interaction that lead to this granting access to an instant app
@@ -27026,10 +26520,13 @@
                         // grant.
                         return;
                     }
-                    mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
+                    accessGranted = mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
                             recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/);
                 } else {
-                    mAppsFilter.grantImplicitAccess(recipientUid, visibleUid);
+                    accessGranted = mAppsFilter.grantImplicitAccess(recipientUid, visibleUid);
+                }
+                if (accessGranted) {
+                    ApplicationPackageManager.invalidateGetPackagesForUidCache();
                 }
             }
         }
@@ -27543,47 +27040,6 @@
         }
 
         @Override
-        public boolean registerInstalledLoadingProgressCallback(String packageName,
-                PackageManagerInternal.InstalledLoadingProgressCallback callback, int userId) {
-            final PackageSetting ps = getPackageSettingForUser(packageName, Binder.getCallingUid(),
-                    userId);
-            if (ps == null) {
-                return false;
-            }
-            if (!ps.isPackageLoading()) {
-                Slog.w(TAG,
-                        "Failed registering loading progress callback. Package is fully loaded.");
-                return false;
-            }
-            if (mIncrementalManager == null) {
-                Slog.w(TAG,
-                        "Failed registering loading progress callback. Incremental is not enabled");
-                return false;
-            }
-            return mIncrementalManager.registerLoadingProgressCallback(ps.getPathString(),
-                    (IPackageLoadingProgressCallback) callback.getBinder());
-        }
-
-        @Override
-        public boolean unregisterInstalledLoadingProgressCallback(String packageName,
-                PackageManagerInternal.InstalledLoadingProgressCallback callback) {
-            final PackageSetting ps;
-            synchronized (mLock) {
-                ps = mSettings.getPackageLPr(packageName);
-                if (ps == null) {
-                    Slog.w(TAG, "Failed unregistering loading progress callback. Package "
-                            + packageName + " is not installed");
-                    return false;
-                }
-            }
-            if (mIncrementalManager == null) {
-                return false;
-            }
-            return mIncrementalManager.unregisterLoadingProgressCallback(ps.getPathString(),
-                    (IPackageLoadingProgressCallback) callback.getBinder());
-        }
-
-        @Override
         public IncrementalStatesInfo getIncrementalStatesInfo(
                 @NonNull String packageName, int filterCallingUid, int userId) {
             final PackageSetting ps = getPackageSettingForUser(packageName, filterCallingUid,
@@ -27608,12 +27064,14 @@
             ps.setStatesOnCrashOrAnr();
         }
 
+        @Override
         public void requestChecksums(@NonNull String packageName, boolean includeSplits,
                 @Checksum.Type int optional, @Checksum.Type int required,
-                @Nullable List trustedInstallers, @NonNull IntentSender statusReceiver, int userId,
+                @Nullable List trustedInstallers,
+                @NonNull IOnChecksumsReadyListener onChecksumsReadyListener, int userId,
                 @NonNull Executor executor, @NonNull Handler handler) {
             requestChecksumsInternal(packageName, includeSplits, optional, required,
-                    trustedInstallers, statusReceiver, userId, executor, handler);
+                    trustedInstallers, onChecksumsReadyListener, userId, executor, handler);
         }
 
         @Override
@@ -28198,6 +27656,13 @@
                     }
                     continue;
                 }
+                if (ps.appId < Process.FIRST_APPLICATION_UID) {
+                    if (DEBUG_PER_UID_READ_TIMEOUTS) {
+                        Slog.i(TAG, "PerUidReadTimeouts: package is system, appId=" + ps.appId);
+                    }
+                    continue;
+                }
+
                 final AndroidPackage pkg = ps.getPkg();
                 if (pkg.getLongVersionCode() < perPackage.versionCodes.minVersionCode
                         || pkg.getLongVersionCode() > perPackage.versionCodes.maxVersionCode) {
@@ -28246,6 +27711,11 @@
                 duration);
         return bOptions;
     }
+
+    @NonNull
+    public DomainVerificationService.Connection getDomainVerificationConnection() {
+        return mDomainVerificationConnection;
+    }
 }
 
 interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index d3d7c60..8015063 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -35,9 +35,12 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.Debug;
 import android.os.Environment;
@@ -621,7 +624,7 @@
      */
     public static boolean verifySignatures(PackageSetting pkgSetting,
             PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
-            boolean compareCompat, boolean compareRecover)
+            boolean compareCompat, boolean compareRecover, boolean isRollback)
             throws PackageManagerException {
         final String packageName = pkgSetting.name;
         boolean compatMatch = false;
@@ -655,6 +658,13 @@
                 match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
             }
 
+            if (!match && isRollback) {
+                // Since a rollback can only be initiated for an APK previously installed on the
+                // device allow rolling back to a previous signing key even if the rollback
+                // capability has not been granted.
+                match = pkgSetting.signatures.mSigningDetails.hasAncestorOrSelf(parsedSignatures);
+            }
+
             if (!match) {
                 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                         "Package " + packageName +
@@ -819,8 +829,8 @@
     /**
      * Parse given package and return minimal details.
      */
-    public static PackageInfoLite getMinimalPackageInfo(Context context,
-            PackageParser.PackageLite pkg, String packagePath, int flags, String abiOverride) {
+    public static PackageInfoLite getMinimalPackageInfo(Context context, PackageLite pkg,
+            String packagePath, int flags, String abiOverride) {
         final PackageInfoLite ret = new PackageInfoLite();
         if (packagePath == null || pkg == null) {
             Slog.i(TAG, "Invalid package file " + packagePath);
@@ -843,19 +853,19 @@
         }
 
         final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
-                pkg.packageName, pkg.installLocation, sizeBytes, flags);
+                pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags);
 
-        ret.packageName = pkg.packageName;
-        ret.splitNames = pkg.splitNames;
-        ret.versionCode = pkg.versionCode;
-        ret.versionCodeMajor = pkg.versionCodeMajor;
-        ret.baseRevisionCode = pkg.baseRevisionCode;
-        ret.splitRevisionCodes = pkg.splitRevisionCodes;
-        ret.installLocation = pkg.installLocation;
-        ret.verifiers = pkg.verifiers;
+        ret.packageName = pkg.getPackageName();
+        ret.splitNames = pkg.getSplitNames();
+        ret.versionCode = pkg.getVersionCode();
+        ret.versionCodeMajor = pkg.getVersionCodeMajor();
+        ret.baseRevisionCode = pkg.getBaseRevisionCode();
+        ret.splitRevisionCodes = pkg.getSplitRevisionCodes();
+        ret.installLocation = pkg.getInstallLocation();
+        ret.verifiers = pkg.getVerifiers();
         ret.recommendedInstallLocation = recommendedInstallLocation;
-        ret.multiArch = pkg.multiArch;
-        ret.debuggable = pkg.debuggable;
+        ret.multiArch = pkg.isMultiArch();
+        ret.debuggable = pkg.isDebuggable();
 
         return ret;
     }
@@ -868,11 +878,16 @@
      */
     public static long calculateInstalledSize(String packagePath, String abiOverride) {
         final File packageFile = new File(packagePath);
-        final PackageParser.PackageLite pkg;
         try {
-            pkg = PackageParser.parsePackageLite(packageFile, 0);
-            return PackageHelper.calculateInstalledSize(pkg, abiOverride);
-        } catch (PackageParserException | IOException e) {
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                    input.reset(), packageFile, /* flags */ 0);
+            if (result.isError()) {
+                throw new PackageManagerException(result.getErrorCode(),
+                        result.getErrorMessage(), result.getException());
+            }
+            return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride);
+        } catch (PackageManagerException | IOException e) {
             Slog.w(TAG, "Failed to calculate installed size: " + e);
             return -1;
         }
@@ -931,16 +946,23 @@
 
         try {
             final File packageFile = new File(packagePath);
-            final PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packageFile, 0);
-            copyFile(pkg.baseCodePath, targetDir, "base.apk");
-            if (!ArrayUtils.isEmpty(pkg.splitNames)) {
-                for (int i = 0; i < pkg.splitNames.length; i++) {
-                    copyFile(pkg.splitCodePaths[i], targetDir,
-                            "split_" + pkg.splitNames[i] + ".apk");
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                    input.reset(), packageFile, /* flags */ 0);
+            if (result.isError()) {
+                Slog.w(TAG, "Failed to parse package at " + packagePath);
+                return result.getErrorCode();
+            }
+            final PackageLite pkg = result.getResult();
+            copyFile(pkg.getBaseApkPath(), targetDir, "base.apk");
+            if (!ArrayUtils.isEmpty(pkg.getSplitNames())) {
+                for (int i = 0; i < pkg.getSplitNames().length; i++) {
+                    copyFile(pkg.getSplitApkPaths()[i], targetDir,
+                            "split_" + pkg.getSplitNames()[i] + ".apk");
                 }
             }
             return PackageManager.INSTALL_SUCCEEDED;
-        } catch (PackageParserException | IOException | ErrnoException e) {
+        } catch (IOException | ErrnoException e) {
             Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
             return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 446342a..b5765b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -17,13 +17,9 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
 
 import android.accounts.IAccountManager;
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -49,8 +45,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
@@ -61,7 +55,9 @@
 import android.content.pm.dex.ArtManager;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
+import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.AssetManager;
@@ -108,6 +104,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
+import com.android.server.pm.verify.domain.DomainVerificationShell;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 
 import dalvik.system.DexFile;
@@ -122,6 +119,7 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.URISyntaxException;
+import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.Collection;
@@ -148,6 +146,7 @@
     final LegacyPermissionManagerInternal mLegacyPermissionManager;
     final PermissionManager mPermissionManager;
     final Context mContext;
+    final DomainVerificationShell mDomainVerificationShell;
     final private WeakHashMap<String, Resources> mResourceCache =
             new WeakHashMap<String, Resources>();
     int mTargetUser;
@@ -155,11 +154,15 @@
     boolean mComponents;
     int mQueryFlags;
 
-    PackageManagerShellCommand(PackageManagerService service, Context context) {
+    private static final SecureRandom RANDOM = new SecureRandom();
+
+    PackageManagerShellCommand(@NonNull PackageManagerService service,
+            @NonNull Context context, @NonNull DomainVerificationShell domainVerificationShell) {
         mInterface = service;
         mLegacyPermissionManager = LocalServices.getService(LegacyPermissionManagerInternal.class);
         mPermissionManager = context.getSystemService(PermissionManager.class);
         mContext = context;
+        mDomainVerificationShell = domainVerificationShell;
     }
 
     @Override
@@ -264,10 +267,6 @@
                     return runGetPrivappDenyPermissions();
                 case "get-oem-permissions":
                     return runGetOemPermissions();
-                case "set-app-link":
-                    return runSetAppLink();
-                case "get-app-link":
-                    return runGetAppLink();
                 case "trim-caches":
                     return runTrimCaches();
                 case "create-user":
@@ -306,6 +305,12 @@
                 case "bypass-staged-installer-check":
                     return runBypassStagedInstallerCheck();
                 default: {
+                    Boolean domainVerificationResult =
+                            mDomainVerificationShell.runCommand(this, cmd);
+                    if (domainVerificationResult != null) {
+                        return domainVerificationResult ? 0 : 1;
+                    }
+
                     String nextArg = getNextArg();
                     if (nextArg == null) {
                         if (cmd.equalsIgnoreCase("-l")) {
@@ -555,8 +560,8 @@
                             apkLiteResult.getException());
                 }
                 final ApkLite apkLite = apkLiteResult.getResult();
-                PackageLite pkgLite = new PackageLite(null, apkLite.codePath, apkLite, null, null,
-                        null, null, null, null);
+                final PackageLite pkgLite = new PackageLite(null, apkLite.getPath(), apkLite, null,
+                        null, null, null, null, null);
                 sessionSize += PackageHelper.calculateInstalledSize(pkgLite,
                         params.sessionParams.abiOverride, fd.getFileDescriptor());
             } catch (IOException e) {
@@ -2424,134 +2429,6 @@
         return 0;
     }
 
-    private String linkStateToString(int state) {
-        switch (state) {
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
-        }
-        return "Unknown link state: " + state;
-    }
-
-    // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
-    private int runSetAppLink() throws RemoteException {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = UserHandle.parseUserArg(getNextArgRequired());
-            } else {
-                getErrPrintWriter().println("Error: unknown option: " + opt);
-                return 1;
-            }
-        }
-
-        // Package name to act on; required
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            getErrPrintWriter().println("Error: no package specified.");
-            return 1;
-        }
-
-        // State to apply; {always|ask|never|undefined}, required
-        final String modeString = getNextArg();
-        if (modeString == null) {
-            getErrPrintWriter().println("Error: no app link state specified.");
-            return 1;
-        }
-
-        final int newMode;
-        switch (modeString.toLowerCase()) {
-            case "undefined":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-                break;
-
-            case "always":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-                break;
-
-            case "ask":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-                break;
-
-            case "always-ask":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-                break;
-
-            case "never":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-                break;
-
-            default:
-                getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'");
-                return 1;
-        }
-
-        final int translatedUserId =
-                translateUserId(userId, UserHandle.USER_NULL, "runSetAppLink");
-        final PackageInfo info = mInterface.getPackageInfo(pkg, 0, translatedUserId);
-        if (info == null) {
-            getErrPrintWriter().println("Error: package " + pkg + " not found.");
-            return 1;
-        }
-
-        if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
-            getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
-            return 1;
-        }
-
-        if (!mInterface.updateIntentVerificationStatus(pkg, newMode, translatedUserId)) {
-            getErrPrintWriter().println("Error: unable to update app link status for " + pkg);
-            return 1;
-        }
-
-        return 0;
-    }
-
-    // pm get-app-link [--user USER_ID] PACKAGE
-    private int runGetAppLink() throws RemoteException {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = UserHandle.parseUserArg(getNextArgRequired());
-            } else {
-                getErrPrintWriter().println("Error: unknown option: " + opt);
-                return 1;
-            }
-        }
-
-        // Package name to act on; required
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            getErrPrintWriter().println("Error: no package specified.");
-            return 1;
-        }
-
-        final int translatedUserId =
-                translateUserId(userId, UserHandle.USER_NULL, "runGetAppLink");
-        final PackageInfo info = mInterface.getPackageInfo(pkg, 0, translatedUserId);
-        if (info == null) {
-            getErrPrintWriter().println("Error: package " + pkg + " not found.");
-            return 1;
-        }
-
-        if ((info.applicationInfo.privateFlags
-                & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
-            getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
-            return 1;
-        }
-
-        getOutPrintWriter().println(linkStateToString(
-                mInterface.getIntentVerificationStatus(pkg, translatedUserId)));
-
-        return 0;
-    }
-
     private int runTrimCaches() throws RemoteException {
         String size = getNextArg();
         if (size == null) {
@@ -3146,7 +3023,7 @@
 
             // 1. Single file from stdin.
             if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
-                final String name = "base." + (isApex ? "apex" : "apk");
+                final String name = "base" + RANDOM.nextInt() + "." + (isApex ? "apex" : "apk");
                 final Metadata metadata = Metadata.forStdIn(name);
                 session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes,
                         metadata.toByteArray(), null);
@@ -3912,6 +3789,8 @@
         pw.println("      --enable: turn on debug logging (default)");
         pw.println("      --disable: turn off debug logging");
         pw.println("");
+        mDomainVerificationShell.printHelp(pw);
+        pw.println("");
         Intent.printIntentArgsHelp(pw , "");
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index ade087b..ca5d2b4 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -39,6 +39,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * Settings data for a particular package we know about.
@@ -99,18 +100,23 @@
     @NonNull
     private PackageStateUnserialized pkgState = new PackageStateUnserialized();
 
+    @NonNull
+    private UUID mDomainSetId;
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public PackageSetting(String name, String realName, @NonNull File codePath,
             String legacyNativeLibraryPathString, String primaryCpuAbiString,
             String secondaryCpuAbiString, String cpuAbiOverrideString,
             long pVersionCode, int pkgFlags, int privateFlags,
             int sharedUserId, String[] usesStaticLibraries,
-            long[] usesStaticLibrariesVersions, Map<String, ArraySet<String>> mimeGroups) {
+            long[] usesStaticLibrariesVersions, Map<String, ArraySet<String>> mimeGroups,
+            @NonNull UUID domainSetId) {
         super(name, realName, codePath, legacyNativeLibraryPathString,
                 primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                 pVersionCode, pkgFlags, privateFlags,
                 usesStaticLibraries, usesStaticLibrariesVersions);
         this.sharedUserId = sharedUserId;
+        mDomainSetId = domainSetId;
         copyMimeGroups(mimeGroups);
     }
 
@@ -168,6 +174,7 @@
         sharedUser = orig.sharedUser;
         sharedUserId = orig.sharedUserId;
         copyMimeGroups(orig.mimeGroups);
+        mDomainSetId = orig.getDomainSetId();
     }
 
     private void copyMimeGroups(@Nullable Map<String, ArraySet<String>> newMimeGroups) {
@@ -337,8 +344,8 @@
                     installSource.originatingPackageName);
             proto.end(sourceToken);
         }
-        proto.write(PackageProto.StatesProto.IS_STARTABLE, incrementalStates.isStartable());
-        proto.write(PackageProto.StatesProto.IS_LOADING, incrementalStates.isLoading());
+        proto.write(PackageProto.StatesProto.IS_STARTABLE, isPackageStartable());
+        proto.write(PackageProto.StatesProto.IS_LOADING, isPackageLoading());
         writeUsersInfoToProto(proto, PackageProto.USERS);
         writePackageUserPermissionsProto(proto, PackageProto.USER_PERMISSIONS, users, dataProvider);
         proto.end(packageToken);
@@ -374,6 +381,7 @@
         pkg = other.pkg;
         sharedUserId = other.sharedUserId;
         sharedUser = other.sharedUser;
+        mDomainSetId = other.mDomainSetId;
 
         Set<String> mimeGroupNames = other.mimeGroups != null ? other.mimeGroups.keySet() : null;
         updateMimeGroups(mimeGroupNames);
@@ -385,4 +393,14 @@
     public PackageStateUnserialized getPkgState() {
         return pkgState;
     }
+
+    @NonNull
+    public UUID getDomainSetId() {
+        return mDomainSetId;
+    }
+
+    public PackageSetting setDomainSetId(@NonNull UUID domainSetId) {
+        mDomainSetId = domainSetId;
+        return this;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 67bd82b4..3a14283 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -26,8 +26,6 @@
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IncrementalStatesInfo;
-import android.content.pm.IntentFilterVerificationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.UninstallReason;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
@@ -131,8 +129,6 @@
     /** Whether or not an update is available. Ostensibly only for instant apps. */
     boolean updateAvailable;
 
-    IntentFilterVerificationInfo verificationInfo;
-
     boolean forceQueryableOverride;
 
     @NonNull
@@ -258,7 +254,6 @@
         for (int i = 0; i < orig.mUserState.size(); i++) {
             mUserState.put(orig.mUserState.keyAt(i), orig.mUserState.valueAt(i));
         }
-        verificationInfo = orig.verificationInfo;
         versionCode = orig.versionCode;
         volumeUuid = orig.volumeUuid;
         categoryHint = orig.categoryHint;
@@ -350,9 +345,12 @@
         return readUserState(userId).getSharedLibraryOverlayPaths();
     }
 
-    /** Only use for testing. Do NOT use in production code. */
+    /**
+     * Only use for testing. Do NOT use in production code.
+     */
     @VisibleForTesting
-    SparseArray<PackageUserState> getUserState() {
+    @Deprecated
+    public SparseArray<PackageUserState> getUserState() {
         return mUserState;
     }
 
@@ -496,8 +494,7 @@
             ArrayMap<String, PackageUserState.SuspendParams> suspendParams, boolean instantApp,
             boolean virtualPreload, String lastDisableAppCaller,
             ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
-            int domainVerifState, int linkGeneration, int installReason, int uninstallReason,
-            String harmfulAppWarning) {
+            int installReason, int uninstallReason, String harmfulAppWarning) {
         PackageUserState state = modifyUserState(userId);
         state.ceDataInode = ceDataInode;
         state.enabled = enabled;
@@ -511,8 +508,6 @@
         state.lastDisableAppCaller = lastDisableAppCaller;
         state.enabledComponents = enabledComponents;
         state.disabledComponents = disabledComponents;
-        state.domainVerificationStatus = domainVerifState;
-        state.appLinkGeneration = linkGeneration;
         state.installReason = installReason;
         state.uninstallReason = uninstallReason;
         state.instantApp = instantApp;
@@ -528,7 +523,6 @@
                 otherState.instantApp,
                 otherState.virtualPreload, otherState.lastDisableAppCaller,
                 otherState.enabledComponents, otherState.disabledComponents,
-                otherState.domainVerificationStatus, otherState.appLinkGeneration,
                 otherState.installReason, otherState.uninstallReason, otherState.harmfulAppWarning);
     }
 
@@ -644,40 +638,6 @@
         return excludedUserIds;
     }
 
-    IntentFilterVerificationInfo getIntentFilterVerificationInfo() {
-        return verificationInfo;
-    }
-
-    void setIntentFilterVerificationInfo(IntentFilterVerificationInfo info) {
-        verificationInfo = info;
-        onChanged();
-    }
-
-    // Returns a packed value as a long:
-    //
-    // high 'int'-sized word: link status: undefined/ask/never/always.
-    // low 'int'-sized word: relative priority among 'always' results.
-    long getDomainVerificationStatusForUser(int userId) {
-        PackageUserState state = readUserState(userId);
-        long result = (long) state.appLinkGeneration;
-        result |= ((long) state.domainVerificationStatus) << 32;
-        return result;
-    }
-
-    void setDomainVerificationStatusForUser(final int status, int generation, int userId) {
-        PackageUserState state = modifyUserState(userId);
-        state.domainVerificationStatus = status;
-        if (status == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
-            state.appLinkGeneration = generation;
-            onChanged();
-        }
-    }
-
-    void clearDomainVerificationStatusForUser(int userId) {
-        modifyUserState(userId).domainVerificationStatus =
-                PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-    }
-
     protected void writeUsersInfoToProto(ProtoOutputStream proto, long fieldId) {
         int count = mUserState.size();
         for (int i = 0; i < count; i++) {
@@ -767,14 +727,14 @@
      * @return True if package is startable, false otherwise.
      */
     public boolean isPackageStartable() {
-        return incrementalStates.isStartable();
+        return getIncrementalStates().isStartable();
     }
 
     /**
      * @return True if package is still being loaded, false if the package is fully loaded.
      */
     public boolean isPackageLoading() {
-        return incrementalStates.isLoading();
+        return getIncrementalStates().isLoading();
     }
 
     /**
@@ -845,7 +805,6 @@
         this.volumeUuid = other.volumeUuid;
         this.categoryHint = other.categoryHint;
         this.updateAvailable = other.updateAvailable;
-        this.verificationInfo = other.verificationInfo;
         this.forceQueryableOverride = other.forceQueryableOverride;
         this.incrementalStates = other.incrementalStates;
 
diff --git a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
index ce77c91..8c5084a 100644
--- a/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
+++ b/services/core/java/com/android/server/pm/ProcessLoggingHandler.java
@@ -16,22 +16,16 @@
 
 package com.android.server.pm;
 
-import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
-
 import android.app.admin.SecurityLog;
 import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
 import android.content.pm.ApkChecksum;
 import android.content.pm.Checksum;
+import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.PackageManagerInternal;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerExecutor;
-import android.os.IBinder;
-import android.os.Parcelable;
+import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -39,7 +33,6 @@
 import com.android.internal.os.BackgroundThread;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -109,26 +102,23 @@
         // Capturing local loggingInfo to still log even if hash was invalidated.
         try {
             pmi.requestChecksums(packageName, false, 0, CHECKSUM_TYPE, null,
-                    new IntentSender((IIntentSender) new IIntentSender.Stub() {
+                    new IOnChecksumsReadyListener.Stub() {
                         @Override
-                        public void send(int code, Intent intent, String resolvedType,
-                                IBinder allowlistToken, IIntentReceiver finishedReceiver,
-                                String requiredPermission, Bundle options) {
-                            processChecksums(loggingInfo, intent);
+                        public void onChecksumsReady(List<ApkChecksum> checksums)
+                                throws RemoteException {
+                            processChecksums(loggingInfo, checksums);
                         }
-                    }), context.getUserId(), mExecutor, this);
+                    }, context.getUserId(),
+                    mExecutor, this);
         } catch (Throwable t) {
             Slog.e(TAG, "requestChecksums() failed", t);
             enqueueProcessChecksum(loggingInfo, null);
         }
     }
 
-    void processChecksums(final LoggingInfo loggingInfo, Intent intent) {
-        Parcelable[] parcelables = intent.getParcelableArrayExtra(EXTRA_CHECKSUMS);
-        ApkChecksum[] checksums = Arrays.copyOf(parcelables, parcelables.length,
-                ApkChecksum[].class);
-
-        for (ApkChecksum checksum : checksums) {
+    void processChecksums(final LoggingInfo loggingInfo, List<ApkChecksum> checksums) {
+        for (int i = 0, size = checksums.size(); i < size; ++i) {
+            ApkChecksum checksum = checksums.get(i);
             if (checksum.getType() == CHECKSUM_TYPE) {
                 processChecksum(loggingInfo, checksum.getValue());
                 return;
diff --git a/services/core/java/com/android/server/pm/RestrictionsSet.java b/services/core/java/com/android/server/pm/RestrictionsSet.java
index ea61ca4..e5a70c3 100644
--- a/services/core/java/com/android/server/pm/RestrictionsSet.java
+++ b/services/core/java/com/android/server/pm/RestrictionsSet.java
@@ -26,6 +26,7 @@
 import android.util.TypedXmlSerializer;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.BundleUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -78,7 +79,7 @@
         if (!changed) {
             return false;
         }
-        if (!UserRestrictionsUtils.isEmpty(restrictions)) {
+        if (!BundleUtils.isEmpty(restrictions)) {
             mUserRestrictions.put(userId, restrictions);
         } else {
             mUserRestrictions.delete(userId);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3369a4f..fb033e6 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -21,15 +21,12 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
 import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN;
 import static android.content.pm.PackageManager.UNINSTALL_REASON_USER_TYPE;
 import static android.os.Process.PACKAGE_INFO_GID;
 import static android.os.Process.SYSTEM_UID;
 
-import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import android.annotation.NonNull;
@@ -73,6 +70,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.incremental.IncrementalManager;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.service.pm.PackageServiceDumpProto;
@@ -107,6 +105,9 @@
 import com.android.server.LocalServices;
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.verify.domain.DomainVerificationLegacySettings;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationPersistence;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -130,6 +131,7 @@
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -146,7 +148,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
@@ -154,6 +155,7 @@
 import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * Holds information about dynamic settings.
@@ -282,9 +284,9 @@
             "persistent-preferred-activities";
     static final String TAG_CROSS_PROFILE_INTENT_FILTERS =
             "crossProfile-intent-filters";
-    private static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
+    public static final String TAG_DOMAIN_VERIFICATION = "domain-verification";
     private static final String TAG_DEFAULT_APPS = "default-apps";
-    private static final String TAG_ALL_INTENT_FILTER_VERIFICATION =
+    public static final String TAG_ALL_INTENT_FILTER_VERIFICATION =
             "all-intent-filter-verifications";
     private static final String TAG_DEFAULT_BROWSER = "default-browser";
     private static final String TAG_DEFAULT_DIALER = "default-dialer";
@@ -389,12 +391,6 @@
     private final WatchedSparseArray<ArraySet<String>> mBlockUninstallPackages =
             new WatchedSparseArray<>();
 
-    // Set of restored intent-filter verification states
-    @Watched
-    private final WatchedArrayMap<String, IntentFilterVerificationInfo>
-            mRestoredIntentFilterVerifications =
-            new WatchedArrayMap<String, IntentFilterVerificationInfo>();
-
     private static final class KernelPackageState {
         int appId;
         int[] excludedUserIds;
@@ -486,7 +482,10 @@
     @Watched
     final WatchedSparseArray<String> mDefaultBrowserApp = new WatchedSparseArray<String>();
 
+    // TODO(b/161161364): This seems unused, and is probably not relevant in the new API, but should
+    //  verify.
     // App-link priority tracking, per-user
+    @NonNull
     @Watched
     final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray();
 
@@ -511,6 +510,8 @@
 
     private final LegacyPermissionDataProvider mPermissionDataProvider;
 
+    private final DomainVerificationManagerInternal mDomainVerificationManager;
+
     /**
      * The observer that watches for changes from array members
      */
@@ -537,13 +538,12 @@
         mStoppedPackagesFilename = null;
         mBackupStoppedPackagesFilename = null;
         mKernelMappingFilename = null;
-
+        mDomainVerificationManager = null;
         mPackages.registerObserver(mObserver);
         mInstallerPackages.registerObserver(mObserver);
         mKernelMapping.registerObserver(mObserver);
         mDisabledSysPackages.registerObserver(mObserver);
         mBlockUninstallPackages.registerObserver(mObserver);
-        mRestoredIntentFilterVerifications.registerObserver(mObserver);
         mVersion.registerObserver(mObserver);
         mPreferredActivities.registerObserver(mObserver);
         mPersistentPreferredActivities.registerObserver(mObserver);
@@ -553,13 +553,14 @@
         mOtherAppIds.registerObserver(mObserver);
         mRenamedPackages.registerObserver(mObserver);
         mDefaultBrowserApp.registerObserver(mObserver);
-        mNextAppLinkGeneration.registerObserver(mObserver);
 
         Watchable.verifyWatchedAttributes(this, mObserver);
     }
 
     Settings(File dataDir, RuntimePermissionsPersistence runtimePermissionsPersistence,
-            LegacyPermissionDataProvider permissionDataProvider, Object lock) {
+            LegacyPermissionDataProvider permissionDataProvider,
+            @NonNull DomainVerificationManagerInternal domainVerificationManager,
+            @NonNull Object lock)  {
         mLock = lock;
         mAppIds = new WatchedArrayList<>();
         mOtherAppIds = new WatchedSparseArray<>();
@@ -586,12 +587,13 @@
         mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
         mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
 
+        mDomainVerificationManager = domainVerificationManager;
+
         mPackages.registerObserver(mObserver);
         mInstallerPackages.registerObserver(mObserver);
         mKernelMapping.registerObserver(mObserver);
         mDisabledSysPackages.registerObserver(mObserver);
         mBlockUninstallPackages.registerObserver(mObserver);
-        mRestoredIntentFilterVerifications.registerObserver(mObserver);
         mVersion.registerObserver(mObserver);
         mPreferredActivities.registerObserver(mObserver);
         mPersistentPreferredActivities.registerObserver(mObserver);
@@ -601,7 +603,6 @@
         mOtherAppIds.registerObserver(mObserver);
         mRenamedPackages.registerObserver(mObserver);
         mDefaultBrowserApp.registerObserver(mObserver);
-        mNextAppLinkGeneration.registerObserver(mObserver);
 
         Watchable.verifyWatchedAttributes(this, mObserver);
     }
@@ -628,11 +629,12 @@
         mBackupStoppedPackagesFilename = null;
         mKernelMappingFilename = null;
 
+        mDomainVerificationManager = r.mDomainVerificationManager;
+
         mInstallerPackages.addAll(r.mInstallerPackages);
         mKernelMapping.putAll(r.mKernelMapping);
         mDisabledSysPackages.putAll(r.mDisabledSysPackages);
         mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages);
-        mRestoredIntentFilterVerifications.putAll(r.mRestoredIntentFilterVerifications);
         mVersion.putAll(r.mVersion);
         mVerifierDeviceIdentity = r.mVerifierDeviceIdentity;
         WatchedSparseArray.snapshot(
@@ -648,7 +650,6 @@
         mKeySetRefs.putAll(r.mKeySetRefs);
         mRenamedPackages.snapshot(r.mRenamedPackages);
         mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
-        mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
         // mReadMessages
         mPendingPackages.addAll(r.mPendingPackages);
         mSystemDir = null;
@@ -765,7 +766,8 @@
                 p.legacyNativeLibraryPathString, p.primaryCpuAbiString,
                 p.secondaryCpuAbiString, p.cpuAbiOverrideString,
                 p.appId, p.versionCode, p.pkgFlags, p.pkgPrivateFlags,
-                p.usesStaticLibraries, p.usesStaticLibrariesVersions, p.mimeGroups);
+                p.usesStaticLibraries, p.usesStaticLibrariesVersions, p.mimeGroups,
+                mDomainVerificationManager.generateNewId());
         if (ret != null) {
             ret.getPkgState().setUpdatedSystemApp(false);
         }
@@ -785,7 +787,8 @@
             String legacyNativeLibraryPathString, String primaryCpuAbiString,
             String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc, int
             pkgFlags, int pkgPrivateFlags, String[] usesStaticLibraries,
-            long[] usesStaticLibraryNames, Map<String, ArraySet<String>> mimeGroups) {
+            long[] usesStaticLibraryNames, Map<String, ArraySet<String>> mimeGroups,
+            @NonNull UUID domainSetId) {
         PackageSetting p = mPackages.get(name);
         if (p != null) {
             if (p.appId == uid) {
@@ -798,7 +801,7 @@
         p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
                 primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
                 pkgPrivateFlags, 0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames,
-                mimeGroups);
+                mimeGroups, domainSetId);
         p.appId = uid;
         if (registerExistingAppIdLPw(uid, p, name)) {
             mPackages.put(name, p);
@@ -862,7 +865,7 @@
             UserHandle installUser, boolean allowInstall, boolean instantApp,
             boolean virtualPreload, UserManagerService userManager,
             String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
-            Set<String> mimeGroupNames) {
+            Set<String> mimeGroupNames, @NonNull UUID domainSetId) {
         final PackageSetting pkgSetting;
         if (originalPkg != null) {
             if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
@@ -882,12 +885,13 @@
             pkgSetting.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
             // Update new package state.
             pkgSetting.setTimeStamp(codePath.lastModified());
+            pkgSetting.setDomainSetId(domainSetId);
         } else {
             pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
                     legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
                     null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
                     0 /*sharedUserId*/, usesStaticLibraries,
-                    usesStaticLibrariesVersions, createMimeGroups(mimeGroupNames));
+                    usesStaticLibrariesVersions, createMimeGroups(mimeGroupNames), domainSetId);
             pkgSetting.setTimeStamp(codePath.lastModified());
             pkgSetting.sharedUser = sharedUser;
             // If this is not a system app, it starts out stopped.
@@ -924,8 +928,6 @@
                                 null /*lastDisableAppCaller*/,
                                 null /*enabledComponents*/,
                                 null /*disabledComponents*/,
-                                INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
-                                0 /*linkGeneration*/,
                                 PackageManager.INSTALL_REASON_UNKNOWN,
                                 PackageManager.UNINSTALL_REASON_UNKNOWN,
                                 null /*harmfulAppWarning*/);
@@ -982,7 +984,7 @@
             @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags,
             int pkgPrivateFlags, @NonNull UserManagerService userManager,
             @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
-            @Nullable Set<String> mimeGroupNames)
+            @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId)
                     throws PackageManagerException {
         final String pkgName = pkgSetting.name;
         if (pkgSetting.sharedUser != sharedUser) {
@@ -1058,6 +1060,7 @@
             pkgSetting.usesStaticLibrariesVersions = null;
         }
         pkgSetting.updateMimeGroups(mimeGroupNames);
+        pkgSetting.setDomainSetId(domainSetId);
     }
 
     /**
@@ -1167,15 +1170,6 @@
                 replaceAppIdLPw(p.appId, sharedUser);
             }
         }
-
-        IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.get(p.name);
-        if (ivi != null) {
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.i(TAG, "Applying restored IVI for " + p.name + " : " + ivi.getStatusString());
-            }
-            mRestoredIntentFilterVerifications.remove(p.name);
-            p.setIntentFilterVerificationInfo(ivi);
-        }
     }
 
     int removePackageLPw(String name) {
@@ -1306,129 +1300,6 @@
         return cpir;
     }
 
-    /**
-     * The following functions suppose that you have a lock for managing access to the
-     * mIntentFiltersVerifications map.
-     */
-
-    /* package protected */
-    IntentFilterVerificationInfo getIntentFilterVerificationLPr(String packageName) {
-        PackageSetting ps = mPackages.get(packageName);
-        if (ps == null) {
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
-            }
-            return null;
-        }
-        return ps.getIntentFilterVerificationInfo();
-    }
-
-    /* package protected */
-    IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(String packageName,
-            ArraySet<String> domains) {
-        PackageSetting ps = mPackages.get(packageName);
-        if (ps == null) {
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
-            }
-            return null;
-        }
-        IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
-        if (ivi == null) {
-            ivi = new IntentFilterVerificationInfo(packageName, domains);
-            ps.setIntentFilterVerificationInfo(ivi);
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.d(PackageManagerService.TAG,
-                        "Creating new IntentFilterVerificationInfo for pkg: " + packageName);
-            }
-        } else {
-            ivi.setDomains(domains);
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.d(PackageManagerService.TAG,
-                        "Setting domains to existing IntentFilterVerificationInfo for pkg: " +
-                                packageName + " and with domains: " + ivi.getDomainsString());
-            }
-        }
-        return ivi;
-    }
-
-    int getIntentFilterVerificationStatusLPr(String packageName, int userId) {
-        PackageSetting ps = mPackages.get(packageName);
-        if (ps == null) {
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
-            }
-            return INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-        }
-        return (int)(ps.getDomainVerificationStatusForUser(userId) >> 32);
-    }
-
-    boolean updateIntentFilterVerificationStatusLPw(String packageName, final int status, int userId) {
-        // Update the status for the current package
-        PackageSetting current = mPackages.get(packageName);
-        if (current == null) {
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
-            }
-            return false;
-        }
-
-        final int alwaysGeneration;
-        if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
-            alwaysGeneration = mNextAppLinkGeneration.get(userId) + 1;
-            mNextAppLinkGeneration.put(userId, alwaysGeneration);
-        } else {
-            alwaysGeneration = 0;
-        }
-
-        current.setDomainVerificationStatusForUser(status, alwaysGeneration, userId);
-        return true;
-    }
-
-    /**
-     * Used for Settings App and PackageManagerService dump. Should be read only.
-     */
-    List<IntentFilterVerificationInfo> getIntentFilterVerificationsLPr(
-            String packageName) {
-        if (packageName == null) {
-            return Collections.<IntentFilterVerificationInfo>emptyList();
-        }
-        ArrayList<IntentFilterVerificationInfo> result = new ArrayList<>();
-        for (PackageSetting ps : mPackages.values()) {
-            IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
-            if (ivi == null || TextUtils.isEmpty(ivi.getPackageName()) ||
-                    !ivi.getPackageName().equalsIgnoreCase(packageName)) {
-                continue;
-            }
-            result.add(ivi);
-        }
-        return result;
-    }
-
-    boolean removeIntentFilterVerificationLPw(String packageName, int userId,
-            boolean alsoResetStatus) {
-        PackageSetting ps = mPackages.get(packageName);
-        if (ps == null) {
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.w(PackageManagerService.TAG, "No package known: " + packageName);
-            }
-            return false;
-        }
-        if (alsoResetStatus) {
-            ps.clearDomainVerificationStatusForUser(userId);
-        }
-        ps.setIntentFilterVerificationInfo(null);
-        return true;
-    }
-
-    boolean removeIntentFilterVerificationLPw(String packageName, int[] userIds) {
-        boolean result = false;
-        for (int userId : userIds) {
-            result |= removeIntentFilterVerificationLPw(packageName, userId, true);
-        }
-        return result;
-    }
-
     String removeDefaultBrowserPackageNameLPw(int userId) {
         return (userId == UserHandle.USER_ALL) ? null : mDefaultBrowserApp.removeReturnOld(userId);
     }
@@ -1590,40 +1461,7 @@
         }
     }
 
-    private void readDomainVerificationLPw(TypedXmlPullParser parser,
-            PackageSettingBase packageSetting) throws XmlPullParserException, IOException {
-        IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
-        packageSetting.setIntentFilterVerificationInfo(ivi);
-        if (DEBUG_PARSER) {
-            Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName());
-        }
-    }
-
-    private void readRestoredIntentFilterVerifications(TypedXmlPullParser parser)
-            throws XmlPullParserException, IOException {
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            final String tagName = parser.getName();
-            if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
-                IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
-                if (DEBUG_DOMAIN_VERIFICATION) {
-                    Slog.i(TAG, "Restored IVI for " + ivi.getPackageName()
-                            + " status=" + ivi.getStatusString());
-                }
-                mRestoredIntentFilterVerifications.put(ivi.getPackageName(), ivi);
-            } else {
-                Slog.w(TAG, "Unknown element: " + tagName);
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    void readDefaultAppsLPw(TypedXmlPullParser parser, int userId)
+    void readDefaultAppsLPw(XmlPullParser parser, int userId)
             throws XmlPullParserException, IOException {
         int outerDepth = parser.getDepth();
         int type;
@@ -1726,8 +1564,6 @@
                                 null /*lastDisableAppCaller*/,
                                 null /*enabledComponents*/,
                                 null /*disabledComponents*/,
-                                INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
-                                0 /*linkGeneration*/,
                                 PackageManager.INSTALL_REASON_UNKNOWN,
                                 PackageManager.UNINSTALL_REASON_UNKNOWN,
                                 null /*harmfulAppWarning*/);
@@ -1752,8 +1588,6 @@
                 return;
             }
 
-            int maxAppLinkGeneration = 0;
-
             int outerDepth = parser.getDepth();
             PackageSetting ps = null;
             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1816,11 +1650,6 @@
                     final int verifState = parser.getAttributeInt(null,
                             ATTR_DOMAIN_VERIFICATON_STATE,
                             PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
-                    final int linkGeneration =
-                            parser.getAttributeInt(null, ATTR_APP_LINK_GENERATION, 0);
-                    if (linkGeneration > maxAppLinkGeneration) {
-                        maxAppLinkGeneration = linkGeneration;
-                    }
                     final int installReason = parser.getAttributeInt(null, ATTR_INSTALL_REASON,
                             PackageManager.INSTALL_REASON_UNKNOWN);
                     final int uninstallReason = parser.getAttributeInt(null, ATTR_UNINSTALL_REASON,
@@ -1896,9 +1725,10 @@
                     }
                     ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
                             hidden, distractionFlags, suspended, suspendParamsMap,
-                            instantApp, virtualPreload,
-                            enabledCaller, enabledComponents, disabledComponents, verifState,
-                            linkGeneration, installReason, uninstallReason, harmfulAppWarning);
+                            instantApp, virtualPreload, enabledCaller, enabledComponents,
+                            disabledComponents, installReason, uninstallReason, harmfulAppWarning);
+
+                    mDomainVerificationManager.setLegacyUserState(name, userId, verifState);
                 } else if (tagName.equals("preferred-activities")) {
                     readPreferredActivitiesLPw(parser, userId);
                 } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
@@ -1917,9 +1747,6 @@
             }
 
             str.close();
-
-            mNextAppLinkGeneration.put(userId, maxAppLinkGeneration + 1);
-
         } catch (XmlPullParserException e) {
             mReadMessages.append("Error reading: " + e.toString());
             PackageManagerService.reportSettingsProblem(Log.ERROR,
@@ -2037,77 +1864,7 @@
         serializer.endTag(null, TAG_CROSS_PROFILE_INTENT_FILTERS);
     }
 
-    void writeDomainVerificationsLPr(TypedXmlSerializer serializer,
-                                     IntentFilterVerificationInfo verificationInfo)
-            throws IllegalArgumentException, IllegalStateException, IOException {
-        if (verificationInfo != null && verificationInfo.getPackageName() != null) {
-            serializer.startTag(null, TAG_DOMAIN_VERIFICATION);
-            verificationInfo.writeToXml(serializer);
-            if (DEBUG_DOMAIN_VERIFICATION) {
-                Slog.d(TAG, "Wrote domain verification for package: "
-                        + verificationInfo.getPackageName());
-            }
-            serializer.endTag(null, TAG_DOMAIN_VERIFICATION);
-        }
-    }
-
-    // Specifically for backup/restore
-    void writeAllDomainVerificationsLPr(TypedXmlSerializer serializer, int userId)
-            throws IllegalArgumentException, IllegalStateException, IOException {
-        serializer.startTag(null, TAG_ALL_INTENT_FILTER_VERIFICATION);
-        final int N = mPackages.size();
-        for (int i = 0; i < N; i++) {
-            PackageSetting ps = mPackages.valueAt(i);
-            IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
-            if (ivi != null) {
-                writeDomainVerificationsLPr(serializer, ivi);
-            }
-        }
-        serializer.endTag(null, TAG_ALL_INTENT_FILTER_VERIFICATION);
-    }
-
-    // Specifically for backup/restore
-    void readAllDomainVerificationsLPr(TypedXmlPullParser parser, int userId)
-            throws XmlPullParserException, IOException {
-        mRestoredIntentFilterVerifications.clear();
-
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            String tagName = parser.getName();
-            if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
-                IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
-                final String pkgName = ivi.getPackageName();
-                final PackageSetting ps = mPackages.get(pkgName);
-                if (ps != null) {
-                    // known/existing package; update in place
-                    ps.setIntentFilterVerificationInfo(ivi);
-                    if (DEBUG_DOMAIN_VERIFICATION) {
-                        Slog.d(TAG, "Restored IVI for existing app " + pkgName
-                                + " status=" + ivi.getStatusString());
-                    }
-                } else {
-                    mRestoredIntentFilterVerifications.put(pkgName, ivi);
-                    if (DEBUG_DOMAIN_VERIFICATION) {
-                        Slog.d(TAG, "Restored IVI for pending app " + pkgName
-                                + " status=" + ivi.getStatusString());
-                    }
-                }
-            } else {
-                PackageManagerService.reportSettingsProblem(Log.WARN,
-                        "Unknown element under <all-intent-filter-verification>: "
-                        + parser.getName());
-                XmlUtils.skipCurrentTag(parser);
-            }
-        }
-    }
-
-    void writeDefaultAppsLPr(TypedXmlSerializer serializer, int userId)
+    void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
             throws IllegalArgumentException, IllegalStateException, IOException {
         serializer.startTag(null, TAG_DEFAULT_APPS);
         String defaultBrowser = mDefaultBrowserApp.get(userId);
@@ -2216,15 +1973,6 @@
                                 ustate.lastDisableAppCaller);
                     }
                 }
-                if (ustate.domainVerificationStatus !=
-                        PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
-                    serializer.attributeInt(null, ATTR_DOMAIN_VERIFICATON_STATE,
-                            ustate.domainVerificationStatus);
-                }
-                if (ustate.appLinkGeneration != 0) {
-                    serializer.attributeInt(null, ATTR_APP_LINK_GENERATION,
-                            ustate.appLinkGeneration);
-                }
                 if (ustate.installReason != PackageManager.INSTALL_REASON_UNKNOWN) {
                     serializer.attributeInt(null, ATTR_INSTALL_REASON, ustate.installReason);
                 }
@@ -2568,22 +2316,7 @@
                 }
             }
 
-            final int numIVIs = mRestoredIntentFilterVerifications.size();
-            if (numIVIs > 0) {
-                if (DEBUG_DOMAIN_VERIFICATION) {
-                    Slog.i(TAG, "Writing restored-ivi entries to packages.xml");
-                }
-                serializer.startTag(null, "restored-ivi");
-                for (int i = 0; i < numIVIs; i++) {
-                    IntentFilterVerificationInfo ivi = mRestoredIntentFilterVerifications.valueAt(i);
-                    writeDomainVerificationsLPr(serializer, ivi);
-                }
-                serializer.endTag(null, "restored-ivi");
-            } else {
-                if (DEBUG_DOMAIN_VERIFICATION) {
-                    Slog.i(TAG, "  no restored IVI entries to write");
-                }
-            }
+            mDomainVerificationManager.writeSettings(serializer);
 
             mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
 
@@ -2957,6 +2690,10 @@
         if (pkg.isPackageLoading()) {
             serializer.attributeBoolean(null, "isLoading", true);
         }
+        serializer.attributeFloat(null, "loadingProgress",
+                pkg.getIncrementalStates().getProgress());
+
+        serializer.attribute(null, "domainSetId", pkg.getDomainSetId().toString());
 
         writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions);
 
@@ -2970,7 +2707,7 @@
         writeSigningKeySetLPr(serializer, pkg.keySetData);
         writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
         writeKeySetAliasesLPr(serializer, pkg.keySetData);
-        writeDomainVerificationsLPr(serializer, pkg.verificationInfo);
+        mDomainVerificationManager.writeLegacySettings(serializer, pkg.name);
         writeMimeGroupLPr(serializer, pkg.mimeGroups);
 
         serializer.endTag(null, "package");
@@ -3101,8 +2838,6 @@
                     if (nname != null && oname != null) {
                         mRenamedPackages.put(nname, oname);
                     }
-                } else if (tagName.equals("restored-ivi")) {
-                    readRestoredIntentFilterVerifications(parser);
                 } else if (tagName.equals("last-platform-version")) {
                     // Upgrade from older XML schema
                     final VersionInfo internal = findOrCreateVersion(
@@ -3144,6 +2879,11 @@
                     ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION);
                     ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION);
                     ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT);
+                } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) {
+                    mDomainVerificationManager.readSettings(parser);
+                } else if (tagName.equals(
+                        DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
+                    mDomainVerificationManager.readLegacySettings(parser);
                 } else {
                     Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
                             + parser.getName());
@@ -3625,9 +3365,15 @@
         if (codePathStr.contains("/priv-app/")) {
             pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
         }
+
+        // When reading a disabled setting, use a disabled domainSetId, which makes it easier to
+        // debug invalid entries. The actual logic for migrating to a new ID is done in other
+        // methods that use DomainVerificationManagerInternal#generateNewId
+        UUID domainSetId = DomainVerificationManagerInternal.DISABLED_ID;
         PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
                 legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
-                versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null);
+                versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserId*/, null, null, null,
+                domainSetId);
         long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
         if (timeStamp == 0) {
             timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3699,6 +3445,8 @@
         boolean installedForceQueryable = false;
         boolean isStartable = false;
         boolean isLoading = false;
+        float loadingProgress = 0;
+        UUID domainSetId;
         try {
             name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
@@ -3717,6 +3465,7 @@
             installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false);
             isStartable = parser.getAttributeBoolean(null, "isStartable", false);
             isLoading = parser.getAttributeBoolean(null, "isLoading", false);
+            loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0);
 
             if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
                 primaryCpuAbiString = legacyCpuAbiString;
@@ -3734,6 +3483,15 @@
             categoryHint = parser.getAttributeInt(null, "categoryHint",
                     ApplicationInfo.CATEGORY_UNDEFINED);
 
+            String domainSetIdString = parser.getAttributeValue(null, "domainSetId");
+
+            if (TextUtils.isEmpty(domainSetIdString)) {
+                // If empty, assume restoring from previous platform version and generate an ID
+                domainSetId = mDomainVerificationManager.generateNewId();
+            } else {
+                domainSetId = UUID.fromString(domainSetIdString);
+            }
+
             systemStr = parser.getAttributeValue(null, "publicFlags");
             if (systemStr != null) {
                 try {
@@ -3805,7 +3563,7 @@
                         legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
                         cpuAbiOverrideString, userId, versionCode, pkgFlags, pkgPrivateFlags,
                         null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/,
-                        null /*mimeGroups*/);
+                        null /*mimeGroups*/, domainSetId);
                 if (PackageManagerService.DEBUG_SETTINGS)
                     Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
                             + userId + " pkg=" + packageSetting);
@@ -3826,7 +3584,7 @@
                             versionCode, pkgFlags, pkgPrivateFlags, sharedUserId,
                             null /*usesStaticLibraries*/,
                             null /*usesStaticLibraryVersions*/,
-                            null /*mimeGroups*/);
+                            null /*mimeGroups*/, domainSetId);
                     packageSetting.setTimeStamp(timeStamp);
                     packageSetting.firstInstallTime = firstInstallTime;
                     packageSetting.lastUpdateTime = lastUpdateTime;
@@ -3864,7 +3622,8 @@
             packageSetting.secondaryCpuAbiString = secondaryCpuAbiString;
             packageSetting.updateAvailable = updateAvailable;
             packageSetting.forceQueryableOverride = installedForceQueryable;
-            packageSetting.incrementalStates = new IncrementalStates(isStartable, isLoading);
+            packageSetting.incrementalStates = new IncrementalStates(isStartable, isLoading,
+                    loadingProgress);
             // Handle legacy string here for single-user mode
             final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
@@ -3940,7 +3699,11 @@
                     packageSetting.installSource =
                             packageSetting.installSource.setInitiatingPackageSignatures(signatures);
                 } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
-                    readDomainVerificationLPw(parser, packageSetting);
+                    IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(parser);
+                    mDomainVerificationManager.addLegacySetting(packageSetting.name, ivi);
+                    if (DEBUG_PARSER) {
+                        Log.d(TAG, "Read domain verification for package: " + ivi.getPackageName());
+                    }
                 } else if (tagName.equals(TAG_MIME_GROUP)) {
                     packageSetting.mimeGroups = readMimeGroupLPw(parser, packageSetting.mimeGroups);
                 } else if (tagName.equals(TAG_USES_STATIC_LIB)) {
@@ -4206,6 +3969,7 @@
         removeCrossProfileIntentFiltersLPw(userId);
 
         mRuntimePermissionsPersistence.onUserRemovedLPw(userId);
+        mDomainVerificationManager.clearUser(userId);
 
         writePackageListLPr();
 
@@ -4814,6 +4578,10 @@
             pw.print(prefix); pw.print("  installerAttributionTag=");
             pw.println(ps.installSource.installerAttributionTag);
         }
+        if (IncrementalManager.isIncrementalPath(ps.getPathString())) {
+            pw.print(prefix); pw.println("  loadingProgress="
+                    + (int) (ps.getIncrementalStates().getProgress() * 100) + "%");
+        }
         if (ps.volumeUuid != null) {
             pw.print(prefix); pw.print("  volumeUuid=");
                     pw.println(ps.volumeUuid);
@@ -4871,7 +4639,7 @@
         }
 
         if (ps.sharedUser == null || permissionNames != null || dumpAll) {
-            dumpInstallPermissionsLPr(pw, prefix + "  ", permissionNames, permissionsState);
+            dumpInstallPermissionsLPr(pw, prefix + "  ", permissionNames, permissionsState, users);
         }
 
         if (dumpAllComponents) {
@@ -5121,9 +4889,12 @@
                     continue;
                 }
 
-                dumpInstallPermissionsLPr(pw, prefix, permissionNames, permissionsState);
+                List<UserInfo> users = getAllUsers(UserManagerService.getInstance());
 
-                for (int userId : UserManagerService.getInstance().getUserIds()) {
+                dumpInstallPermissionsLPr(pw, prefix, permissionNames, permissionsState, users);
+
+                for (UserInfo user : users) {
+                    final int userId = user.id;
                     final int[] gids = mPermissionDataProvider.getGidsForUid(UserHandle.getUid(
                             userId, su.userId));
                     final Collection<PermissionState> permissions =
@@ -5237,31 +5008,55 @@
         }
     }
 
-    void dumpInstallPermissionsLPr(PrintWriter pw, String prefix, ArraySet<String> permissionNames,
-            LegacyPermissionState permissionsState) {
-        Collection<PermissionState> permissionStates = permissionsState.getPermissionStates(
-                UserHandle.USER_SYSTEM);
-        boolean hasInstallPermissions = false;
-        for (PermissionState permissionState : permissionStates) {
-            if (!permissionState.isRuntime()) {
-                hasInstallPermissions = true;
-                break;
-            }
-        }
-        if (hasInstallPermissions) {
-            pw.print(prefix); pw.println("install permissions:");
+    void dumpInstallPermissionsLPr(PrintWriter pw, String prefix,
+            ArraySet<String> filterPermissionNames, LegacyPermissionState permissionsState,
+            List<UserInfo> users) {
+        ArraySet<String> dumpPermissionNames = new ArraySet<>();
+        for (UserInfo user : users) {
+            int userId = user.id;
+            Collection<PermissionState> permissionStates = permissionsState.getPermissionStates(
+                    userId);
             for (PermissionState permissionState : permissionStates) {
                 if (permissionState.isRuntime()) {
                     continue;
                 }
-                if (permissionNames != null
-                        && !permissionNames.contains(permissionState.getName())) {
+                String permissionName = permissionState.getName();
+                if (filterPermissionNames != null
+                        && !filterPermissionNames.contains(permissionName)) {
                     continue;
                 }
-                pw.print(prefix); pw.print("  "); pw.print(permissionState.getName());
-                    pw.print(": granted="); pw.print(permissionState.isGranted());
-                    pw.println(permissionFlagsToString(", flags=",
-                        permissionState.getFlags()));
+                dumpPermissionNames.add(permissionName);
+            }
+        }
+        boolean printedSomething = false;
+        for (String permissionName : dumpPermissionNames) {
+            PermissionState systemPermissionState = permissionsState.getPermissionState(
+                    permissionName, UserHandle.USER_SYSTEM);
+            for (UserInfo user : users) {
+                int userId = user.id;
+                PermissionState permissionState;
+                if (userId == UserHandle.USER_SYSTEM) {
+                    permissionState = systemPermissionState;
+                } else {
+                    permissionState = permissionsState.getPermissionState(permissionName, userId);
+                    if (Objects.equals(permissionState, systemPermissionState)) {
+                        continue;
+                    }
+                }
+                if (!printedSomething) {
+                    pw.print(prefix); pw.println("install permissions:");
+                    printedSomething = true;
+                }
+                pw.print(prefix); pw.print("  "); pw.print(permissionName);
+                pw.print(": granted="); pw.print(
+                        permissionState != null && permissionState.isGranted());
+                pw.print(permissionFlagsToString(", flags=",
+                        permissionState != null ? permissionState.getFlags() : 0));
+                if (userId == UserHandle.USER_SYSTEM) {
+                    pw.println();
+                } else {
+                    pw.print(", userId="); pw.println(userId);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/SettingsXml.java b/services/core/java/com/android/server/pm/SettingsXml.java
new file mode 100644
index 0000000..9588a27
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SettingsXml.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2020 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.pm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Stack;
+
+/**
+ * A very specialized serialization/parsing wrapper around {@link TypedXmlSerializer} and {@link
+ * TypedXmlPullParser} intended for use with PackageManager related settings files.
+ * Assumptions/chosen behaviors:
+ * <ul>
+ *     <li>No namespace support</li>
+ *     <li>Data for a parent object is stored as attributes</li>
+ *     <li>All attribute read methods return a default false, -1, or null</li>
+ *     <li>Default values will not be written</li>
+ *     <li>Children are sub-elements</li>
+ *     <li>Collections are repeated sub-elements, no attribute support for collections</li>
+ * </ul>
+ */
+public class SettingsXml {
+
+    private static final String TAG = "SettingsXml";
+
+    private static final boolean DEBUG_THROW_EXCEPTIONS = false;
+
+    private static final String FEATURE_INDENT =
+            "http://xmlpull.org/v1/doc/features.html#indent-output";
+
+    private static final int DEFAULT_NUMBER = -1;
+
+    public static Serializer serializer(TypedXmlSerializer serializer) {
+        return new Serializer(serializer);
+    }
+
+    public static ReadSection parser(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        return new ReadSectionImpl(parser);
+    }
+
+    public static class Serializer implements AutoCloseable {
+
+        @NonNull
+        private final TypedXmlSerializer mXmlSerializer;
+
+        private final WriteSectionImpl mWriteSection;
+
+        private Serializer(TypedXmlSerializer serializer) {
+            mXmlSerializer = serializer;
+            mWriteSection = new WriteSectionImpl(mXmlSerializer);
+        }
+
+        public WriteSection startSection(@NonNull String sectionName) throws IOException {
+            return mWriteSection.startSection(sectionName);
+        }
+
+        @Override
+        public void close() throws IOException {
+            mWriteSection.closeCompletely();
+            mXmlSerializer.endDocument();
+        }
+    }
+
+    public interface ReadSection extends AutoCloseable {
+
+        @NonNull
+        String getName();
+
+        @NonNull
+        String getDescription();
+
+        boolean has(String attrName);
+
+        @Nullable
+        String getString(String attrName);
+
+        /**
+         * @return value as String or {@param defaultValue} if doesn't exist
+         */
+        @NonNull
+        String getString(String attrName, @NonNull String defaultValue);
+
+        /**
+         * @return value as boolean or false if doesn't exist
+         */
+        boolean getBoolean(String attrName);
+
+        /**
+         * @return value as boolean or {@param defaultValue} if doesn't exist
+         */
+        boolean getBoolean(String attrName, boolean defaultValue);
+
+        /**
+         * @return value as int or {@link #DEFAULT_NUMBER} if doesn't exist
+         */
+        int getInt(String attrName);
+
+        /**
+         * @return value as int or {@param defaultValue} if doesn't exist
+         */
+        int getInt(String attrName, int defaultValue);
+
+        /**
+         * @return value as long or {@link #DEFAULT_NUMBER} if doesn't exist
+         */
+        long getLong(String attrName);
+
+        /**
+         * @return value as long or {@param defaultValue} if doesn't exist
+         */
+        long getLong(String attrName, int defaultValue);
+
+        ChildSection children();
+    }
+
+    /**
+     * <pre><code>
+     * ChildSection child = parentSection.children();
+     * while (child.moveToNext(TAG_CHILD)) {
+     *     String readValue = child.getString(...);
+     *     ...
+     * }
+     * </code></pre>
+     */
+    public interface ChildSection extends ReadSection {
+        boolean moveToNext();
+
+        boolean moveToNext(@NonNull String expectedChildTagName);
+    }
+
+    public static class ReadSectionImpl implements ChildSection {
+
+        @Nullable
+        private final InputStream mInput;
+
+        @NonNull
+        private final TypedXmlPullParser mParser;
+
+        @NonNull
+        private final Stack<Integer> mDepthStack = new Stack<>();
+
+        public ReadSectionImpl(@NonNull InputStream input)
+                throws IOException, XmlPullParserException {
+            mInput = input;
+            mParser = Xml.newFastPullParser();
+            mParser.setInput(mInput, StandardCharsets.UTF_8.name());
+            moveToFirstTag();
+        }
+
+        public ReadSectionImpl(@NonNull TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            mInput = null;
+            mParser = parser;
+            moveToFirstTag();
+        }
+
+        private void moveToFirstTag() throws IOException, XmlPullParserException {
+            // Move to first tag
+            int type;
+            //noinspection StatementWithEmptyBody
+            while ((type = mParser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+            }
+        }
+
+        @NonNull
+        @Override
+        public String getName() {
+            return mParser.getName();
+        }
+
+        @NonNull
+        @Override
+        public String getDescription() {
+            return mParser.getPositionDescription();
+        }
+
+        @Override
+        public boolean has(String attrName) {
+            return mParser.getAttributeValue(null, attrName) != null;
+        }
+
+        @Nullable
+        @Override
+        public String getString(String attrName) {
+            return mParser.getAttributeValue(null, attrName);
+        }
+
+        @NonNull
+        @Override
+        public String getString(String attrName, @NonNull String defaultValue) {
+            String value = mParser.getAttributeValue(null, attrName);
+            if (value == null) {
+                return defaultValue;
+            }
+            return value;
+        }
+
+        @Override
+        public boolean getBoolean(String attrName) {
+            return getBoolean(attrName, false);
+        }
+
+        @Override
+        public boolean getBoolean(String attrName, boolean defaultValue) {
+            return mParser.getAttributeBoolean(null, attrName, defaultValue);
+        }
+
+        @Override
+        public int getInt(String attrName) {
+            return getInt(attrName, DEFAULT_NUMBER);
+        }
+
+        @Override
+        public int getInt(String attrName, int defaultValue) {
+            return mParser.getAttributeInt(null, attrName, defaultValue);
+        }
+
+        @Override
+        public long getLong(String attrName) {
+            return getLong(attrName, DEFAULT_NUMBER);
+        }
+
+        @Override
+        public long getLong(String attrName, int defaultValue) {
+            return mParser.getAttributeLong(null, attrName, defaultValue);
+        }
+
+        @Override
+        public ChildSection children() {
+            mDepthStack.push(mParser.getDepth());
+            return this;
+        }
+
+        @Override
+        public boolean moveToNext() {
+            return moveToNextInternal(null);
+        }
+
+        @Override
+        public boolean moveToNext(@NonNull String expectedChildTagName) {
+            return moveToNextInternal(expectedChildTagName);
+        }
+
+        private boolean moveToNextInternal(@Nullable String expectedChildTagName) {
+            try {
+                int depth = mDepthStack.peek();
+                boolean hasTag = false;
+                int type;
+                while (!hasTag
+                        && (type = mParser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || mParser.getDepth() > depth)) {
+                    if (type != XmlPullParser.START_TAG) {
+                        continue;
+                    }
+
+                    if (expectedChildTagName != null
+                            && !expectedChildTagName.equals(mParser.getName())) {
+                        continue;
+                    }
+
+                    hasTag = true;
+                }
+
+                if (!hasTag) {
+                    mDepthStack.pop();
+                }
+
+                return hasTag;
+            } catch (Exception ignored) {
+                return false;
+            }
+        }
+
+        @Override
+        public void close() throws Exception {
+            if (mDepthStack.isEmpty()) {
+                Slog.wtf(TAG, "Children depth stack was not empty, data may have been lost",
+                        new Exception());
+            }
+            if (mInput != null) {
+                mInput.close();
+            }
+        }
+    }
+
+    public interface WriteSection extends AutoCloseable {
+
+        WriteSection startSection(@NonNull String sectionName) throws IOException;
+
+        WriteSection attribute(String attrName, @Nullable String value) throws IOException;
+
+        WriteSection attribute(String attrName, int value) throws IOException;
+
+        WriteSection attribute(String attrName, long value) throws IOException;
+
+        WriteSection attribute(String attrName, boolean value) throws IOException;
+
+        @Override
+        void close() throws IOException;
+
+        void finish() throws IOException;
+    }
+
+    private static class WriteSectionImpl implements WriteSection {
+
+        @NonNull
+        private final TypedXmlSerializer mXmlSerializer;
+
+        @NonNull
+        private final Stack<String> mTagStack = new Stack<>();
+
+        private WriteSectionImpl(@NonNull TypedXmlSerializer xmlSerializer) {
+            mXmlSerializer = xmlSerializer;
+        }
+
+        @Override
+        public WriteSection startSection(@NonNull String sectionName) throws IOException {
+            // Try to start the tag first before we push it to the stack
+            mXmlSerializer.startTag(null, sectionName);
+            mTagStack.push(sectionName);
+            return this;
+        }
+
+        @Override
+        public WriteSection attribute(String attrName, String value) throws IOException {
+            if (value != null) {
+                mXmlSerializer.attribute(null, attrName, value);
+            }
+            return this;
+        }
+
+        @Override
+        public WriteSection attribute(String attrName, int value) throws IOException {
+            if (value != DEFAULT_NUMBER) {
+                mXmlSerializer.attributeInt(null, attrName, value);
+            }
+            return this;
+        }
+
+        @Override
+        public WriteSection attribute(String attrName, long value) throws IOException {
+            if (value != DEFAULT_NUMBER) {
+                mXmlSerializer.attributeLong(null, attrName, value);
+            }
+            return this;
+        }
+
+        @Override
+        public WriteSection attribute(String attrName, boolean value) throws IOException {
+            if (value) {
+                mXmlSerializer.attributeBoolean(null, attrName, value);
+            }
+            return this;
+        }
+
+        @Override
+        public void finish() throws IOException {
+            close();
+        }
+
+        @Override
+        public void close() throws IOException {
+            mXmlSerializer.endTag(null, mTagStack.pop());
+        }
+
+        private void closeCompletely() throws IOException {
+            if (DEBUG_THROW_EXCEPTIONS && mTagStack != null && !mTagStack.isEmpty()) {
+                throw new IllegalStateException(
+                        "tag stack is not empty when closing, contains " + mTagStack);
+            } else if (mTagStack != null) {
+                while (!mTagStack.isEmpty()) {
+                    close();
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 89729b5..9b092c0 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1535,6 +1535,27 @@
         pw.println(")");
     }
 
+    public void dumpShortcuts(@NonNull PrintWriter pw, int matchFlags) {
+        final boolean matchDynamic = (matchFlags & ShortcutManager.FLAG_MATCH_DYNAMIC) != 0;
+        final boolean matchPinned = (matchFlags & ShortcutManager.FLAG_MATCH_PINNED) != 0;
+        final boolean matchManifest = (matchFlags & ShortcutManager.FLAG_MATCH_MANIFEST) != 0;
+        final boolean matchCached = (matchFlags & ShortcutManager.FLAG_MATCH_CACHED) != 0;
+
+        final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0)
+                | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0)
+                | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
+                | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
+
+        final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
+        final int size = shortcuts.size();
+        for (int i = 0; i < size; i++) {
+            final ShortcutInfo si = shortcuts.valueAt(i);
+            if ((si.getFlags() & shortcutFlags) != 0) {
+                pw.println(si.toDumpString(""));
+            }
+        }
+    }
+
     @Override
     public JSONObject dumpCheckin(boolean clear) throws JSONException {
         final JSONObject result = super.dumpCheckin(clear);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 3c4457d..532e78c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -4556,6 +4556,10 @@
 
         private int mUserId = UserHandle.USER_SYSTEM;
 
+        private int mShortcutMatchFlags = ShortcutManager.FLAG_MATCH_CACHED
+                | ShortcutManager.FLAG_MATCH_DYNAMIC | ShortcutManager.FLAG_MATCH_MANIFEST
+                | ShortcutManager.FLAG_MATCH_PINNED;
+
         private void parseOptionsLocked(boolean takeUser)
                 throws CommandException {
             String opt;
@@ -4571,6 +4575,9 @@
                             break;
                         }
                         // fallthrough
+                    case "--flags":
+                        mShortcutMatchFlags = Integer.parseInt(getNextArgRequired());
+                        break;
                     default:
                         throw new CommandException("Unknown option: " + opt);
                 }
@@ -4606,6 +4613,9 @@
                     case "clear-shortcuts":
                         handleClearShortcuts();
                         break;
+                    case "get-shortcuts":
+                        handleGetShortcuts();
+                        break;
                     case "verify-states": // hidden command to verify various internal states.
                         handleVerifyStates();
                         break;
@@ -4649,6 +4659,9 @@
             pw.println("cmd shortcut clear-shortcuts [--user USER_ID] PACKAGE");
             pw.println("    Remove all shortcuts from a package, including pinned shortcuts");
             pw.println();
+            pw.println("cmd shortcut get-shortcuts [--user USER_ID] [--flags FLAGS] PACKAGE");
+            pw.println("    Show the shortcuts for a package that match the given flags");
+            pw.println();
         }
 
         private void handleResetThrottling() throws CommandException {
@@ -4723,6 +4736,24 @@
             }
         }
 
+        private void handleGetShortcuts() throws CommandException {
+            synchronized (mLock) {
+                parseOptionsLocked(/* takeUser =*/ true);
+                final String packageName = getNextArgRequired();
+
+                Slog.i(TAG, "cmd: handleGetShortcuts: user=" + mUserId + ", flags="
+                        + mShortcutMatchFlags + ", package=" + packageName);
+
+                final ShortcutUser user = ShortcutService.this.getUserShortcutsLocked(mUserId);
+                final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName);
+                if (p == null) {
+                    return;
+                }
+
+                p.dumpShortcuts(getOutPrintWriter(), mShortcutMatchFlags);
+            }
+        }
+
         private void handleVerifyStates() throws CommandException {
             try {
                 verifyStatesForce(); // This will throw when there's an issue.
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index c182d68..c17d2e4 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -298,4 +298,11 @@
      * Gets all {@link UserInfo UserInfos}.
      */
     public abstract @NonNull UserInfo[] getUserInfos();
+
+    /**
+     * Sets all default cross profile intent filters between {@code parentUserId} and
+     * {@code profileUserId}.
+     */
+    public abstract void setDefaultCrossProfileIntentFilters(
+            @UserIdInt int parentUserId, @UserIdInt int profileUserId);
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 314510b..753f22d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -47,6 +47,7 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.os.Binder;
@@ -79,6 +80,7 @@
 import android.os.UserManager.EnforcingUser;
 import android.os.UserManager.QuietModeFlag;
 import android.os.storage.StorageManager;
+import android.provider.Settings;
 import android.security.GateKeeper;
 import android.service.gatekeeper.IGateKeeperService;
 import android.stats.devicepolicy.DevicePolicyEnums;
@@ -92,6 +94,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.TypedValue;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
 import android.util.Xml;
@@ -106,6 +109,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.BundleUtils;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.SystemService;
@@ -139,6 +143,7 @@
 import java.util.Set;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Service for {@link UserManager}.
@@ -461,6 +466,26 @@
         }
     };
 
+    /**
+     * Cache the owner name string, since it could be read repeatedly on a critical code path
+     * but hit by slow IO. This could be eliminated once we have the cached UserInfo in place.
+     */
+    private final AtomicReference<String> mOwnerName = new AtomicReference<>();
+
+    private final TypedValue mOwnerNameTypedValue = new TypedValue();
+
+    private final Configuration mLastConfiguration = new Configuration();
+
+    private final BroadcastReceiver mConfigurationChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) {
+                return;
+            }
+            invalidateOwnerNameIfNecessary(context.getResources(), false /* forceUpdate */);
+        }
+    };
+
     // TODO(b/161915546): remove once userWithName() is fixed / removed
     // Use to debug / dump when user 0 is allocated at userWithName()
     public static final boolean DBG_ALLOCATION = false; // DO NOT SUBMIT WITH TRUE
@@ -636,6 +661,7 @@
         mHandler = new MainHandler();
         mUserDataPreparer = userDataPreparer;
         mUserTypes = UserTypeFactory.getUserTypes();
+        invalidateOwnerNameIfNecessary(context.getResources(), true /* forceUpdate */);
         synchronized (mPackagesLock) {
             mUsersDir = new File(dataDir, USER_INFO_DIR);
             mUsersDir.mkdirs();
@@ -669,6 +695,10 @@
         mContext.registerReceiver(mDisableQuietModeCallback,
                 new IntentFilter(ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK),
                 null, mHandler);
+
+        mContext.registerReceiver(mConfigurationChangeReceiver,
+                new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
+                null, mHandler);
     }
 
     /**
@@ -1932,11 +1962,11 @@
         final Bundle global = mDevicePolicyGlobalUserRestrictions.mergeAll();
         final RestrictionsSet local = getDevicePolicyLocalRestrictionsForTargetUserLR(userId);
 
-        if (UserRestrictionsUtils.isEmpty(global) && local.isEmpty()) {
+        if (BundleUtils.isEmpty(global) && local.isEmpty()) {
             // Common case first.
             return baseRestrictions;
         }
-        final Bundle effective = UserRestrictionsUtils.clone(baseRestrictions);
+        final Bundle effective = BundleUtils.clone(baseRestrictions);
         UserRestrictionsUtils.merge(effective, global);
         UserRestrictionsUtils.merge(effective, local.mergeAll());
 
@@ -2077,7 +2107,7 @@
     @Override
     public Bundle getUserRestrictions(@UserIdInt int userId) {
         checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserRestrictions");
-        return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
+        return BundleUtils.clone(getEffectiveUserRestrictions(userId));
     }
 
     @Override
@@ -2101,7 +2131,7 @@
         synchronized (mRestrictionsLock) {
             // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
             // a copy.
-            final Bundle newRestrictions = UserRestrictionsUtils.clone(
+            final Bundle newRestrictions = BundleUtils.clone(
                     mBaseUserRestrictions.getRestrictions(userId));
             newRestrictions.putBoolean(key, value);
 
@@ -2699,7 +2729,7 @@
         if (userVersion < 7) {
             // Previously only one user could enforce global restrictions, now it is per-user.
             synchronized (mRestrictionsLock) {
-                if (!UserRestrictionsUtils.isEmpty(oldGlobalUserRestrictions)
+                if (!BundleUtils.isEmpty(oldGlobalUserRestrictions)
                         && mDeviceOwnerUserId != UserHandle.USER_NULL) {
                     mDevicePolicyGlobalUserRestrictions.updateRestrictions(
                             mDeviceOwnerUserId, oldGlobalUserRestrictions);
@@ -2851,7 +2881,16 @@
     }
 
     private String getOwnerName() {
-        return mContext.getResources().getString(com.android.internal.R.string.owner_name);
+        return mOwnerName.get();
+    }
+
+    private void invalidateOwnerNameIfNecessary(@NonNull Resources res, boolean forceUpdate) {
+        final int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
+        if (forceUpdate || (configChanges & mOwnerNameTypedValue.changingConfigurations) != 0) {
+            res.getValue(com.android.internal.R.string.owner_name, mOwnerNameTypedValue, true);
+            final CharSequence ownerName = mOwnerNameTypedValue.coerceToString();
+            mOwnerName.set(ownerName != null ? ownerName.toString() : null);
+        }
     }
 
     private void scheduleWriteUser(UserData userData) {
@@ -3525,6 +3564,9 @@
             t.traceBegin("PM.onNewUserCreated-" + userId);
             mPm.onNewUserCreated(userId, /* convertedFromPreCreated= */ false);
             t.traceEnd();
+            applyDefaultUserSettings(userTypeDetails, userId);
+            setDefaultCrossProfileIntentFilters(userId, userTypeDetails, restrictions, parentId);
+
             if (preCreate) {
                 // Must start user (which will be stopped right away, through
                 // UserController.finishUserUnlockedCompleted) so services can properly
@@ -3560,6 +3602,78 @@
         return userInfo;
     }
 
+    private void applyDefaultUserSettings(UserTypeDetails userTypeDetails, @UserIdInt int userId) {
+        final Bundle systemSettings = userTypeDetails.getDefaultSystemSettings();
+        final Bundle secureSettings = userTypeDetails.getDefaultSecureSettings();
+        if (systemSettings.isEmpty() && secureSettings.isEmpty()) {
+            return;
+        }
+
+        final int systemSettingsSize = systemSettings.size();
+        final String[] systemSettingsArray = systemSettings.keySet().toArray(
+                new String[systemSettingsSize]);
+        for (int i = 0; i < systemSettingsSize; i++) {
+            final String setting = systemSettingsArray[i];
+            if (!Settings.System.putStringForUser(
+                    mContext.getContentResolver(), setting, systemSettings.getString(setting),
+                    userId)) {
+                Slog.e(LOG_TAG, "Failed to insert default system setting: " + setting);
+            }
+        }
+
+        final int secureSettingsSize = secureSettings.size();
+        final String[] secureSettingsArray = secureSettings.keySet().toArray(
+                new String[secureSettingsSize]);
+        for (int i = 0; i < secureSettingsSize; i++) {
+            final String setting = secureSettingsArray[i];
+            if (!Settings.Secure.putStringForUser(
+                    mContext.getContentResolver(), setting, secureSettings.getString(setting),
+                    userId)) {
+                Slog.e(LOG_TAG, "Failed to insert default secure setting: " + setting);
+            }
+        }
+    }
+
+    /**
+     * Sets all default cross profile intent filters between {@code parentUserId} and
+     * {@code profileUserId}, does nothing if {@code userType} is not a profile.
+     */
+    private void setDefaultCrossProfileIntentFilters(
+            @UserIdInt int profileUserId, UserTypeDetails profileDetails,
+            Bundle profileRestrictions, @UserIdInt int parentUserId) {
+        if (profileDetails == null || !profileDetails.isProfile()) {
+            return;
+        }
+        final List<DefaultCrossProfileIntentFilter> filters =
+                profileDetails.getDefaultCrossProfileIntentFilters();
+        if (filters.isEmpty()) {
+            return;
+        }
+
+        // Skip filters that allow data to be shared into the profile, if admin has disabled it.
+        final boolean disallowSharingIntoProfile =
+                profileRestrictions.getBoolean(
+                        UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+                        /* defaultValue = */ false);
+        final int size = profileDetails.getDefaultCrossProfileIntentFilters().size();
+        for (int i = 0; i < size; i++) {
+            final DefaultCrossProfileIntentFilter filter =
+                    profileDetails.getDefaultCrossProfileIntentFilters().get(i);
+            if (disallowSharingIntoProfile && filter.letsPersonalDataIntoProfile) {
+                continue;
+            }
+            if (filter.direction == DefaultCrossProfileIntentFilter.Direction.TO_PARENT) {
+                mPm.addCrossProfileIntentFilter(
+                        filter.filter, mContext.getOpPackageName(), profileUserId, parentUserId,
+                        filter.flags);
+            } else {
+                mPm.addCrossProfileIntentFilter(
+                        filter.filter, mContext.getOpPackageName(), parentUserId, profileUserId,
+                        filter.flags);
+            }
+        }
+    }
+
     /**
      * Finds and converts a previously pre-created user into a regular user, if possible.
      *
@@ -3843,6 +3957,7 @@
      */
     @Override
     public boolean removeUser(@UserIdInt int userId) {
+        Slog.i(LOG_TAG, "removeUser u" + userId);
         checkManageOrCreateUsersPermission("Only the system can remove users");
 
         final String restriction = getUserRemovalRestriction(userId);
@@ -5424,6 +5539,15 @@
                 return allInfos;
             }
         }
+
+        @Override
+        public void setDefaultCrossProfileIntentFilters(
+                @UserIdInt int parentUserId, @UserIdInt int profileUserId) {
+            final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(profileUserId);
+            final Bundle restrictions = getEffectiveUserRestrictions(profileUserId);
+            UserManagerService.this.setDefaultCrossProfileIntentFilters(
+                    profileUserId, userTypeDetails, restrictions, parentUserId);
+        }
     }
 
     /**
@@ -5688,8 +5812,8 @@
 
         // merge existing base restrictions with the new type's default restrictions
         synchronized (mRestrictionsLock) {
-            if (!UserRestrictionsUtils.isEmpty(newUserType.getDefaultRestrictions())) {
-                final Bundle newRestrictions = UserRestrictionsUtils.clone(
+            if (!BundleUtils.isEmpty(newUserType.getDefaultRestrictions())) {
+                final Bundle newRestrictions = BundleUtils.clone(
                         mBaseUserRestrictions.getRestrictions(userInfo.id));
                 UserRestrictionsUtils.merge(newRestrictions,
                         newUserType.getDefaultRestrictions());
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 51dff40..ff90043 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -44,6 +44,7 @@
 
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
+import com.android.server.BundleUtils;
 import com.android.server.LocalServices;
 
 import com.google.android.collect.Sets;
@@ -384,10 +385,6 @@
         return in != null ? in : new Bundle();
     }
 
-    public static boolean isEmpty(@Nullable Bundle in) {
-        return (in == null) || (in.size() == 0);
-    }
-
     /**
      * Returns {@code true} if given bundle is not null and contains {@code true} for a given
      * restriction.
@@ -396,17 +393,6 @@
         return in != null && in.getBoolean(restriction);
     }
 
-    /**
-     * Creates a copy of the {@code in} Bundle.  If {@code in} is null, it'll return an empty
-     * bundle.
-     *
-     * <p>The resulting {@link Bundle} is always writable. (i.e. it won't return
-     * {@link Bundle#EMPTY})
-     */
-    public static @NonNull Bundle clone(@Nullable Bundle in) {
-        return (in != null) ? new Bundle(in) : new Bundle();
-    }
-
     public static void merge(@NonNull Bundle dest, @Nullable Bundle in) {
         Objects.requireNonNull(dest);
         Preconditions.checkArgument(dest != in);
@@ -483,10 +469,10 @@
         if (a == b) {
             return true;
         }
-        if (isEmpty(a)) {
-            return isEmpty(b);
+        if (BundleUtils.isEmpty(a)) {
+            return BundleUtils.isEmpty(b);
         }
-        if (isEmpty(b)) {
+        if (BundleUtils.isEmpty(b)) {
             return false;
         }
         for (String key : a.keySet()) {
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 5fa46b9..17ce386 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -28,8 +28,12 @@
 import android.os.UserManager;
 
 import com.android.internal.util.Preconditions;
+import com.android.server.BundleUtils;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Contains the details about a multiuser "user type", such as a
@@ -82,6 +86,24 @@
      */
     private final @Nullable Bundle mDefaultRestrictions;
 
+    /**
+     * List of {@link android.provider.Settings.System} to apply by default to newly created users
+     * of this type.
+     */
+    private final @Nullable Bundle mDefaultSystemSettings;
+
+    /**
+     * List of {@link android.provider.Settings.Secure} to apply by default to newly created users
+     * of this type.
+     */
+    private final @Nullable Bundle mDefaultSecureSettings;
+
+    /**
+     * List of {@link DefaultCrossProfileIntentFilter} to allow by default for newly created
+     * profiles.
+     */
+    private final @Nullable List<DefaultCrossProfileIntentFilter> mDefaultCrossProfileIntentFilters;
+
 
     // Fields for profiles only, controlling the nature of their badges.
     // All badge information should be set if {@link #hasBadge()} is true.
@@ -133,7 +155,10 @@
             int iconBadge, int badgePlain, int badgeNoBackground,
             @Nullable int[] badgeLabels, @Nullable int[] badgeColors,
             @Nullable int[] darkThemeBadgeColors,
-            @Nullable Bundle defaultRestrictions) {
+            @Nullable Bundle defaultRestrictions,
+            @Nullable Bundle defaultSystemSettings,
+            @Nullable Bundle defaultSecureSettings,
+            @Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters) {
         this.mName = name;
         this.mEnabled = enabled;
         this.mMaxAllowed = maxAllowed;
@@ -141,6 +166,9 @@
         this.mBaseType = baseType;
         this.mDefaultUserInfoPropertyFlags = defaultUserInfoPropertyFlags;
         this.mDefaultRestrictions = defaultRestrictions;
+        this.mDefaultSystemSettings = defaultSystemSettings;
+        this.mDefaultSecureSettings = defaultSecureSettings;
+        this.mDefaultCrossProfileIntentFilters = defaultCrossProfileIntentFilters;
 
         this.mIconBadge = iconBadge;
         this.mBadgePlain = badgePlain;
@@ -263,9 +291,9 @@
         return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
     }
 
-    /** Returns a Bundle representing the default user restrictions. */
+    /** Returns a {@link Bundle} representing the default user restrictions. */
     @NonNull Bundle getDefaultRestrictions() {
-        return UserRestrictionsUtils.clone(mDefaultRestrictions);
+        return BundleUtils.clone(mDefaultRestrictions);
     }
 
     /** Adds the default user restrictions to the given bundle of restrictions. */
@@ -273,6 +301,24 @@
         UserRestrictionsUtils.merge(currentRestrictions, mDefaultRestrictions);
     }
 
+    /** Returns a {@link Bundle} representing the default system settings. */
+    @NonNull Bundle getDefaultSystemSettings() {
+        return BundleUtils.clone(mDefaultSystemSettings);
+    }
+
+    /** Returns a {@link Bundle} representing the default secure settings. */
+    @NonNull Bundle getDefaultSecureSettings() {
+        return BundleUtils.clone(mDefaultSecureSettings);
+    }
+
+    /** Returns a list of default cross profile intent filters. */
+    @NonNull List<DefaultCrossProfileIntentFilter> getDefaultCrossProfileIntentFilters() {
+        return mDefaultCrossProfileIntentFilters != null
+                ? new ArrayList<>(mDefaultCrossProfileIntentFilters)
+                : Collections.emptyList();
+    }
+
+
     /** Dumps details of the UserTypeDetails. Do not parse this. */
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("mName: "); pw.println(mName);
@@ -325,6 +371,10 @@
         private int mMaxAllowedPerParent = UNLIMITED_NUMBER_OF_USERS;
         private int mDefaultUserInfoPropertyFlags = 0;
         private @Nullable Bundle mDefaultRestrictions = null;
+        private @Nullable Bundle mDefaultSystemSettings = null;
+        private @Nullable Bundle mDefaultSecureSettings = null;
+        private @Nullable List<DefaultCrossProfileIntentFilter> mDefaultCrossProfileIntentFilters =
+                null;
         private boolean mEnabled = true;
         private int mLabel = Resources.ID_NULL;
         private @Nullable int[] mBadgeLabels = null;
@@ -407,6 +457,22 @@
             return this;
         }
 
+        public Builder setDefaultSystemSettings(@Nullable Bundle settings) {
+            mDefaultSystemSettings = settings;
+            return this;
+        }
+
+        public Builder setDefaultSecureSettings(@Nullable Bundle settings) {
+            mDefaultSecureSettings = settings;
+            return this;
+        }
+
+        public Builder setDefaultCrossProfileIntentFilters(
+                @Nullable List<DefaultCrossProfileIntentFilter> intentFilters) {
+            mDefaultCrossProfileIntentFilters = intentFilters;
+            return this;
+        }
+
         @UserInfoFlag int getBaseType() {
             return mBaseType;
         }
@@ -425,17 +491,28 @@
                 Preconditions.checkArgument(mBadgeColors != null && mBadgeColors.length != 0,
                         "UserTypeDetails " + mName + " has badge but no badgeColors.");
             }
+            if (!isProfile()) {
+                Preconditions.checkArgument(mDefaultCrossProfileIntentFilters == null
+                                || mDefaultCrossProfileIntentFilters.isEmpty(),
+                        "UserTypeDetails %s has a non empty "
+                                + "defaultCrossProfileIntentFilters", mName);
+            }
             return new UserTypeDetails(mName, mEnabled, mMaxAllowed, mBaseType,
                     mDefaultUserInfoPropertyFlags, mLabel, mMaxAllowedPerParent,
                     mIconBadge, mBadgePlain, mBadgeNoBackground, mBadgeLabels, mBadgeColors,
                     mDarkThemeBadgeColors == null ? mBadgeColors : mDarkThemeBadgeColors,
-                    mDefaultRestrictions);
+                    mDefaultRestrictions, mDefaultSystemSettings, mDefaultSecureSettings,
+                    mDefaultCrossProfileIntentFilters);
         }
 
         private boolean hasBadge() {
             return mIconBadge != Resources.ID_NULL;
         }
 
+        private boolean isProfile() {
+            return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
+        }
+
         // TODO(b/143784345): Refactor this when we clean up UserInfo.
         private boolean hasValidBaseType() {
             return mBaseType == UserInfo.FLAG_FULL
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 1ffbf60..6aac0b2 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -133,7 +133,9 @@
                         com.android.internal.R.color.profile_badge_1_dark,
                         com.android.internal.R.color.profile_badge_2_dark,
                         com.android.internal.R.color.profile_badge_3_dark)
-                .setDefaultRestrictions(null);
+                .setDefaultRestrictions(getDefaultManagedProfileRestrictions())
+                .setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
+                .setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter());
     }
 
     /**
@@ -256,12 +258,35 @@
         return restrictions;
     }
 
+    private static Bundle getDefaultManagedProfileRestrictions() {
+        final Bundle restrictions = new Bundle();
+        restrictions.putBoolean(UserManager.DISALLOW_WALLPAPER, true);
+        return restrictions;
+    }
+
+    private static Bundle getDefaultManagedProfileSecureSettings() {
+        // Only add String values to the bundle, settings are written as Strings eventually
+        final Bundle settings = new Bundle();
+        settings.putString(
+                android.provider.Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH, "1");
+        settings.putString(
+                android.provider.Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED, "1");
+        return settings;
+    }
+
+    private static List<DefaultCrossProfileIntentFilter>
+            getDefaultManagedCrossProfileIntentFilter() {
+        return DefaultCrossProfileIntentFiltersUtils.getDefaultManagedProfileFilters();
+    }
+
     /**
      * Reads the given xml parser to obtain device user-type customization, and updates the given
      * map of {@link UserTypeDetails.Builder}s accordingly.
      * <p>
      * The xml file can specify the attributes according to the set... methods below.
      */
+    // TODO(b/176973369): Add parsing logic to support custom settings/filters
+    //  in config_user_types.xml
     @VisibleForTesting
     static void customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders,
             XmlResourceParser parser) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 9dde7df..629f120 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -406,7 +406,8 @@
             ParsedProcess proc = procs.get(key);
             retProcs.put(proc.getName(),
                     new ProcessInfo(proc.getName(), new ArraySet<>(proc.getDeniedPermissions()),
-                            proc.getGwpAsanMode()));
+                            proc.getGwpAsanMode(), proc.getMemtagMode(),
+                            proc.getNativeHeapZeroInit()));
         }
         return retProcs;
     }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 851ddd1..e8be9b6 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -19,6 +19,7 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
@@ -33,6 +34,7 @@
 import android.os.Build;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.permission.PermissionManager;
 import android.util.DisplayMetrics;
 import android.util.Slog;
 
@@ -42,6 +44,7 @@
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import java.io.File;
+import java.util.List;
 
 /**
  * The v2 of {@link PackageParser} for use when parsing is initiated in the server and must
@@ -118,10 +121,15 @@
             displayMetrics.setToDefaults();
         }
 
+        PermissionManager permissionManager = ActivityThread.currentApplication()
+                .getSystemService(PermissionManager.class);
+        List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
+                .getSplitPermissions();
+
         mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
 
         parsingUtils = new ParsingPackageUtils(onlyCoreApps, separateProcesses, displayMetrics,
-                callback);
+                splitPermissions, callback);
 
         ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
             ApplicationInfo appInfo = mSharedAppInfo.get();
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
index a13680a..471a4d3 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java
@@ -24,6 +24,7 @@
 import android.content.pm.PackageParser;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.parsing.ParsingPackageRead;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.content.pm.parsing.component.ParsedPermissionGroup;
@@ -56,7 +57,7 @@
 
     /**
      * The names of packages to adopt ownership of permissions from, parsed under
-     * {@link PackageParser#TAG_ADOPT_PERMISSIONS}.
+     * {@link ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
      * @see R.styleable#AndroidManifestOriginalPackage_name
      */
     @NonNull
@@ -84,7 +85,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
-     * {@link PackageParser#TAG_KEY_SETS}.
+     * {@link ParsingPackageUtils#TAG_KEY_SETS}.
      * @see R.styleable#AndroidManifestKeySet
      * @see R.styleable#AndroidManifestPublicKey
      */
@@ -230,7 +231,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in
-     * {@link PackageParser#TAG_KEY_SETS}.
+     * {@link ParsingPackageUtils#TAG_KEY_SETS}.
      * @see R.styleable#AndroidManifestUpgradeKeySet
      */
     @NonNull
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index ab25a7c..37dfea4 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -21,12 +21,12 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.VersionedPackage;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.parsing.ParsingPackageRead;
+import android.content.pm.parsing.ParsingPackageUtils;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedProvider;
@@ -233,7 +233,7 @@
     }
 
     public static int getIcon(ParsingPackageRead pkg) {
-        return (PackageParser.sUseRoundIcon && pkg.getRoundIconRes() != 0)
+        return (ParsingPackageUtils.sUseRoundIcon && pkg.getRoundIconRes() != 0)
                 ? pkg.getRoundIconRes() : pkg.getIconRes();
     }
 
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
index 92f22a4..e8eae47 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionState.java
@@ -104,6 +104,26 @@
     }
 
     /**
+     * Get the permission state for a permission and a user.
+     *
+     * @param permissionName the permission name
+     * @param userId the user ID
+     * @return the permission state
+     *
+     * @hide
+     */
+    @Nullable
+    public PermissionState getPermissionState(@NonNull String permissionName,
+            @UserIdInt int userId) {
+        checkUserId(userId);
+        UserState userState = mUserStates.get(userId);
+        if (userState == null) {
+            return null;
+        }
+        return userState.getPermissionState(permissionName);
+    }
+
+    /**
      * Put a permission state for a user.
      *
      * @param permissionState the permission state
@@ -142,10 +162,10 @@
     }
 
     /**
-     * Get all the runtime permission states for a user.
+     * Get all the permission states for a user.
      *
      * @param userId the user ID
-     * @return the runtime permission states
+     * @return the permission states
      */
     @NonNull
     public Collection<PermissionState> getPermissionStates(@UserIdInt int userId) {
@@ -301,5 +321,25 @@
         public int getFlags() {
             return mFlags;
         }
+
+        @Override
+        public boolean equals(@Nullable Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (object == null || getClass() != object.getClass()) {
+                return false;
+            }
+            PermissionState that = (PermissionState) object;
+            return mRuntime == that.mRuntime
+                    && mGranted == that.mGranted
+                    && mFlags == that.mFlags
+                    && Objects.equals(mName, that.mName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mName, mRuntime, mGranted, mFlags);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 8c31d88..aff87111 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -3356,11 +3356,12 @@
         //     - or its signing certificate was rotated from the source package's certificate
         //     - or its signing certificate is a previous signing certificate of the defining
         //       package, and the defining package still trusts the old certificate for permissions
+        //     - or it shares a common signing certificate in its lineage with the defining package,
+        //       and the defining package still trusts the old certificate for permissions
         //     - or it shares the above relationships with the system package
         final PackageParser.SigningDetails sourceSigningDetails =
                 getSourcePackageSigningDetails(bp);
-        return pkg.getSigningDetails().hasAncestorOrSelf(sourceSigningDetails)
-                || sourceSigningDetails.checkCapability(
+        return sourceSigningDetails.hasCommonSignerWithCapability(
                         pkg.getSigningDetails(),
                         PackageParser.SigningDetails.CertCapabilities.PERMISSION)
                 || pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails())
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
new file mode 100644
index 0000000..36efb39
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedIntentInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.util.ArraySet;
+import android.util.Patterns;
+
+import com.android.server.SystemConfig;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.List;
+import java.util.Set;
+
+public class DomainVerificationCollector {
+
+    @NonNull
+    private final PlatformCompat mPlatformCompat;
+
+    @NonNull
+    private final SystemConfig mSystemConfig;
+
+    public DomainVerificationCollector(@NonNull PlatformCompat platformCompat,
+            @NonNull SystemConfig systemConfig) {
+        mPlatformCompat = platformCompat;
+        mSystemConfig = systemConfig;
+    }
+
+    /**
+     * With the updated form of the app links verification APIs, an app will be required to declare
+     * domains inside an intent filter which includes all of the following:
+     * <ul>
+     *     <li>- android:autoVerify="true"</li>
+     *     <li>- Intent.ACTION_VIEW</li>
+     *     <li>- Intent.CATEGORY_BROWSABLE</li>
+     *     <li>- Intent.CATEGORY_DEFAULT</li>
+     *     <li>- Only IntentFilter.SCHEME_HTTP and/or IntentFilter.SCHEME_HTTPS,
+     *           with no other schemes</li>
+     * </ul>
+     *
+     * On prior versions of Android, Intent.CATEGORY_BROWSABLE was not a requirement, other
+     * schemes were allowed, and setting autoVerify to true in any intent filter would implicitly
+     * pretend that all intent filters were set to autoVerify="true".
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+    public static final long RESTRICT_DOMAINS = 175408749L;
+
+    @NonNull
+    public ArraySet<String> collectAllWebDomains(@NonNull AndroidPackage pkg) {
+        return collectDomains(pkg, false);
+    }
+
+    /**
+     * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires
+     * {@link IntentFilter#getAutoVerify()} == true.
+     */
+    @NonNull
+    public ArraySet<String> collectAutoVerifyDomains(@NonNull AndroidPackage pkg) {
+        return collectDomains(pkg, true);
+    }
+
+    @NonNull
+    private ArraySet<String> collectDomains(@NonNull AndroidPackage pkg,
+            boolean checkAutoVerify) {
+        boolean restrictDomains =
+                DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS);
+
+        ArraySet<String> domains = new ArraySet<>();
+
+        if (restrictDomains) {
+            collectDomains(domains, pkg, checkAutoVerify);
+        } else {
+            collectDomainsLegacy(domains, pkg, checkAutoVerify);
+        }
+
+        return domains;
+    }
+
+    /** @see #RESTRICT_DOMAINS */
+    private void collectDomainsLegacy(@NonNull Set<String> domains,
+            @NonNull AndroidPackage pkg, boolean checkAutoVerify) {
+        if (!checkAutoVerify) {
+            // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2
+            collectDomains(domains, pkg, false);
+            return;
+        }
+
+        List<ParsedActivity> activities = pkg.getActivities();
+        int activitiesSize = activities.size();
+
+        // Due to a bug in the platform, for backwards compatibility, assume that all linked apps
+        // require auto verification, even if they forget to mark their manifest as such.
+        boolean needsAutoVerify = mSystemConfig.getLinkedApps().contains(pkg.getPackageName());
+        if (!needsAutoVerify) {
+            for (int activityIndex = 0; activityIndex < activitiesSize && !needsAutoVerify;
+                    activityIndex++) {
+                ParsedActivity activity = activities.get(activityIndex);
+                List<ParsedIntentInfo> intents = activity.getIntents();
+                int intentsSize = intents.size();
+                for (int intentIndex = 0; intentIndex < intentsSize && !needsAutoVerify;
+                        intentIndex++) {
+                    ParsedIntentInfo intent = intents.get(intentIndex);
+                    needsAutoVerify = intent.needsVerification();
+                }
+            }
+
+            if (!needsAutoVerify) {
+                return;
+            }
+        }
+
+        for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+            ParsedActivity activity = activities.get(activityIndex);
+            List<ParsedIntentInfo> intents = activity.getIntents();
+            int intentsSize = intents.size();
+            for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) {
+                ParsedIntentInfo intent = intents.get(intentIndex);
+                if (intent.handlesWebUris(false)) {
+                    int authorityCount = intent.countDataAuthorities();
+                    for (int index = 0; index < authorityCount; index++) {
+                        domains.add(intent.getDataAuthority(index).getHost());
+                    }
+                }
+            }
+        }
+    }
+
+    /** @see #RESTRICT_DOMAINS */
+    private void collectDomains(@NonNull Set<String> domains,
+            @NonNull AndroidPackage pkg, boolean checkAutoVerify) {
+        List<ParsedActivity> activities = pkg.getActivities();
+        int activitiesSize = activities.size();
+        for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+            ParsedActivity activity = activities.get(activityIndex);
+            List<ParsedIntentInfo> intents = activity.getIntents();
+            int intentsSize = intents.size();
+            for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) {
+                ParsedIntentInfo intent = intents.get(intentIndex);
+                if (checkAutoVerify && !intent.getAutoVerify()) {
+                    continue;
+                }
+
+                if (!intent.hasCategory(Intent.CATEGORY_DEFAULT)
+                        || !intent.handlesWebUris(checkAutoVerify)) {
+                    continue;
+                }
+
+                // TODO(b/159952358): There seems to be no way to associate the exact host
+                //  with its scheme, meaning all hosts have to be verified as if they were
+                //  web schemes. This means that given the following:
+                //  <intent-filter android:autoVerify="true">
+                //      ...
+                //      <data android:scheme="https" android:host="one.example.com"/>
+                //      <data android:scheme="https" android:host="two.example.com"/>
+                //      <data android:host="three.example.com"/>
+                //      <data android:scheme="nonWeb" android:host="four.example.com"/>
+                //  </intent-filter>
+                //  The verification agent will be asked to verify four.example.com, which the
+                //  app will probably fail. This can be re-configured to work properly by the
+                //  app developer by declaring a separate intent-filter. This may not be worth
+                //  fixing.
+                int authorityCount = intent.countDataAuthorities();
+                for (int index = 0; index < authorityCount; index++) {
+                    String host = intent.getDataAuthority(index).getHost();
+                    // It's easy to misconfigure autoVerify intent filters, so to avoid
+                    // adding unintended hosts, check if the host is an HTTP domain.
+                    if (Patterns.DOMAIN_NAME.matcher(host).matches()) {
+                        domains.add(host);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
new file mode 100644
index 0000000..af9978b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.PackageUtils;
+import android.util.SparseArray;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.Arrays;
+
+public class DomainVerificationDebug {
+
+    @NonNull
+    private final DomainVerificationCollector mCollector;
+
+    DomainVerificationDebug(DomainVerificationCollector collector) {
+        mCollector = collector;
+    }
+
+    public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+            @Nullable @UserIdInt Integer userId,
+            @NonNull DomainVerificationService.Connection connection,
+            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> stateMap)
+            throws NameNotFoundException {
+        ArrayMap<String, Integer> reusedMap = new ArrayMap<>();
+        ArraySet<String> reusedSet = new ArraySet<>();
+
+        if (packageName == null) {
+            int size = stateMap.size();
+            for (int index = 0; index < size; index++) {
+                DomainVerificationPkgState pkgState = stateMap.valueAt(index);
+                String pkgName = pkgState.getPackageName();
+                PackageSetting pkgSetting = connection.getPackageSettingLocked(pkgName);
+                if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                    continue;
+                }
+
+                boolean wasHeaderPrinted = printState(writer, pkgState, pkgSetting.getPkg(),
+                        reusedMap, false);
+                printState(writer, pkgState, pkgSetting.getPkg(), userId, reusedSet,
+                        wasHeaderPrinted);
+            }
+        } else {
+            DomainVerificationPkgState pkgState = stateMap.get(packageName);
+            if (pkgState == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+            }
+
+            PackageSetting pkgSetting = connection.getPackageSettingLocked(packageName);
+            if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+            }
+
+            AndroidPackage pkg = pkgSetting.getPkg();
+            printState(writer, pkgState, pkg, reusedMap, false);
+            printState(writer, pkgState, pkg, userId, reusedSet, true);
+        }
+    }
+
+    boolean printState(@NonNull IndentingPrintWriter writer,
+            @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+            @NonNull ArrayMap<String, Integer> reusedMap, boolean wasHeaderPrinted) {
+        reusedMap.clear();
+        reusedMap.putAll(pkgState.getStateMap());
+
+        ArraySet<String> declaredDomains = mCollector.collectAutoVerifyDomains(pkg);
+        int declaredSize = declaredDomains.size();
+        for (int declaredIndex = 0; declaredIndex < declaredSize; declaredIndex++) {
+            String domain = declaredDomains.valueAt(declaredIndex);
+            reusedMap.putIfAbsent(domain, DomainVerificationState.STATE_NO_RESPONSE);
+        }
+
+        boolean printedHeader = false;
+
+        if (!reusedMap.isEmpty()) {
+            if (!wasHeaderPrinted) {
+                Signature[] signatures = pkg.getSigningDetails().signatures;
+                String signaturesDigest = signatures == null ? null : Arrays.toString(
+                        PackageUtils.computeSignaturesSha256Digests(
+                                pkg.getSigningDetails().signatures));
+
+                writer.println(pkgState.getPackageName() + ":");
+                writer.increaseIndent();
+                writer.println("ID: " + pkgState.getId());
+                writer.println("Signatures: " + signaturesDigest);
+                writer.decreaseIndent();
+                printedHeader = true;
+            }
+
+            writer.increaseIndent();
+            writer.println("Domain verification state:");
+            writer.increaseIndent();
+            int stateSize = reusedMap.size();
+            for (int stateIndex = 0; stateIndex < stateSize; stateIndex++) {
+                String domain = reusedMap.keyAt(stateIndex);
+                Integer state = reusedMap.valueAt(stateIndex);
+                writer.print(domain);
+                writer.print(": ");
+                writer.println(DomainVerificationManager.stateToDebugString(state));
+            }
+            writer.decreaseIndent();
+            writer.decreaseIndent();
+        }
+
+        return printedHeader;
+    }
+
+    void printState(@NonNull IndentingPrintWriter writer,
+            @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+            @Nullable @UserIdInt Integer userId, @NonNull ArraySet<String> reusedSet,
+            boolean wasHeaderPrinted) {
+        if (userId == null) {
+            return;
+        }
+
+        ArraySet<String> allWebDomains = mCollector.collectAllWebDomains(pkg);
+        SparseArray<DomainVerificationUserState> userStates =
+                pkgState.getUserSelectionStates();
+        if (userId == UserHandle.USER_ALL) {
+            int size = userStates.size();
+            if (size == 0) {
+                printState(writer, pkgState, userId, null, reusedSet, allWebDomains,
+                        wasHeaderPrinted);
+            } else {
+                for (int index = 0; index < size; index++) {
+                    DomainVerificationUserState userState = userStates.valueAt(index);
+                    printState(writer, pkgState, userState.getUserId(), userState, reusedSet,
+                            allWebDomains, wasHeaderPrinted);
+                }
+            }
+        } else {
+            DomainVerificationUserState userState = userStates.get(userId);
+            printState(writer, pkgState, userId, userState, reusedSet, allWebDomains,
+                    wasHeaderPrinted);
+        }
+    }
+
+    boolean printState(@NonNull IndentingPrintWriter writer,
+            @NonNull DomainVerificationPkgState pkgState, @UserIdInt int userId,
+            @Nullable DomainVerificationUserState userState, @NonNull ArraySet<String> reusedSet,
+            @NonNull ArraySet<String> allWebDomains, boolean wasHeaderPrinted) {
+        reusedSet.clear();
+        reusedSet.addAll(allWebDomains);
+        if (userState != null) {
+            reusedSet.removeAll(userState.getEnabledHosts());
+        }
+
+        boolean printedHeader = false;
+
+        ArraySet<String> enabledHosts = userState == null ? null : userState.getEnabledHosts();
+        int enabledSize = CollectionUtils.size(enabledHosts);
+        int disabledSize = reusedSet.size();
+        if (enabledSize > 0 || disabledSize > 0) {
+            if (!wasHeaderPrinted) {
+                writer.println(pkgState.getPackageName() + " " + pkgState.getId() + ":");
+                printedHeader = true;
+            }
+
+            boolean isLinkHandlingAllowed = userState == null
+                    || !userState.isDisallowLinkHandling();
+
+            writer.increaseIndent();
+            writer.print("User ");
+            writer.print(userId == UserHandle.USER_ALL ? "all" : userId);
+            writer.println(":");
+            writer.increaseIndent();
+            writer.print("Verification link handling allowed: ");
+            writer.println(isLinkHandlingAllowed);
+            writer.println("Selection state:");
+            writer.increaseIndent();
+
+            if (enabledSize > 0) {
+                writer.println("Enabled:");
+                writer.increaseIndent();
+                for (int enabledIndex = 0; enabledIndex < enabledSize; enabledIndex++) {
+                    //noinspection ConstantConditions
+                    writer.println(enabledHosts.valueAt(enabledIndex));
+                }
+                writer.decreaseIndent();
+            }
+
+            if (disabledSize > 0) {
+                writer.println("Disabled:");
+                writer.increaseIndent();
+                for (int disabledIndex = 0; disabledIndex < disabledSize; disabledIndex++) {
+                    writer.println(reusedSet.valueAt(disabledIndex));
+                }
+                writer.decreaseIndent();
+            }
+
+            writer.decreaseIndent();
+            writer.decreaseIndent();
+            writer.decreaseIndent();
+        }
+
+        return printedHeader;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
new file mode 100644
index 0000000..c521f82
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Process;
+
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+
+public class DomainVerificationEnforcer {
+
+    @NonNull
+    private final Context mContext;
+
+    public DomainVerificationEnforcer(@NonNull Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Enforced when mutating any state from shell or internally in the system process.
+     */
+    public void assertInternal(int callingUid) {
+        switch (callingUid) {
+            case Process.ROOT_UID:
+            case Process.SHELL_UID:
+            case Process.SYSTEM_UID:
+                break;
+            default:
+                throw new SecurityException(
+                        "Caller " + callingUid + " is not allowed to change internal state");
+        }
+    }
+
+    /**
+     * Enforced when retrieving state for a package. The system, the verifier, and anyone approved
+     * to mutate user selections are allowed through.
+     */
+    public void assertApprovedQuerent(int callingUid, @NonNull DomainVerificationProxy proxy) {
+        switch (callingUid) {
+            case Process.ROOT_UID:
+            case Process.SHELL_UID:
+            case Process.SYSTEM_UID:
+                break;
+            default:
+                if (!proxy.isCallerVerifier(callingUid)) {
+                    mContext.enforcePermission(
+                            android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
+                            Binder.getCallingPid(), callingUid,
+                            "Caller " + callingUid
+                                    + " is not allowed to query domain verification state");
+                }
+                break;
+        }
+    }
+
+    /**
+     * Enforced when mutating domain verification state inside an exposed API method.
+     */
+    public void assertApprovedVerifier(int callingUid, @NonNull DomainVerificationProxy proxy)
+            throws SecurityException {
+        boolean isAllowed;
+        switch (callingUid) {
+            case Process.ROOT_UID:
+            case Process.SHELL_UID:
+            case Process.SYSTEM_UID:
+                isAllowed = true;
+                break;
+            default:
+                // TODO(b/159952358): Remove permission check? The component package should
+                //  have been checked when the verifier component was first scanned in PMS.
+                mContext.enforcePermission(
+                        android.Manifest.permission.DOMAIN_VERIFICATION_AGENT,
+                        Binder.getCallingPid(), callingUid,
+                        "Caller " + callingUid + " does not hold DOMAIN_VERIFICATION_AGENT");
+                isAllowed = proxy.isCallerVerifier(callingUid);
+                break;
+        }
+
+        if (!isAllowed) {
+            throw new SecurityException("Caller " + callingUid
+                    + " is not the approved domain verification agent, isVerifier = "
+                    + proxy.isCallerVerifier(callingUid));
+        }
+    }
+
+    /**
+     * Enforced when mutating user selection state inside an exposed API method.
+     */
+    public void assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
+            @UserIdInt int targetUserId) throws SecurityException {
+        if (callingUserId != targetUserId) {
+            mContext.enforcePermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    Binder.getCallingPid(), callingUid,
+                    "Caller is not allowed to edit other users");
+        }
+
+        mContext.enforcePermission(
+                android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
+                Binder.getCallingPid(), callingUid,
+                "Caller is not allowed to edit user selections");
+    }
+
+    public void callerIsLegacyUserSelector(int callingUid) {
+        mContext.enforcePermission(
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS,
+                Binder.getCallingPid(), callingUid,
+                "Caller is not allowed to edit user state");
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
new file mode 100644
index 0000000..c787356
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationLegacySettings.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2021 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.SparseIntArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.SettingsXml;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Reads and writes the old {@link android.content.pm.IntentFilterVerificationInfo} so that it can
+ * be migrated in to the new API. Will throw away the state once it's successfully applied so that
+ * eventually there will be no legacy state on the device.
+ *
+ * This attempt is best effort, and if the legacy state is lost that's acceptable. The user setting
+ * in the legacy API may have been set incorrectly because it was never made obvious to the user
+ * what it actually toggled, so there's a strong argument to prevent migration anyways. The user
+ * can just set their preferences again, this time with finer grained control, if the legacy state
+ * gets dropped.
+ */
+public class DomainVerificationLegacySettings {
+
+    public static final String TAG_DOMAIN_VERIFICATIONS_LEGACY = "domain-verifications-legacy";
+    public static final String TAG_USER_STATES = "user-states";
+    public static final String ATTR_PACKAGE_NAME = "packageName";
+    public static final String TAG_USER_STATE = "user-state";
+    public static final String ATTR_USER_ID = "userId";
+    public static final String ATTR_STATE = "state";
+
+    @NonNull
+    private final Object mLock = new Object();
+
+    @NonNull
+    private final ArrayMap<String, LegacyState> mStates = new ArrayMap<>();
+
+    public void add(@NonNull String packageName, @NonNull IntentFilterVerificationInfo info) {
+        synchronized (mLock) {
+            getOrCreateStateLocked(packageName).setInfo(info);
+        }
+    }
+
+    public void add(@NonNull String packageName, @UserIdInt int userId, int state) {
+        synchronized (mLock) {
+            getOrCreateStateLocked(packageName).addUserState(userId, state);
+        }
+    }
+
+    public int getUserState(@NonNull String packageName, @UserIdInt int userId) {
+        synchronized (mLock) {
+            LegacyState state = mStates.get(packageName);
+            if (state != null) {
+                return state.getUserState(userId);
+            }
+        }
+        return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+    }
+
+    @Nullable
+    public SparseIntArray getUserStates(@NonNull String packageName) {
+        synchronized (mLock) {
+            LegacyState state = mStates.get(packageName);
+            if (state != null) {
+                // Yes, this returns outside of the lock, but we assume that retrieval generally
+                // only happens after all adding has concluded from reading settings.
+                return state.getUserStates();
+            }
+        }
+        return null;
+    }
+
+    @Nullable
+    public IntentFilterVerificationInfo remove(@NonNull String packageName) {
+        synchronized (mLock) {
+            LegacyState state = mStates.get(packageName);
+            if (state != null && !state.isAttached()) {
+                state.markAttached();
+                return state.getInfo();
+            }
+        }
+        return null;
+    }
+
+    @GuardedBy("mLock")
+    @NonNull
+    private LegacyState getOrCreateStateLocked(@NonNull String packageName) {
+        LegacyState state = mStates.get(packageName);
+        if (state == null) {
+            state = new LegacyState();
+            mStates.put(packageName, state);
+        }
+
+        return state;
+    }
+
+    public void writeSettings(TypedXmlSerializer xmlSerializer) throws IOException {
+        try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
+            try (SettingsXml.WriteSection ignored =
+                         serializer.startSection(TAG_DOMAIN_VERIFICATIONS_LEGACY)) {
+                synchronized (mLock) {
+                    final int statesSize = mStates.size();
+                    for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
+                        final LegacyState state = mStates.valueAt(stateIndex);
+                        final SparseIntArray userStates = state.getUserStates();
+                        if (userStates == null) {
+                            continue;
+                        }
+
+                        final String packageName = mStates.keyAt(stateIndex);
+                        try (SettingsXml.WriteSection userStatesSection =
+                                     serializer.startSection(TAG_USER_STATES)
+                                             .attribute(ATTR_PACKAGE_NAME, packageName)) {
+                            final int userStatesSize = userStates.size();
+                            for (int userStateIndex = 0; userStateIndex < userStatesSize;
+                                    userStateIndex++) {
+                                final int userId = userStates.keyAt(userStateIndex);
+                                final int userState = userStates.valueAt(userStateIndex);
+                                userStatesSection.startSection(TAG_USER_STATE)
+                                        .attribute(ATTR_USER_ID, userId)
+                                        .attribute(ATTR_STATE, userState)
+                                        .finish();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void readSettings(TypedXmlPullParser xmlParser)
+            throws IOException, XmlPullParserException {
+        final SettingsXml.ChildSection child = SettingsXml.parser(xmlParser).children();
+        while (child.moveToNext()) {
+            if (TAG_USER_STATES.equals(child.getName())) {
+                readUserStates(child);
+            }
+        }
+    }
+
+    private void readUserStates(SettingsXml.ReadSection section) {
+        String packageName = section.getString(ATTR_PACKAGE_NAME);
+        synchronized (mLock) {
+            final LegacyState legacyState = getOrCreateStateLocked(packageName);
+            final SettingsXml.ChildSection child = section.children();
+            while (child.moveToNext()) {
+                if (TAG_USER_STATE.equals(child.getName())) {
+                    readUserState(child, legacyState);
+                }
+            }
+        }
+    }
+
+    private void readUserState(SettingsXml.ReadSection section, LegacyState legacyState) {
+        int userId = section.getInt(ATTR_USER_ID);
+        int state = section.getInt(ATTR_STATE);
+        legacyState.addUserState(userId, state);
+    }
+
+    static class LegacyState {
+        @Nullable
+        private IntentFilterVerificationInfo mInfo;
+
+        @Nullable
+        private SparseIntArray mUserStates;
+
+        private boolean attached;
+
+        @Nullable
+        public IntentFilterVerificationInfo getInfo() {
+            return mInfo;
+        }
+
+        public int getUserState(int userId) {
+            return mUserStates.get(userId,
+                    PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
+        }
+
+        @Nullable
+        public SparseIntArray getUserStates() {
+            return mUserStates;
+        }
+
+        public void setInfo(@NonNull IntentFilterVerificationInfo info) {
+            mInfo = info;
+        }
+
+        public void addUserState(@UserIdInt int userId, int state) {
+            if (mUserStates == null) {
+                mUserStates = new SparseIntArray(1);
+            }
+            mUserStates.put(userId, state);
+        }
+
+        public boolean isAttached() {
+            return attached;
+        }
+
+        public void markAttached() {
+            attached = true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
new file mode 100644
index 0000000..7ad275a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+import android.content.Intent;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.UUID;
+
+public interface DomainVerificationManagerInternal extends DomainVerificationManager {
+
+    UUID DISABLED_ID = new UUID(0, 0);
+
+    /**
+     * Generate a new domain set ID to be used for attaching new packages.
+     */
+    @NonNull
+    UUID generateNewId();
+
+    void setConnection(@NonNull Connection connection);
+
+    @NonNull
+    DomainVerificationProxy getProxy();
+
+    /**
+     * Update the proxy implementation that talks to the domain verification agent on device. The
+     * default proxy is a stub that does nothing, and broadcast functionality will only work once a
+     * real implementation is attached.
+     */
+    void setProxy(@NonNull DomainVerificationProxy proxy);
+
+    /**
+     * @see DomainVerificationProxy.BaseConnection#runMessage(int, Object)
+     */
+    boolean runMessage(int messageCode, Object object);
+
+    /**
+     * Restores or creates internal state for the new package. This can either be from scanning a
+     * package at boot, or a truly new installation on the device. It is expected that the {@link
+     * PackageSetting#getDomainSetId()} already be set to the correct value.
+     * <p>
+     * If this is from scan, there should be a pending state that was previous read using {@link
+     * #readSettings(TypedXmlPullParser)}, which will be attached as-is to the package. In this
+     * case, a broadcast will not be sent to the domain verification agent on device, as it is
+     * assumed nothing has changed since the device rebooted.
+     * <p>
+     * If this is a new install, state will be restored from a previous call to {@link
+     * #restoreSettings(TypedXmlPullParser)}, or a new one will be generated. In either case, a
+     * broadcast will be sent to the domain verification agent so it may re-run any verification
+     * logic for the newly associated domains.
+     * <p>
+     * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
+     * lock. This should never be called from within the domain verification classes themselves.
+     * <p>
+     * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
+     * caller.
+     */
+    void addPackage(@NonNull PackageSetting newPkgSetting);
+
+    /**
+     * Migrates verification state from a previous install to a new one. It is expected that the
+     * {@link PackageSetting#getDomainSetId()} already be set to the correct value, usually from
+     * {@link #generateNewId()}. This will preserve {@link #STATE_SUCCESS} domains under the
+     * assumption that the new package will pass the same server side config as the previous
+     * package, as they have matching signatures.
+     * <p>
+     * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal
+     * lock. This should never be called from within the domain verification classes themselves.
+     * <p>
+     * This will NOT call {@link #writeSettings(TypedXmlSerializer)}. That must be handled by the
+     * caller.
+     */
+    void migrateState(@NonNull PackageSetting oldPkgSetting, @NonNull PackageSetting newPkgSetting);
+
+    /**
+     * Serializes the entire internal state. This is equivalent to a full backup of the existing
+     * verification state. This write includes legacy state, as a sibling tag the modern state.
+     */
+    void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException;
+
+    /**
+     * Read back a list of {@link DomainVerificationPkgState}s previously written by {@link
+     * #writeSettings(TypedXmlSerializer)}. Assumes that the
+     * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} tag has already been entered.
+     * <p>
+     * This is expected to only be used to re-attach states for packages already known to be on the
+     * device. If restoring from a backup, use {@link #restoreSettings(TypedXmlPullParser)}.
+     */
+    void readSettings(@NonNull TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException;
+
+    /**
+     * Read back data from
+     * {@link DomainVerificationLegacySettings#writeSettings(TypedXmlSerializer)}. Assumes that the
+     * {@link DomainVerificationLegacySettings#TAG_DOMAIN_VERIFICATIONS_LEGACY} tag has already
+     * been entered.
+     */
+    void readLegacySettings(@NonNull TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException;
+
+    /**
+     * Remove all state for the given package.
+     */
+    void clearPackage(@NonNull String packageName);
+
+    /**
+     * Delete all the state for a user. This can be because the user has been removed from the
+     * device, or simply that the state for a user should be deleted.
+     */
+    void clearUser(@UserIdInt int userId);
+
+    /**
+     * Restore a list of {@link DomainVerificationPkgState}s previously written by {@link
+     * #writeSettings(TypedXmlSerializer)}. Assumes that the
+     * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS}
+     * tag has already been entered.
+     * <p>
+     * This is <b>only</b> for restore, and will override package states, ignoring if their {@link
+     * DomainVerificationInfo#getIdentifier()}s match. It's expected that any restored domains marked
+     * as success verify against the server correctly, although the verification agent may decide to
+     * re-verify them when it gets the chance.
+     */
+    /*
+     * TODO(b/170746586): Figure out how to verify that package signatures match at snapshot time
+     *  and restore time.
+     */
+    void restoreSettings(@NonNull TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException;
+
+    /**
+     * Set aside a legacy {@link IntentFilterVerificationInfo} that will be restored to a pending
+     * {@link DomainVerificationPkgState} once it's added through
+     * {@link #addPackage(PackageSetting)}.
+     */
+    void addLegacySetting(@NonNull String packageName, @NonNull IntentFilterVerificationInfo info);
+
+    /**
+     * Set aside a legacy user selection that will be restored to a pending
+     * {@link DomainVerificationPkgState} once it's added through
+     * {@link #addPackage(PackageSetting)}.
+     */
+    void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state);
+
+    /**
+     * Until the legacy APIs are entirely removed, returns the legacy state from the previously
+     * written info stored in {@link com.android.server.pm.Settings}.
+     */
+    int getLegacyState(@NonNull String packageName, @UserIdInt int userId);
+
+    /**
+     * Serialize a legacy setting that wasn't attached yet.
+     * TODO: Does this even matter? Should consider for removal.
+     */
+    void writeLegacySettings(TypedXmlSerializer serializer, String name);
+
+    /**
+     * Print the verification state and user selection state of a package.
+     *
+     * @param packageName the package whose state to change, or all packages if none is specified
+     * @param userId      the specific user to print, or null to skip printing user selection
+     *                    states, supports {@link android.os.UserHandle#USER_ALL}
+     */
+    void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+            @Nullable @UserIdInt Integer userId) throws NameNotFoundException;
+
+    @NonNull
+    DomainVerificationShell getShell();
+
+    @NonNull
+    DomainVerificationCollector getCollector();
+
+    /**
+     * Check if a resolving URI is approved to takeover the domain as the sole resolved target.
+     * This can be because the domain was auto-verified for the package, or if the user manually
+     * chose to enable the domain for the package.
+     */
+    boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+            @UserIdInt int userId);
+
+    /**
+     * @return the domain verification set ID for the given package, or null if the ID is
+     * unavailable
+     */
+    @Nullable
+    UUID getDomainVerificationInfoId(@NonNull String packageName);
+
+    @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
+    void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
+            @NonNull Set<String> domains, int state)
+            throws IllegalArgumentException, NameNotFoundException;
+
+
+    interface Connection {
+
+        /**
+         * Notify that a settings change has been made and that eventually
+         * {@link #writeSettings(TypedXmlSerializer)} should be invoked by the parent.
+         */
+        void scheduleWriteSettings();
+
+        /**
+         * Delegate to {@link Binder#getCallingUid()} to allow mocking in tests.
+         */
+        int getCallingUid();
+
+        /**
+         * Delegate to {@link UserHandle#getCallingUserId()} to allow mocking in tests.
+         */
+        @UserIdInt
+        int getCallingUserId();
+
+        /**
+         * @see DomainVerificationProxy.BaseConnection#schedule(int, java.lang.Object)
+         */
+        void schedule(int code, @Nullable Object object);
+
+        @Nullable
+        PackageSetting getPackageSettingLocked(@NonNull String pkgName);
+
+        @Nullable
+        AndroidPackage getPackageLocked(@NonNull String pkgName);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
new file mode 100644
index 0000000..8aa6337
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
+import android.content.pm.verify.domain.DomainVerificationManagerImpl;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.ServiceSpecificException;
+import android.util.ArraySet;
+
+import java.util.List;
+import java.util.UUID;
+
+class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
+
+    @NonNull
+    private DomainVerificationService mService;
+
+    DomainVerificationManagerStub(DomainVerificationService service) {
+        mService = service;
+    }
+
+    @NonNull
+    @Override
+    public List<String> getValidVerificationPackageNames() {
+        try {
+            return mService.getValidVerificationPackageNames();
+        } catch (Exception e) {
+            throw rethrow(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public DomainVerificationInfo getDomainVerificationInfo(String packageName) {
+        try {
+            return mService.getDomainVerificationInfo(packageName);
+        } catch (Exception e) {
+            throw rethrow(e);
+        }
+    }
+
+    @Override
+    public void setDomainVerificationStatus(String domainSetId, List<String> domains,
+            int state) {
+        try {
+            mService.setDomainVerificationStatus(UUID.fromString(domainSetId),
+                            new ArraySet<>(domains), state);
+        } catch (Exception e) {
+            throw rethrow(e);
+        }
+    }
+
+    @Override
+    public void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed,
+            @UserIdInt int userId) {
+        try {
+            mService.setDomainVerificationLinkHandlingAllowed(packageName, allowed, userId);
+        } catch (Exception e) {
+            throw rethrow(e);
+        }
+    }
+
+    @Override
+    public void setDomainVerificationUserSelection(String domainSetId, List<String> domains,
+            boolean enabled, @UserIdInt int userId) {
+        try {
+            mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId),
+                            new ArraySet<>(domains), enabled, userId);
+        } catch (Exception e) {
+            throw rethrow(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public DomainVerificationUserSelection getDomainVerificationUserSelection(
+            String packageName, @UserIdInt int userId) {
+        try {
+            return mService.getDomainVerificationUserSelection(packageName, userId);
+        } catch (Exception e) {
+            throw rethrow(e);
+        }
+    }
+
+    private RuntimeException rethrow(Exception exception) throws RuntimeException {
+        if (exception instanceof InvalidDomainSetException) {
+            int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET;
+            packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16;
+            return new ServiceSpecificException(packedErrorCode,
+                    ((InvalidDomainSetException) exception).getPackageName());
+        } else if (exception instanceof NameNotFoundException) {
+            return new ServiceSpecificException(
+                    DomainVerificationManagerImpl.ERROR_NAME_NOT_FOUND);
+        } else if (exception instanceof RuntimeException) {
+            return (RuntimeException) exception;
+        } else {
+            return new RuntimeException(exception);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java
new file mode 100644
index 0000000..7af78c6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationMessageCodes.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 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.pm.verify.domain;
+
+import android.os.Handler;
+
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+
+/**
+ * Codes that are sent through the {@link PackageManagerService} {@link Handler} and eventually
+ * delegated to {@link DomainVerificationService} and {@link DomainVerificationProxy}.
+ *
+ * These codes are wrapped and thus exclusive to the domain verification APIs. They do not have be
+ * distinct from any of the codes inside {@link PackageManagerService}.
+ */
+public final class DomainVerificationMessageCodes {
+
+    public static final int SEND_REQUEST = 1;
+    public static final int LEGACY_SEND_REQUEST = 2;
+    public static final int LEGACY_ON_INTENT_FILTER_VERIFIED = 3;
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
new file mode 100644
index 0000000..679f948
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationPersistence.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.server.pm.SettingsXml;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.UUID;
+
+public class DomainVerificationPersistence {
+
+    private static final String TAG = "DomainVerificationPersistence";
+
+    public static final String TAG_DOMAIN_VERIFICATIONS = "domain-verifications";
+    public static final String TAG_ACTIVE = "active";
+    public static final String TAG_RESTORED = "restored";
+
+    public static final String TAG_PACKAGE_STATE = "package-state";
+    private static final String ATTR_PACKAGE_NAME = "packageName";
+    private static final String ATTR_ID = "id";
+    private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains";
+    private static final String TAG_USER_STATES = "user-states";
+
+    public static final String TAG_USER_STATE = "user-state";
+    public static final String ATTR_USER_ID = "userId";
+    public static final String ATTR_DISALLOW_LINK_HANDLING = "disallowLinkHandling";
+    public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
+    public static final String TAG_HOST = "host";
+
+    private static final String TAG_STATE = "state";
+    public static final String TAG_DOMAIN = "domain";
+    public static final String ATTR_NAME = "name";
+    public static final String ATTR_STATE = "state";
+
+    public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer,
+            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
+            @NonNull ArrayMap<String, DomainVerificationPkgState> pending,
+            @NonNull ArrayMap<String, DomainVerificationPkgState> restored) throws IOException {
+        try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
+            try (SettingsXml.WriteSection ignored = serializer.startSection(
+                    TAG_DOMAIN_VERIFICATIONS)) {
+                // Both attached and pending states are written to the active set, since both
+                // should be restored when the device reboots or runs a backup. They're merged into
+                // the same list because at read time the distinction isn't relevant. The pending
+                // list should generally be empty at this point anyways.
+                ArraySet<DomainVerificationPkgState> active = new ArraySet<>();
+
+                int attachedSize = attached.size();
+                for (int attachedIndex = 0; attachedIndex < attachedSize; attachedIndex++) {
+                    active.add(attached.valueAt(attachedIndex));
+                }
+
+                int pendingSize = pending.size();
+                for (int pendingIndex = 0; pendingIndex < pendingSize; pendingIndex++) {
+                    active.add(pending.valueAt(pendingIndex));
+                }
+
+                try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
+                    writePackageStates(activeSection, active);
+                }
+
+                try (SettingsXml.WriteSection restoredSection = serializer.startSection(
+                        TAG_RESTORED)) {
+                    writePackageStates(restoredSection, restored.values());
+                }
+            }
+        }
+    }
+
+    private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
+            @NonNull Collection<DomainVerificationPkgState> states) throws IOException {
+        if (states.isEmpty()) {
+            return;
+        }
+
+        for (DomainVerificationPkgState state : states) {
+            writePkgStateToXml(section, state);
+        }
+    }
+
+    @NonNull
+    public static ReadResult readFromXml(@NonNull TypedXmlPullParser parentParser)
+            throws IOException, XmlPullParserException {
+        ArrayMap<String, DomainVerificationPkgState> active = new ArrayMap<>();
+        ArrayMap<String, DomainVerificationPkgState> restored = new ArrayMap<>();
+
+        SettingsXml.ChildSection child = SettingsXml.parser(parentParser).children();
+        while (child.moveToNext()) {
+            switch (child.getName()) {
+                case TAG_ACTIVE:
+                    readPackageStates(child, active);
+                    break;
+                case TAG_RESTORED:
+                    readPackageStates(child, restored);
+                    break;
+            }
+        }
+
+        return new ReadResult(active, restored);
+    }
+
+    private static void readPackageStates(@NonNull SettingsXml.ReadSection section,
+            @NonNull ArrayMap<String, DomainVerificationPkgState> map) {
+        SettingsXml.ChildSection child = section.children();
+        while (child.moveToNext(TAG_PACKAGE_STATE)) {
+            DomainVerificationPkgState pkgState = createPkgStateFromXml(child);
+            if (pkgState != null) {
+                // State is unique by package name
+                map.put(pkgState.getPackageName(), pkgState);
+            }
+        }
+    }
+
+    /**
+     * Reads a package state from XML. Assumes the starting {@link #TAG_PACKAGE_STATE} has already
+     * been entered.
+     */
+    @Nullable
+    public static DomainVerificationPkgState createPkgStateFromXml(
+            @NonNull SettingsXml.ReadSection section) {
+        String packageName = section.getString(ATTR_PACKAGE_NAME);
+        String idString = section.getString(ATTR_ID);
+        boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS);
+        if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) {
+            return null;
+        }
+        UUID id = UUID.fromString(idString);
+
+        final ArrayMap<String, Integer> stateMap = new ArrayMap<>();
+        final SparseArray<DomainVerificationUserState> userStates = new SparseArray<>();
+
+        SettingsXml.ChildSection child = section.children();
+        while (child.moveToNext()) {
+            switch (child.getName()) {
+                case TAG_STATE:
+                    readDomainStates(child, stateMap);
+                    break;
+                case TAG_USER_STATES:
+                    readUserStates(child, userStates);
+                    break;
+            }
+        }
+
+        return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
+                userStates);
+    }
+
+    private static void readUserStates(@NonNull SettingsXml.ReadSection section,
+            @NonNull SparseArray<DomainVerificationUserState> userStates) {
+        SettingsXml.ChildSection child = section.children();
+        while (child.moveToNext(TAG_USER_STATE)) {
+            DomainVerificationUserState userState = createUserStateFromXml(child);
+            if (userState != null) {
+                userStates.put(userState.getUserId(), userState);
+            }
+        }
+    }
+
+    private static void readDomainStates(@NonNull SettingsXml.ReadSection stateSection,
+            @NonNull ArrayMap<String, Integer> stateMap) {
+        SettingsXml.ChildSection child = stateSection.children();
+        while (child.moveToNext(TAG_DOMAIN)) {
+            String name = child.getString(ATTR_NAME);
+            int state = child.getInt(ATTR_STATE, DomainVerificationState.STATE_NO_RESPONSE);
+            stateMap.put(name, state);
+        }
+    }
+
+    public static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
+            @NonNull DomainVerificationPkgState pkgState) throws IOException {
+        try (SettingsXml.WriteSection ignored =
+                     parentSection.startSection(TAG_PACKAGE_STATE)
+                             .attribute(ATTR_PACKAGE_NAME, pkgState.getPackageName())
+                             .attribute(ATTR_ID, pkgState.getId().toString())
+                             .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
+                                     pkgState.isHasAutoVerifyDomains())) {
+            writeStateMap(parentSection, pkgState.getStateMap());
+            writeUserStates(parentSection, pkgState.getUserSelectionStates());
+        }
+    }
+
+    private static void writeUserStates(@NonNull SettingsXml.WriteSection parentSection,
+            @NonNull SparseArray<DomainVerificationUserState> states) throws IOException {
+        int size = states.size();
+        if (size == 0) {
+            return;
+        }
+
+        try (SettingsXml.WriteSection section = parentSection.startSection(TAG_USER_STATES)) {
+            for (int index = 0; index < size; index++) {
+                writeUserStateToXml(section, states.valueAt(index));
+            }
+        }
+    }
+
+    private static void writeStateMap(@NonNull SettingsXml.WriteSection parentSection,
+            @NonNull ArrayMap<String, Integer> stateMap) throws IOException {
+        if (stateMap.isEmpty()) {
+            return;
+        }
+
+        try (SettingsXml.WriteSection stateSection = parentSection.startSection(TAG_STATE)) {
+            int size = stateMap.size();
+            for (int index = 0; index < size; index++) {
+                stateSection.startSection(TAG_DOMAIN)
+                        .attribute(ATTR_NAME, stateMap.keyAt(index))
+                        .attribute(ATTR_STATE, stateMap.valueAt(index))
+                        .finish();
+            }
+        }
+    }
+
+    /**
+     * Reads a user state from XML. Assumes the starting {@link #TAG_USER_STATE} has already been
+     * entered.
+     */
+    @Nullable
+    public static DomainVerificationUserState createUserStateFromXml(
+            @NonNull SettingsXml.ReadSection section) {
+        int userId = section.getInt(ATTR_USER_ID);
+        if (userId == -1) {
+            return null;
+        }
+
+        boolean disallowLinkHandling = section.getBoolean(ATTR_DISALLOW_LINK_HANDLING);
+        ArraySet<String> enabledHosts = new ArraySet<>();
+
+        SettingsXml.ChildSection child = section.children();
+        while (child.moveToNext(TAG_ENABLED_HOSTS)) {
+            readEnabledHosts(child, enabledHosts);
+        }
+
+        return new DomainVerificationUserState(userId, enabledHosts, disallowLinkHandling);
+    }
+
+    private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
+            @NonNull ArraySet<String> enabledHosts) {
+        SettingsXml.ChildSection child = section.children();
+        while (child.moveToNext(TAG_HOST)) {
+            String hostName = child.getString(ATTR_NAME);
+            if (!TextUtils.isEmpty(hostName)) {
+                enabledHosts.add(hostName);
+            }
+        }
+    }
+
+    public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
+            @NonNull DomainVerificationUserState userState) throws IOException {
+        try (SettingsXml.WriteSection section =
+                     parentSection.startSection(TAG_USER_STATE)
+                             .attribute(ATTR_USER_ID, userState.getUserId())
+                             .attribute(ATTR_DISALLOW_LINK_HANDLING,
+                                     userState.isDisallowLinkHandling())) {
+            ArraySet<String> enabledHosts = userState.getEnabledHosts();
+            if (!enabledHosts.isEmpty()) {
+                try (SettingsXml.WriteSection enabledHostsSection =
+                             section.startSection(TAG_ENABLED_HOSTS)) {
+                    int size = enabledHosts.size();
+                    for (int index = 0; index < size; index++) {
+                        enabledHostsSection.startSection(TAG_HOST)
+                                .attribute(ATTR_NAME, enabledHosts.valueAt(index))
+                                .finish();
+                    }
+                }
+            }
+        }
+    }
+
+    public static class ReadResult {
+
+        @NonNull
+        public final ArrayMap<String, DomainVerificationPkgState> active;
+
+        @NonNull
+        public final ArrayMap<String, DomainVerificationPkgState> restored;
+
+        public ReadResult(@NonNull ArrayMap<String, DomainVerificationPkgState> active,
+                @NonNull ArrayMap<String, DomainVerificationPkgState> restored) {
+            this.active = active;
+            this.restored = restored;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
new file mode 100644
index 0000000..53540c8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -0,0 +1,1241 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.content.pm.verify.domain.IDomainVerificationManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.SystemConfig;
+import com.android.server.SystemService;
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+public class DomainVerificationService extends SystemService
+        implements DomainVerificationManagerInternal, DomainVerificationShell.Callback {
+
+    private static final String TAG = "DomainVerificationService";
+
+    public static final boolean DEBUG_APPROVAL = true;
+
+    /**
+     * The new user preference API for verifying domains marked autoVerify=true in
+     * AndroidManifest.xml intent filters is not yet implemented in the current platform preview.
+     * This is anticipated to ship before S releases.
+     *
+     * For now, it is possible to preview the new user preference changes by enabling this
+     * ChangeId and using the <code>adb shell pm set-app-links-user-selection</code> and similar
+     * commands.
+     */
+    @ChangeId
+    @Disabled
+    private static final long SETTINGS_API_V2 = 178111421;
+
+    /**
+     * States that are currently alive and attached to a package. Entries are exclusive with the
+     * state stored in {@link DomainVerificationSettings}, as any pending/restored state should be
+     * immediately attached once its available.
+     * <p>
+     * Generally this should be not accessed directly. Prefer calling {@link
+     * #getAndValidateAttachedLocked(UUID, Set, boolean)}.
+     *
+     * @see #getAndValidateAttachedLocked(UUID, Set, boolean)
+     **/
+    @GuardedBy("mLock")
+    @NonNull
+    private final DomainVerificationStateMap<DomainVerificationPkgState> mAttachedPkgStates =
+            new DomainVerificationStateMap<>();
+
+    /**
+     * Lock for all state reads/writes.
+     */
+    private final Object mLock = new Object();
+
+    @NonNull
+    private Connection mConnection;
+
+    @NonNull
+    private final SystemConfig mSystemConfig;
+
+    @NonNull
+    private final PlatformCompat mPlatformCompat;
+
+    @NonNull
+    private final DomainVerificationSettings mSettings;
+
+    @NonNull
+    private final DomainVerificationCollector mCollector;
+
+    @NonNull
+    private final DomainVerificationEnforcer mEnforcer;
+
+    @NonNull
+    private final DomainVerificationDebug mDebug;
+
+    @NonNull
+    private final DomainVerificationShell mShell;
+
+    @NonNull
+    private final DomainVerificationLegacySettings mLegacySettings;
+
+    @NonNull
+    private final IDomainVerificationManager.Stub mStub = new DomainVerificationManagerStub(this);
+
+    @NonNull
+    private DomainVerificationProxy mProxy = new DomainVerificationProxyUnavailable();
+
+    public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig,
+            @NonNull PlatformCompat platformCompat) {
+        super(context);
+        mSystemConfig = systemConfig;
+        mPlatformCompat = platformCompat;
+        mSettings = new DomainVerificationSettings();
+        mCollector = new DomainVerificationCollector(platformCompat, systemConfig);
+        mEnforcer = new DomainVerificationEnforcer(context);
+        mDebug = new DomainVerificationDebug(mCollector);
+        mShell = new DomainVerificationShell(this);
+        mLegacySettings = new DomainVerificationLegacySettings();
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.DOMAIN_VERIFICATION_SERVICE, mStub);
+    }
+
+    @Override
+    public void setConnection(@NonNull Connection connection) {
+        mConnection = connection;
+    }
+
+    @NonNull
+    @Override
+    public DomainVerificationProxy getProxy() {
+        return mProxy;
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        super.onBootPhase(phase);
+        if (phase != SystemService.PHASE_BOOT_COMPLETED || !hasRealVerifier()) {
+            return;
+        }
+
+        verifyPackages(null, false);
+    }
+
+    @Override
+    public void onUserUnlocked(@NonNull TargetUser user) {
+        super.onUserUnlocked(user);
+
+        // Package verification is sent at both boot and user unlock. The latter will allow v1
+        // verification agents to respond to the request, since they will not be directBootAware.
+        // However, ideally v2 implementations are boot aware and can handle the initial boot
+        // broadcast, to start verifying packages as soon as possible. It's possible this causes
+        // unnecessary duplication at device start up, but the implementation is responsible for
+        // de-duplicating.
+        // TODO: This can be improved by checking if the broadcast was received by the
+        //  verification agent in the initial boot broadcast
+        verifyPackages(null, false);
+    }
+
+    @Override
+    public void setProxy(@NonNull DomainVerificationProxy proxy) {
+        mProxy = proxy;
+    }
+
+    @NonNull
+    @Override
+    public List<String> getValidVerificationPackageNames() {
+        mEnforcer.assertApprovedVerifier(mConnection.getCallingUid(), mProxy);
+        List<String> packageNames = new ArrayList<>();
+        synchronized (mLock) {
+            int size = mAttachedPkgStates.size();
+            for (int index = 0; index < size; index++) {
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                if (pkgState.isHasAutoVerifyDomains()) {
+                    packageNames.add(pkgState.getPackageName());
+                }
+            }
+        }
+        return packageNames;
+    }
+
+    @Nullable
+    @Override
+    public UUID getDomainVerificationInfoId(@NonNull String packageName) {
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState != null) {
+                return pkgState.getId();
+            } else {
+                return null;
+            }
+        }
+    }
+
+    @Nullable
+    @Override
+    public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName)
+            throws NameNotFoundException {
+        mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState == null) {
+                return null;
+            }
+
+            AndroidPackage pkg = mConnection.getPackageLocked(packageName);
+            if (pkg == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+            }
+
+            Map<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
+
+            // TODO(b/159952358): Should the domain list be cached?
+            ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
+            if (domains.isEmpty()) {
+                return null;
+            }
+
+            int size = domains.size();
+            for (int index = 0; index < size; index++) {
+                hostToStateMap.putIfAbsent(domains.valueAt(index),
+                        DomainVerificationState.STATE_NO_RESPONSE);
+            }
+
+            // TODO(b/159952358): Do not return if no values are editable (all ignored states)?
+            return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
+        }
+    }
+
+    @Override
+    public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
+            int state) throws InvalidDomainSetException, NameNotFoundException {
+        if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
+            if (state != DomainVerificationState.STATE_SUCCESS) {
+                throw new IllegalArgumentException(
+                        "Verifier can only set STATE_SUCCESS or codes greater than or equal to "
+                                + "STATE_FIRST_VERIFIER_DEFINED");
+            }
+        }
+
+        setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, domains,
+                state);
+    }
+
+    @Override
+    public void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
+            @NonNull Set<String> domains, int state)
+            throws InvalidDomainSetException, NameNotFoundException {
+        mEnforcer.assertApprovedVerifier(callingUid, mProxy);
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+                    true /* forAutoVerify */);
+            ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+            for (String domain : domains) {
+                Integer previousState = stateMap.get(domain);
+                if (previousState != null
+                        && !DomainVerificationManager.isStateModifiable(previousState)) {
+                    continue;
+                }
+
+                stateMap.put(domain, state);
+            }
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    @Override
+    public void setDomainVerificationStatusInternal(@Nullable String packageName, int state,
+            @Nullable ArraySet<String> domains) throws NameNotFoundException {
+        mEnforcer.assertInternal(mConnection.getCallingUid());
+
+        switch (state) {
+            case DomainVerificationState.STATE_NO_RESPONSE:
+            case DomainVerificationState.STATE_SUCCESS:
+            case DomainVerificationState.STATE_APPROVED:
+            case DomainVerificationState.STATE_DENIED:
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "State must be one of NO_RESPONSE, SUCCESS, APPROVED, or DENIED");
+        }
+
+        if (packageName == null) {
+            synchronized (mLock) {
+                ArraySet<String> validDomains = new ArraySet<>();
+
+                int size = mAttachedPkgStates.size();
+                for (int index = 0; index < size; index++) {
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                    String pkgName = pkgState.getPackageName();
+                    PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+                    if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                        continue;
+                    }
+
+                    AndroidPackage pkg = pkgSetting.getPkg();
+
+                    validDomains.clear();
+
+                    ArraySet<String> autoVerifyDomains = mCollector.collectAutoVerifyDomains(pkg);
+                    if (domains == null) {
+                        validDomains.addAll(autoVerifyDomains);
+                    } else {
+                        validDomains.addAll(domains);
+                        validDomains.retainAll(autoVerifyDomains);
+                    }
+
+                    setDomainVerificationStatusInternal(pkgState, state, validDomains);
+                }
+            }
+        } else {
+            synchronized (mLock) {
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+                if (pkgState == null) {
+                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+                }
+
+                PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
+                if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+                }
+
+                AndroidPackage pkg = pkgSetting.getPkg();
+                if (domains == null) {
+                    domains = mCollector.collectAutoVerifyDomains(pkg);
+                } else {
+                    domains.retainAll(mCollector.collectAutoVerifyDomains(pkg));
+                }
+
+                setDomainVerificationStatusInternal(pkgState, state, domains);
+            }
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    private void setDomainVerificationStatusInternal(@NonNull DomainVerificationPkgState pkgState,
+            int state, @NonNull ArraySet<String> validDomains) {
+        ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+        int size = validDomains.size();
+        for (int index = 0; index < size; index++) {
+            stateMap.put(validDomains.valueAt(index), state);
+        }
+    }
+
+    @Override
+    public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+            boolean allowed) throws NameNotFoundException {
+        setDomainVerificationLinkHandlingAllowed(packageName, allowed,
+                mConnection.getCallingUserId());
+    }
+
+    public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
+            boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
+        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), userId);
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+            }
+
+            pkgState.getOrCreateUserSelectionState(userId)
+                    .setDisallowLinkHandling(!allowed);
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    @Override
+    public void setDomainVerificationLinkHandlingAllowedInternal(@Nullable String packageName,
+            boolean allowed, @UserIdInt int userId) throws NameNotFoundException {
+        mEnforcer.assertInternal(mConnection.getCallingUid());
+        if (packageName == null) {
+            synchronized (mLock) {
+                int pkgStateSize = mAttachedPkgStates.size();
+                for (int pkgStateIndex = 0; pkgStateIndex < pkgStateSize; pkgStateIndex++) {
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
+                    if (userId == UserHandle.USER_ALL) {
+                        SparseArray<DomainVerificationUserState> userStates =
+                                pkgState.getUserSelectionStates();
+                        int userStatesSize = userStates.size();
+                        for (int userStateIndex = 0; userStateIndex < userStatesSize;
+                                userStateIndex++) {
+                            userStates.valueAt(userStateIndex)
+                                    .setDisallowLinkHandling(!allowed);
+                        }
+                    } else {
+                        pkgState.getOrCreateUserSelectionState(userId)
+                                .setDisallowLinkHandling(!allowed);
+                    }
+                }
+
+            }
+        } else {
+            synchronized (mLock) {
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+                if (pkgState == null) {
+                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+                }
+
+                pkgState.getOrCreateUserSelectionState(userId)
+                        .setDisallowLinkHandling(!allowed);
+            }
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    @Override
+    public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+            @NonNull Set<String> domains, boolean enabled)
+            throws InvalidDomainSetException, NameNotFoundException {
+        setDomainVerificationUserSelection(domainSetId, domains, enabled,
+                mConnection.getCallingUserId());
+    }
+
+    public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
+            @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
+            throws InvalidDomainSetException, NameNotFoundException {
+        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), userId);
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
+                    false /* forAutoVerify */);
+            DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+            if (enabled) {
+                userState.addHosts(domains);
+            } else {
+                userState.removeHosts(domains);
+            }
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    @Override
+    public void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
+            @Nullable String packageName, boolean enabled, @NonNull ArraySet<String> domains)
+            throws NameNotFoundException {
+        mEnforcer.assertInternal(mConnection.getCallingUid());
+
+        if (packageName == null) {
+            synchronized (mLock) {
+                Set<String> validDomains = new ArraySet<>();
+
+                int size = mAttachedPkgStates.size();
+                for (int index = 0; index < size; index++) {
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                    String pkgName = pkgState.getPackageName();
+                    PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+                    if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                        continue;
+                    }
+
+                    validDomains.clear();
+                    validDomains.addAll(domains);
+
+                    setDomainVerificationUserSelectionInternal(userId, pkgState,
+                            pkgSetting.getPkg(), enabled, validDomains);
+                }
+            }
+        } else {
+            synchronized (mLock) {
+                DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+                if (pkgState == null) {
+                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+                }
+
+                PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
+                if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                    throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+                }
+
+                setDomainVerificationUserSelectionInternal(userId, pkgState, pkgSetting.getPkg(),
+                        enabled, domains);
+            }
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    private void setDomainVerificationUserSelectionInternal(int userId,
+            @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
+            boolean enabled, Set<String> domains) {
+        domains.retainAll(mCollector.collectAllWebDomains(pkg));
+
+        SparseArray<DomainVerificationUserState> userStates =
+                pkgState.getUserSelectionStates();
+        if (userId == UserHandle.USER_ALL) {
+            int size = userStates.size();
+            for (int index = 0; index < size; index++) {
+                DomainVerificationUserState userState = userStates.valueAt(index);
+                if (enabled) {
+                    userState.addHosts(domains);
+                } else {
+                    userState.removeHosts(domains);
+                }
+            }
+        } else {
+            DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
+            if (enabled) {
+                userState.addHosts(domains);
+            } else {
+                userState.removeHosts(domains);
+            }
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    @Nullable
+    @Override
+    public DomainVerificationUserSelection getDomainVerificationUserSelection(
+            @NonNull String packageName) throws NameNotFoundException {
+        return getDomainVerificationUserSelection(packageName,
+                mConnection.getCallingUserId());
+    }
+
+    @Nullable
+    @Override
+    public DomainVerificationUserSelection getDomainVerificationUserSelection(
+            @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
+        mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(),
+                mConnection.getCallingUserId(), userId);
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState == null) {
+                return null;
+            }
+
+            AndroidPackage pkg = mConnection.getPackageLocked(packageName);
+            if (pkg == null) {
+                throw DomainVerificationUtils.throwPackageUnavailable(packageName);
+            }
+
+            ArrayMap<String, Boolean> hostToUserSelectionMap = new ArrayMap<>();
+
+            ArraySet<String> domains = mCollector.collectAllWebDomains(pkg);
+            int domainsSize = domains.size();
+            for (int index = 0; index < domainsSize; index++) {
+                hostToUserSelectionMap.put(domains.valueAt(index), false);
+            }
+
+            boolean openVerifiedLinks = false;
+            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+            if (userState != null) {
+                openVerifiedLinks = !userState.isDisallowLinkHandling();
+                ArraySet<String> enabledHosts = userState.getEnabledHosts();
+                int hostsSize = enabledHosts.size();
+                for (int index = 0; index < hostsSize; index++) {
+                    hostToUserSelectionMap.put(enabledHosts.valueAt(index), true);
+                }
+            }
+
+            return new DomainVerificationUserSelection(pkgState.getId(), packageName,
+                    UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
+        }
+    }
+
+    @NonNull
+    @Override
+    public UUID generateNewId() {
+        // TODO(b/159952358): Domain set ID collisions
+        return UUID.randomUUID();
+    }
+
+    @Override
+    public void migrateState(@NonNull PackageSetting oldPkgSetting,
+            @NonNull PackageSetting newPkgSetting) {
+        String pkgName = newPkgSetting.name;
+        boolean sendBroadcast;
+
+        synchronized (mLock) {
+            UUID oldDomainSetId = oldPkgSetting.getDomainSetId();
+            UUID newDomainSetId = newPkgSetting.getDomainSetId();
+            DomainVerificationPkgState oldPkgState = mAttachedPkgStates.remove(oldDomainSetId);
+
+            AndroidPackage oldPkg = oldPkgSetting.getPkg();
+            AndroidPackage newPkg = newPkgSetting.getPkg();
+
+            ArrayMap<String, Integer> newStateMap = new ArrayMap<>();
+            SparseArray<DomainVerificationUserState> newUserStates = new SparseArray<>();
+
+            if (oldPkgState == null || oldPkg == null || newPkg == null) {
+                // Should be impossible, but to be safe, continue with a new blank state instead
+                Slog.wtf(TAG, "Invalid state nullability old state = " + oldPkgState
+                        + ", old pkgSetting = " + oldPkgSetting
+                        + ", new pkgSetting = " + newPkgSetting
+                        + ", old pkg = " + oldPkg
+                        + ", new pkg = " + newPkg, new Exception());
+
+                DomainVerificationPkgState newPkgState = new DomainVerificationPkgState(
+                        pkgName, newDomainSetId, true, newStateMap, newUserStates);
+                mAttachedPkgStates.put(pkgName, newDomainSetId, newPkgState);
+                return;
+            }
+
+            ArrayMap<String, Integer> oldStateMap = oldPkgState.getStateMap();
+            ArraySet<String> newAutoVerifyDomains = mCollector.collectAutoVerifyDomains(newPkg);
+            int newDomainsSize = newAutoVerifyDomains.size();
+
+            for (int newDomainsIndex = 0; newDomainsIndex < newDomainsSize; newDomainsIndex++) {
+                String domain = newAutoVerifyDomains.valueAt(newDomainsIndex);
+                Integer oldStateInteger = oldStateMap.get(domain);
+                if (oldStateInteger != null) {
+                    int oldState = oldStateInteger;
+                    switch (oldState) {
+                        case DomainVerificationState.STATE_SUCCESS:
+                        case DomainVerificationState.STATE_RESTORED:
+                        case DomainVerificationState.STATE_MIGRATED:
+                            newStateMap.put(domain, oldState);
+                            break;
+                        default:
+                            // In all other cases, the state code is left unset
+                            // (STATE_NO_RESPONSE) to signal to the verification agent that any
+                            // existing error has been cleared and the domain should be
+                            // re-attempted. This makes update of a package a signal to
+                            // re-verify.
+                            break;
+                    }
+                }
+            }
+
+            SparseArray<DomainVerificationUserState> oldUserStates =
+                    oldPkgState.getUserSelectionStates();
+            int oldUserStatesSize = oldUserStates.size();
+            if (oldUserStatesSize > 0) {
+                ArraySet<String> newWebDomains = mCollector.collectAutoVerifyDomains(newPkg);
+                for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize;
+                        oldUserStatesIndex++) {
+                    int userId = oldUserStates.keyAt(oldUserStatesIndex);
+                    DomainVerificationUserState oldUserState = oldUserStates.valueAt(
+                            oldUserStatesIndex);
+                    ArraySet<String> oldEnabledHosts = oldUserState.getEnabledHosts();
+                    ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
+                    newEnabledHosts.retainAll(newWebDomains);
+                    DomainVerificationUserState newUserState = new DomainVerificationUserState(
+                            userId, newEnabledHosts, oldUserState.isDisallowLinkHandling());
+                    newUserStates.put(userId, newUserState);
+                }
+            }
+
+            boolean hasAutoVerifyDomains = newDomainsSize > 0;
+            boolean needsBroadcast =
+                    applyImmutableState(pkgName, newStateMap, newAutoVerifyDomains);
+
+            sendBroadcast = hasAutoVerifyDomains && needsBroadcast;
+
+            mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState(
+                    pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates));
+        }
+
+        if (sendBroadcast) {
+            sendBroadcastForPackage(pkgName);
+        }
+    }
+
+    // TODO(b/159952358): Handle valid domainSetIds for PackageSettings with no AndroidPackage
+    @Override
+    public void addPackage(@NonNull PackageSetting newPkgSetting) {
+        // TODO(b/159952358): Optimize packages without any domains. Those wouldn't have to be in
+        //  the state map, but it would require handling the "migration" case where an app either
+        //  gains or loses all domains.
+
+        UUID domainSetId = newPkgSetting.getDomainSetId();
+        String pkgName = newPkgSetting.name;
+
+        boolean sendBroadcast = true;
+
+        DomainVerificationPkgState pkgState;
+        pkgState = mSettings.getPendingState(pkgName);
+        if (pkgState != null) {
+            // Don't send when attaching from pending read, which is usually boot scan. Re-send on
+            // boot is handled in a separate method once all packages are added.
+            sendBroadcast = false;
+        } else {
+            pkgState = mSettings.getRestoredState(pkgName);
+        }
+
+        AndroidPackage pkg = newPkgSetting.getPkg();
+        ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
+        boolean hasAutoVerifyDomains = !domains.isEmpty();
+        boolean isPendingOrRestored = pkgState != null;
+        if (isPendingOrRestored) {
+            pkgState.setId(domainSetId);
+        } else {
+            pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains);
+        }
+
+        boolean needsBroadcast = applyImmutableState(pkgState, domains);
+        if (needsBroadcast && !isPendingOrRestored) {
+            // TODO(b/159952358): Test this behavior
+            // Attempt to preserve user experience by automatically verifying all domains from
+            // legacy state if they were previously approved, or by automatically enabling all
+            // hosts through user selection if legacy state indicates a user previously made the
+            // choice in settings to allow supported links. The domain verification agent should
+            // re-verify these links (set to STATE_MIGRATED) at the next possible opportunity,
+            // and disable them if appropriate.
+            ArraySet<String> webDomains = null;
+
+            SparseIntArray legacyUserStates = mLegacySettings.getUserStates(pkgName);
+            int userStateSize = legacyUserStates == null ? 0 : legacyUserStates.size();
+            for (int index = 0; index < userStateSize; index++) {
+                int userId = legacyUserStates.keyAt(index);
+                int legacyStatus = legacyUserStates.valueAt(index);
+                if (legacyStatus
+                        == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+                    if (webDomains == null) {
+                        webDomains = mCollector.collectAllWebDomains(pkg);
+                    }
+
+                    pkgState.getOrCreateUserSelectionState(userId).addHosts(webDomains);
+                }
+            }
+
+            IntentFilterVerificationInfo legacyInfo = mLegacySettings.remove(pkgName);
+            if (legacyInfo != null
+                    && legacyInfo.getStatus()
+                    == PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
+                ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+                int domainsSize = domains.size();
+                for (int index = 0; index < domainsSize; index++) {
+                    stateMap.put(domains.valueAt(index), DomainVerificationState.STATE_MIGRATED);
+                }
+            }
+        }
+
+        synchronized (mLock) {
+            mAttachedPkgStates.put(pkgName, domainSetId, pkgState);
+        }
+
+        if (sendBroadcast && hasAutoVerifyDomains) {
+            sendBroadcastForPackage(pkgName);
+        }
+    }
+
+    private boolean applyImmutableState(@NonNull DomainVerificationPkgState pkgState,
+            @NonNull ArraySet<String> autoVerifyDomains) {
+        return applyImmutableState(pkgState.getPackageName(), pkgState.getStateMap(),
+                autoVerifyDomains);
+    }
+
+    /**
+     * Applies any immutable state as the final step when adding or migrating state. Currently only
+     * applies {@link SystemConfig#getLinkedApps()}, which approves all domains for a package.
+     *
+     * @return whether or not a broadcast is necessary for this package
+     */
+    private boolean applyImmutableState(@NonNull String packageName,
+            @NonNull ArrayMap<String, Integer> stateMap,
+            @NonNull ArraySet<String> autoVerifyDomains) {
+        if (mSystemConfig.getLinkedApps().contains(packageName)) {
+            int domainsSize = autoVerifyDomains.size();
+            for (int index = 0; index < domainsSize; index++) {
+                stateMap.put(autoVerifyDomains.valueAt(index),
+                        DomainVerificationState.STATE_SYS_CONFIG);
+            }
+            return false;
+        } else {
+            int size = stateMap.size();
+            for (int index = size - 1; index >= 0; index--) {
+                Integer state = stateMap.valueAt(index);
+                // If no longer marked in SysConfig, demote any previous SysConfig state
+                if (state == DomainVerificationState.STATE_SYS_CONFIG) {
+                    stateMap.removeAt(index);
+                }
+            }
+
+            return true;
+        }
+    }
+
+    @Override
+    public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException {
+        synchronized (mLock) {
+            mSettings.writeSettings(serializer, mAttachedPkgStates);
+        }
+
+        mLegacySettings.writeSettings(serializer);
+    }
+
+    @Override
+    public void readSettings(@NonNull TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        synchronized (mLock) {
+            mSettings.readSettings(parser, mAttachedPkgStates);
+        }
+    }
+
+    @Override
+    public void readLegacySettings(@NonNull TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        mLegacySettings.readSettings(parser);
+    }
+
+    @Override
+    public void restoreSettings(@NonNull TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        synchronized (mLock) {
+            mSettings.restoreSettings(parser, mAttachedPkgStates);
+        }
+    }
+
+    @Override
+    public void addLegacySetting(@NonNull String packageName,
+            @NonNull IntentFilterVerificationInfo info) {
+        mLegacySettings.add(packageName, info);
+    }
+
+    @Override
+    public void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state) {
+        mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid());
+        mLegacySettings.add(packageName, userId, state);
+    }
+
+    @Override
+    public int getLegacyState(@NonNull String packageName, @UserIdInt int userId) {
+        return mLegacySettings.getUserState(packageName, userId);
+    }
+
+    @Override
+    public void writeLegacySettings(TypedXmlSerializer serializer, String name) {
+
+    }
+
+    @Override
+    public void clearPackage(@NonNull String packageName) {
+        synchronized (mLock) {
+            mAttachedPkgStates.remove(packageName);
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    @Override
+    public void clearUser(@UserIdInt int userId) {
+        synchronized (mLock) {
+            int attachedSize = mAttachedPkgStates.size();
+            for (int index = 0; index < attachedSize; index++) {
+                mAttachedPkgStates.valueAt(index).removeUser(userId);
+            }
+
+            mSettings.removeUser(userId);
+        }
+
+        mConnection.scheduleWriteSettings();
+    }
+
+    @Override
+    public boolean runMessage(int messageCode, Object object) {
+        return mProxy.runMessage(messageCode, object);
+    }
+
+    @Override
+    public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+            @Nullable @UserIdInt Integer userId) throws NameNotFoundException {
+        synchronized (mLock) {
+            mDebug.printState(writer, packageName, userId, mConnection, mAttachedPkgStates);
+        }
+    }
+
+    @NonNull
+    @Override
+    public DomainVerificationShell getShell() {
+        return mShell;
+    }
+
+    @NonNull
+    @Override
+    public DomainVerificationCollector getCollector() {
+        return mCollector;
+    }
+
+    private void sendBroadcastForPackage(@NonNull String packageName) {
+        mProxy.sendBroadcastForPackages(Collections.singleton(packageName));
+    }
+
+    private boolean hasRealVerifier() {
+        return !(mProxy instanceof DomainVerificationProxyUnavailable);
+    }
+
+    /**
+     * Validates parameters provided by an external caller. Checks that an ID is still live and that
+     * any provided domains are valid. Should be called at the beginning of each API that takes in a
+     * {@link UUID} domain set ID.
+     */
+    @GuardedBy("mLock")
+    private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
+            @NonNull Set<String> domains, boolean forAutoVerify)
+            throws InvalidDomainSetException, NameNotFoundException {
+        if (domainSetId == null) {
+            throw new InvalidDomainSetException(null, null,
+                    InvalidDomainSetException.REASON_ID_NULL);
+        }
+
+        DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId);
+        if (pkgState == null) {
+            throw new InvalidDomainSetException(domainSetId, null,
+                    InvalidDomainSetException.REASON_ID_INVALID);
+        }
+
+        String pkgName = pkgState.getPackageName();
+        PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+        if (pkgSetting == null || pkgSetting.getPkg() == null) {
+            throw DomainVerificationUtils.throwPackageUnavailable(pkgName);
+        }
+
+        if (CollectionUtils.isEmpty(domains)) {
+            throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
+                    InvalidDomainSetException.REASON_SET_NULL_OR_EMPTY);
+        }
+        AndroidPackage pkg = pkgSetting.getPkg();
+        ArraySet<String> declaredDomains = forAutoVerify
+                ? mCollector.collectAutoVerifyDomains(pkg)
+                : mCollector.collectAllWebDomains(pkg);
+
+        if (domains.retainAll(declaredDomains)) {
+            throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
+                    InvalidDomainSetException.REASON_UNKNOWN_DOMAIN);
+        }
+
+        return pkgState;
+    }
+
+    @Override
+    public void verifyPackages(@Nullable List<String> packageNames, boolean reVerify) {
+        mEnforcer.assertInternal(mConnection.getCallingUid());
+        Set<String> packagesToBroadcast = new ArraySet<>();
+
+        if (packageNames == null) {
+            synchronized (mLock) {
+                int pkgStatesSize = mAttachedPkgStates.size();
+                for (int pkgStateIndex = 0; pkgStateIndex < pkgStatesSize; pkgStateIndex++) {
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(pkgStateIndex);
+                    addIfShouldBroadcastLocked(packagesToBroadcast, pkgState, reVerify);
+                }
+            }
+        } else {
+            synchronized (mLock) {
+                int size = packageNames.size();
+                for (int index = 0; index < size; index++) {
+                    String packageName = packageNames.get(index);
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+                    if (pkgState != null) {
+                        addIfShouldBroadcastLocked(packagesToBroadcast, pkgState, reVerify);
+                    }
+                }
+            }
+        }
+
+        if (!packagesToBroadcast.isEmpty()) {
+            mProxy.sendBroadcastForPackages(packagesToBroadcast);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void addIfShouldBroadcastLocked(@NonNull Collection<String> packageNames,
+            @NonNull DomainVerificationPkgState pkgState, boolean reVerify) {
+        if ((reVerify && pkgState.isHasAutoVerifyDomains()) || shouldReBroadcastPackage(pkgState)) {
+            packageNames.add(pkgState.getPackageName());
+        }
+    }
+
+    /**
+     * Determine whether or not a broadcast should be sent at boot for the given {@param pkgState}.
+     * Sends only if the only states recorded are default as decided by {@link
+     * DomainVerificationManager#isStateDefault(int)}.
+     *
+     * If any other state is set, it's assumed that the domain verification agent is aware of the
+     * package and has already scheduled future verification requests.
+     */
+    private boolean shouldReBroadcastPackage(DomainVerificationPkgState pkgState) {
+        if (!pkgState.isHasAutoVerifyDomains()) {
+            return false;
+        }
+
+        ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+        int statesSize = stateMap.size();
+        for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
+            Integer state = stateMap.valueAt(stateIndex);
+            if (!DomainVerificationManager.isStateDefault(state)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public void clearDomainVerificationState(@Nullable List<String> packageNames) {
+        mEnforcer.assertInternal(mConnection.getCallingUid());
+        synchronized (mLock) {
+            if (packageNames == null) {
+                int size = mAttachedPkgStates.size();
+                for (int index = 0; index < size; index++) {
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                    String pkgName = pkgState.getPackageName();
+                    PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+                    if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                        continue;
+                    }
+                    resetDomainState(pkgState, pkgSetting.getPkg());
+                }
+            } else {
+                int size = packageNames.size();
+                for (int index = 0; index < size; index++) {
+                    String pkgName = packageNames.get(index);
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
+                    PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
+                    if (pkgSetting == null || pkgSetting.getPkg() == null) {
+                        continue;
+                    }
+                    resetDomainState(pkgState, pkgSetting.getPkg());
+                }
+            }
+        }
+    }
+
+    /**
+     * Reset states that are mutable by the domain verification agent.
+     */
+    private void resetDomainState(@NonNull DomainVerificationPkgState pkgState,
+            @NonNull AndroidPackage pkg) {
+        ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+        int size = stateMap.size();
+        for (int index = size - 1; index >= 0; index--) {
+            Integer state = stateMap.valueAt(index);
+            boolean reset;
+            switch (state) {
+                case DomainVerificationState.STATE_SUCCESS:
+                case DomainVerificationState.STATE_RESTORED:
+                    reset = true;
+                    break;
+                default:
+                    reset = state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
+                    break;
+            }
+
+            if (reset) {
+                stateMap.removeAt(index);
+            }
+        }
+
+        applyImmutableState(pkgState, mCollector.collectAutoVerifyDomains(pkg));
+    }
+
+    @Override
+    public void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId) {
+        mEnforcer.assertInternal(mConnection.getCallingUid());
+        synchronized (mLock) {
+            if (packageNames == null) {
+                int size = mAttachedPkgStates.size();
+                for (int index = 0; index < size; index++) {
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
+                    if (userId == UserHandle.USER_ALL) {
+                        pkgState.removeAllUsers();
+                    } else {
+                        pkgState.removeUser(userId);
+                    }
+                }
+            } else {
+                int size = packageNames.size();
+                for (int index = 0; index < size; index++) {
+                    String pkgName = packageNames.get(index);
+                    DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName);
+                    if (userId == UserHandle.USER_ALL) {
+                        pkgState.removeAllUsers();
+                    } else {
+                        pkgState.removeUser(userId);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
+            @UserIdInt int userId) {
+        String packageName = pkgSetting.name;
+        if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) {
+            if (DEBUG_APPROVAL) {
+                debugApproval(packageName, intent, userId, false, "not valid intent");
+            }
+            return false;
+        }
+
+        String host = intent.getData().getHost();
+        final AndroidPackage pkg = pkgSetting.getPkg();
+
+        // Should never be null, but if it is, skip this and assume that v2 is enabled
+        if (pkg != null) {
+            // To allow an instant app to immediately open domains after being installed by the
+            // user, auto approve them for any declared autoVerify domains.
+            if (pkgSetting.getInstantApp(userId)
+                    && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
+                return true;
+            }
+
+            if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
+                int legacyState = mLegacySettings.getUserState(packageName, userId);
+                switch (legacyState) {
+                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
+                        // If nothing specifically set, assume v2 rules
+                        break;
+                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
+                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
+                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
+                        // With v2 split into 2 lists, always and undefined, the concept of whether
+                        // or not to ask is irrelevant. Assume the user wants this application to
+                        // open the domain.
+                        return true;
+                    case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
+                        // Never has the same semantics are before
+                        return false;
+                }
+            }
+        }
+
+        synchronized (mLock) {
+            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
+            if (pkgState == null) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, intent, userId, false, "pkgState unavailable");
+                }
+                return false;
+            }
+
+            ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
+            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
+
+            // Only allow autoVerify approval if the user hasn't disabled it
+            if (userState == null || !userState.isDisallowLinkHandling()) {
+                // Check if the exact host matches
+                Integer state = stateMap.get(host);
+                if (state != null && DomainVerificationManager.isStateVerified(state)) {
+                    if (DEBUG_APPROVAL) {
+                        debugApproval(packageName, intent, userId, true, "host verified exactly");
+                    }
+                    return true;
+                }
+
+                // Otherwise see if the host matches a verified domain by wildcard
+                int stateMapSize = stateMap.size();
+                for (int index = 0; index < stateMapSize; index++) {
+                    if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
+                        continue;
+                    }
+
+                    String domain = stateMap.keyAt(index);
+                    if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+                        if (DEBUG_APPROVAL) {
+                            debugApproval(packageName, intent, userId, true,
+                                    "host verified by wildcard");
+                        }
+                        return true;
+                    }
+                }
+            }
+
+            // Check user state if available
+            if (userState == null) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, intent, userId, false, "userState unavailable");
+                }
+                return false;
+            }
+
+            // See if the user has approved the exact host
+            ArraySet<String> enabledHosts = userState.getEnabledHosts();
+            if (enabledHosts.contains(host)) {
+                if (DEBUG_APPROVAL) {
+                    debugApproval(packageName, intent, userId, true,
+                            "host enabled by user exactly");
+                }
+                return true;
+            }
+
+            // See if the host matches a user selection by wildcard
+            int enabledHostsSize = enabledHosts.size();
+            for (int index = 0; index < enabledHostsSize; index++) {
+                String domain = enabledHosts.valueAt(index);
+                if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
+                    if (DEBUG_APPROVAL) {
+                        debugApproval(packageName, intent, userId, true,
+                                "host enabled by user through wildcard");
+                    }
+                    return true;
+                }
+            }
+
+            if (DEBUG_APPROVAL) {
+                debugApproval(packageName, intent, userId, false, "not approved");
+            }
+            return false;
+        }
+    }
+
+    private void debugApproval(@NonNull String packageName, @NonNull Intent intent,
+            @UserIdInt int userId, boolean approved, @NonNull String reason) {
+        String approvalString = approved ? "approved" : "denied";
+        Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + intent
+                + " for user " + userId + ": " + reason);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
new file mode 100644
index 0000000..073967e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class DomainVerificationSettings {
+
+    /**
+     * States read from disk that have yet to attach to a package, but are expected to, generally in
+     * the context of scanning packages already on disk. This is expected to be empty once the boot
+     * package scan completes.
+     **/
+    @GuardedBy("mLock")
+    @NonNull
+    private final ArrayMap<String, DomainVerificationPkgState> mPendingPkgStates = new ArrayMap<>();
+
+    /**
+     * States from restore that have yet to attach to a package. These are special in that their IDs
+     * are dropped when the package is installed/otherwise becomes available, because the ID will
+     * not match if the data is restored from a different device install.
+     * <p>
+     * If multiple restore calls come in and they overlap, the latest entry added for a package name
+     * will be taken, dropping any previous versions.
+     **/
+    @GuardedBy("mLock")
+    @NonNull
+    private final ArrayMap<String, DomainVerificationPkgState> mRestoredPkgStates =
+            new ArrayMap<>();
+
+    /**
+     * Lock for all state reads/writes.
+     */
+    private final Object mLock = new Object();
+
+
+    public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
+            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+            throws IOException {
+        synchronized (mLock) {
+            DomainVerificationPersistence.writeToXml(xmlSerializer, liveState,
+                    mPendingPkgStates, mRestoredPkgStates);
+        }
+    }
+
+    /**
+     * Parses a previously stored set of states and merges them with {@param liveState}, directly
+     * mutating the values. This is intended for reading settings written by {@link
+     * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap)} on the same device setup.
+     */
+    public void readSettings(@NonNull TypedXmlPullParser parser,
+            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+            throws IOException, XmlPullParserException {
+        DomainVerificationPersistence.ReadResult result =
+                DomainVerificationPersistence.readFromXml(parser);
+        ArrayMap<String, DomainVerificationPkgState> active = result.active;
+        ArrayMap<String, DomainVerificationPkgState> restored = result.restored;
+
+        synchronized (mLock) {
+            int activeSize = active.size();
+            for (int activeIndex = 0; activeIndex < activeSize; activeIndex++) {
+                DomainVerificationPkgState pkgState = active.valueAt(activeIndex);
+                String pkgName = pkgState.getPackageName();
+                DomainVerificationPkgState existingState = liveState.get(pkgName);
+                if (existingState != null) {
+                    // This branch should never be possible. Settings should be read from disk
+                    // before any states are attached. But just in case, handle it.
+                    if (!existingState.getId().equals(pkgState.getId())) {
+                        mergePkgState(existingState, pkgState);
+                    }
+                } else {
+                    mPendingPkgStates.put(pkgName, pkgState);
+                }
+            }
+
+            int restoredSize = restored.size();
+            for (int restoredIndex = 0; restoredIndex < restoredSize; restoredIndex++) {
+                DomainVerificationPkgState pkgState = restored.valueAt(restoredIndex);
+                mRestoredPkgStates.put(pkgState.getPackageName(), pkgState);
+            }
+        }
+    }
+
+    /**
+     * Parses a previously stored set of states and merges them with {@param liveState}, directly
+     * mutating the values. This is intended for restoration across device setups.
+     */
+    public void restoreSettings(@NonNull TypedXmlPullParser parser,
+            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+            throws IOException, XmlPullParserException {
+        // TODO(b/170746586): Restoration assumes user IDs match, which is probably not the case on
+        //  a new device.
+
+        DomainVerificationPersistence.ReadResult result =
+                DomainVerificationPersistence.readFromXml(parser);
+
+        // When restoring settings, both active and previously restored are merged, since they
+        // should both go into the newly restored data. Active is added on top of restored just
+        // in case a duplicate is found. Active should be preferred.
+        ArrayMap<String, DomainVerificationPkgState> stateList = result.restored;
+        stateList.putAll(result.active);
+
+        synchronized (mLock) {
+            for (int stateIndex = 0; stateIndex < stateList.size(); stateIndex++) {
+                DomainVerificationPkgState newState = stateList.valueAt(stateIndex);
+                String pkgName = newState.getPackageName();
+                DomainVerificationPkgState existingState = liveState.get(pkgName);
+                if (existingState == null) {
+                    existingState = mPendingPkgStates.get(pkgName);
+                }
+                if (existingState == null) {
+                    existingState = mRestoredPkgStates.get(pkgName);
+                }
+
+                if (existingState != null) {
+                    mergePkgState(existingState, newState);
+                } else {
+                    // If there's no existing state, that means the new state has to be transformed
+                    // in preparation for attaching to brand new package that may eventually be
+                    // installed. This means coercing STATE_SUCCESS and STATE_RESTORED to
+                    // STATE_RESTORED and dropping everything else, the same logic that
+                    // mergePkgState runs, without the merge part.
+                    ArrayMap<String, Integer> stateMap = newState.getStateMap();
+                    int size = stateMap.size();
+                    for (int index = 0; index < size; index++) {
+                        Integer stateInteger = stateMap.valueAt(index);
+                        if (stateInteger != null) {
+                            int state = stateInteger;
+                            if (state == DomainVerificationState.STATE_SUCCESS
+                                    || state == DomainVerificationState.STATE_RESTORED) {
+                                stateMap.setValueAt(index, state);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Merges a newly restored state with existing state. This should only be called for restore,
+     * when the IDs aren't required to match.
+     * <p>
+     * If the existing state for a domain is
+     * {@link DomainVerificationState#STATE_NO_RESPONSE}, then it will be overridden with
+     * {@link DomainVerificationState#STATE_RESTORED} if the restored state is
+     * {@link DomainVerificationState#STATE_SUCCESS} or
+     * {@link DomainVerificationState#STATE_RESTORED}.
+     * <p>
+     * Otherwise the existing state is preserved, assuming any system rules, success state, or
+     * specific error codes are fresher than the restored state. Essentially state is only restored
+     * to grant additional verifications to an app.
+     * <p>
+     * For user selection state, presence in either state will be considered an enabled host. NOTE:
+     * only {@link UserHandle#USER_SYSTEM} is merged. There is no restore path in place for
+     * multiple users.
+     * <p>
+     * TODO(b/170746586): Figure out the restore path for multiple users
+     * <p>
+     * This will mutate {@param oldState} to contain the merged state.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public static void mergePkgState(@NonNull DomainVerificationPkgState oldState,
+            @NonNull DomainVerificationPkgState newState) {
+        ArrayMap<String, Integer> oldStateMap = oldState.getStateMap();
+        ArrayMap<String, Integer> newStateMap = newState.getStateMap();
+        int size = newStateMap.size();
+        for (int index = 0; index < size; index++) {
+            String domain = newStateMap.keyAt(index);
+            Integer newStateCode = newStateMap.valueAt(index);
+            Integer oldStateCodeInteger = oldStateMap.get(domain);
+            if (oldStateCodeInteger == null) {
+                // Cannot add domains to an app
+                continue;
+            }
+
+            int oldStateCode = oldStateCodeInteger;
+            if (oldStateCode == DomainVerificationState.STATE_NO_RESPONSE) {
+                if (newStateCode == DomainVerificationState.STATE_SUCCESS
+                        || newStateCode == DomainVerificationState.STATE_RESTORED) {
+                    oldStateMap.put(domain, DomainVerificationState.STATE_RESTORED);
+                }
+            }
+        }
+
+        SparseArray<DomainVerificationUserState> oldSelectionStates =
+                oldState.getUserSelectionStates();
+
+        SparseArray<DomainVerificationUserState> newSelectionStates =
+                newState.getUserSelectionStates();
+
+        DomainVerificationUserState newUserState = newSelectionStates.get(UserHandle.USER_SYSTEM);
+        if (newUserState != null) {
+            ArraySet<String> newEnabledHosts = newUserState.getEnabledHosts();
+            DomainVerificationUserState oldUserState =
+                    oldSelectionStates.get(UserHandle.USER_SYSTEM);
+
+            boolean disallowLinkHandling = newUserState.isDisallowLinkHandling();
+            if (oldUserState == null) {
+                oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
+                        newEnabledHosts, disallowLinkHandling);
+                oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
+            } else {
+                oldUserState.addHosts(newEnabledHosts)
+                        .setDisallowLinkHandling(disallowLinkHandling);
+            }
+        }
+    }
+
+    public void removeUser(@UserIdInt int userId) {
+        int pendingSize = mPendingPkgStates.size();
+        for (int index = 0; index < pendingSize; index++) {
+            mPendingPkgStates.valueAt(index).removeUser(userId);
+        }
+
+        // TODO(b/170746586): Restored assumes user IDs match, which is probably not the case
+        //  on a new device
+        int restoredSize = mRestoredPkgStates.size();
+        for (int index = 0; index < restoredSize; index++) {
+            mRestoredPkgStates.valueAt(index).removeUser(userId);
+        }
+    }
+
+    @Nullable
+    public DomainVerificationPkgState getPendingState(@NonNull String pkgName) {
+        synchronized (mLock) {
+            return mPendingPkgStates.get(pkgName);
+        }
+    }
+
+    @Nullable
+    public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) {
+        synchronized (mLock) {
+            return mRestoredPkgStates.get(pkgName);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
new file mode 100644
index 0000000..7f9e75a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.content.pm.verify.domain.DomainVerificationUserSelection;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class DomainVerificationShell {
+
+    @NonNull
+    private final Callback mCallback;
+
+    public DomainVerificationShell(@NonNull Callback callback) {
+        mCallback = callback;
+    }
+
+    public void printHelp(@NonNull PrintWriter pw) {
+        pw.println("  get-app-links [--user <USER_ID>] [<PACKAGE>]");
+        pw.println("    Prints the domain verification state for the given package, or for all");
+        pw.println("    packages if none is specified.");
+        pw.println("      --user <USER_ID>: include user selections (includes all domains, not");
+        pw.println("        just autoVerify ones)");
+        pw.println("  reset-app-links [--user <USER_ID>] [<PACKAGE>]");
+        pw.println("    Resets domain verification state for the given package, or for all");
+        pw.println("    packages if none is specified.");
+        pw.println("      --user <USER_ID>: clear user selection state instead; note this means");
+        pw.println("        domain verification state will NOT be cleared");
+        pw.println("      <PACKAGE>: the package to reset, or \"all\" to reset all packages");
+        pw.println("  verify-app-links [--re-verify] [<PACKAGE>]");
+        pw.println("    Broadcasts a verification request for the given package, or for all");
+        pw.println("    packages if none is specified. Only sends if the package has previously");
+        pw.println("    not recorded a response.");
+        pw.println("      --re-verify: send even if the package has recorded a response");
+        pw.println("  set-app-links [--package <PACKAGE>] <STATE> <DOMAINS>...");
+        pw.println("    Manually set the state of a domain for a package. The domain must be");
+        pw.println("    declared by the package as autoVerify for this to work. This command");
+        pw.println("    will not report a failure for domains that could not be applied.");
+        pw.println("      --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+        pw.println("      <STATE>: the code to set the domains to, valid values are:");
+        pw.println("        STATE_NO_RESPONSE (0): reset as if no response was ever recorded.");
+        pw.println("        STATE_SUCCESS (1): treat domain as successfully verified by domain.");
+        pw.println("          verification agent. Note that the domain verification agent can");
+        pw.println("          override this.");
+        pw.println("        STATE_APPROVED (2): treat domain as always approved, preventing the");
+        pw.println("           domain verification agent from changing it.");
+        pw.println("        STATE_DENIED (3): treat domain as always denied, preveting the domain");
+        pw.println("          verification agent from changing it.");
+        pw.println("      <DOMAINS>: space separated list of domains to change, or \"all\" to");
+        pw.println("        change every domain.");
+        pw.println("  set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>]");
+        pw.println("      <ENABLED> <DOMAINS>...");
+        pw.println("    Manually set the state of a host user selection for a package. The domain");
+        pw.println("    must be declared by the package for this to work. This command will not");
+        pw.println("    report a failure for domains that could not be applied.");
+        pw.println("      --user <USER_ID>: the user to change selections for");
+        pw.println("      --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+        pw.println("      <ENABLED>: whether or not to approve the domain");
+        pw.println("      <DOMAINS>: space separated list of domains to change, or \"all\" to");
+        pw.println("        change every domain.");
+        pw.println("  set-app-links-allowed --user <USER_ID> [--package <PACKAGE>] <ALLOWED>");
+        pw.println("      <ENABLED> <DOMAINS>...");
+        pw.println("    Toggle the auto verified link handling setting for a package.");
+        pw.println("      --user <USER_ID>: the user to change selections for");
+        pw.println("      --package <PACKAGE>: the package to set, or \"all\" to set all packages");
+        pw.println("        packages will be reset if no one package is specified.");
+        pw.println("      <ALLOWED>: true to allow the package to open auto verified links, false");
+        pw.println("        to disable");
+    }
+
+    /**
+     * Run a shell/debugging command.
+     *
+     * @return null if the command is unhandled, true if the command succeeded, false if it failed
+     */
+    public Boolean runCommand(@NonNull BasicShellCommandHandler commandHandler,
+            @NonNull String command) {
+        switch (command) {
+            case "get-app-links":
+                return runGetAppLinks(commandHandler);
+            case "reset-app-links":
+                return runResetAppLinks(commandHandler);
+            case "verify-app-links":
+                return runVerifyAppLinks(commandHandler);
+            case "set-app-links":
+                return runSetAppLinks(commandHandler);
+            case "set-app-links-user-selection":
+                return runSetAppLinksUserSelection(commandHandler);
+            case "set-app-links-allowed":
+                return runSetAppLinksAllowed(commandHandler);
+        }
+
+        return null;
+    }
+
+
+    // pm set-app-links [--package <PACKAGE>] <STATE> <DOMAINS>...
+    private boolean runSetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+        String packageName = null;
+
+        String option;
+        while ((option = commandHandler.getNextOption()) != null) {
+            if (option.equals("--package")) {
+                packageName = commandHandler.getNextArgRequired();
+            } else {
+                commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+                return false;
+            }
+        }
+
+        if (TextUtils.isEmpty(packageName)) {
+            commandHandler.getErrPrintWriter().println("Error: no package specified");
+            return false;
+        } else if (packageName.equalsIgnoreCase("all")) {
+            packageName = null;
+        }
+
+        String state = commandHandler.getNextArgRequired();
+        int stateInt;
+        switch (state) {
+            case "STATE_NO_RESPONSE":
+            case "0":
+                stateInt = DomainVerificationState.STATE_NO_RESPONSE;
+                break;
+            case "STATE_SUCCESS":
+            case "1":
+                stateInt = DomainVerificationState.STATE_SUCCESS;
+                break;
+            case "STATE_APPROVED":
+            case "2":
+                stateInt = DomainVerificationState.STATE_APPROVED;
+                break;
+            case "STATE_DENIED":
+            case "3":
+                stateInt = DomainVerificationState.STATE_DENIED;
+                break;
+            default:
+                commandHandler.getErrPrintWriter().println("Invalid state option: " + state);
+                return false;
+        }
+
+        ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler));
+        if (domains.isEmpty()) {
+            commandHandler.getErrPrintWriter().println("No domains specified");
+            return false;
+        }
+
+        if (domains.size() == 1 && domains.contains("all")) {
+            domains = null;
+        }
+
+        try {
+            mCallback.setDomainVerificationStatusInternal(packageName, stateInt,
+                    domains);
+        } catch (NameNotFoundException e) {
+            commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+            return false;
+        }
+        return true;
+    }
+
+    // pm set-app-links-user-selection --user <USER_ID> [--package <PACKAGE>] <ENABLED> <DOMAINS>...
+    private boolean runSetAppLinksUserSelection(@NonNull BasicShellCommandHandler commandHandler) {
+        Integer userId = null;
+        String packageName = null;
+
+        String option;
+        while ((option = commandHandler.getNextOption()) != null) {
+            switch (option) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+                    break;
+                case "--package":
+                    packageName = commandHandler.getNextArgRequired();
+                    break;
+                default:
+                    commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+                    return false;
+            }
+        }
+
+        if (TextUtils.isEmpty(packageName)) {
+            commandHandler.getErrPrintWriter().println("Error: no package specified");
+            return false;
+        } else if (packageName.equalsIgnoreCase("all")) {
+            packageName = null;
+        }
+
+        if (userId == null) {
+            commandHandler.getErrPrintWriter().println("Error: User ID not specified");
+            return false;
+        }
+
+        userId = translateUserId(userId, "runSetAppLinksUserSelection");
+
+        String enabledString = commandHandler.getNextArgRequired();
+
+        // Manually ensure that "true" and "false" are the only options, to ensure a domain isn't
+        // accidentally parsed as a boolean
+        boolean enabled;
+        switch (enabledString) {
+            case "true":
+                enabled = true;
+                break;
+            case "false":
+                enabled = false;
+                break;
+            default:
+                commandHandler.getErrPrintWriter().println(
+                        "Invalid enabled param: " + enabledString);
+                return false;
+        }
+
+        ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler));
+        if (domains.isEmpty()) {
+            commandHandler.getErrPrintWriter().println("No domains specified");
+            return false;
+        }
+
+        try {
+            mCallback.setDomainVerificationUserSelectionInternal(userId,
+                    packageName, enabled, domains);
+        } catch (NameNotFoundException e) {
+            commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+            return false;
+        }
+        return true;
+    }
+
+    // pm get-app-links [--user <USER_ID>] [<PACKAGE>]
+    private boolean runGetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+        Integer userId = null;
+
+        String option;
+        while ((option = commandHandler.getNextOption()) != null) {
+            if (option.equals("--user")) {
+                userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+            } else {
+                commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+                return false;
+            }
+        }
+
+        userId = userId == null ? null : translateUserId(userId, "runGetAppLinks");
+
+        String packageName = commandHandler.getNextArg();
+
+        try (IndentingPrintWriter writer = new IndentingPrintWriter(
+                commandHandler.getOutPrintWriter(), /* singleIndent */ "  ", /* wrapLength */
+                120)) {
+            writer.increaseIndent();
+            try {
+                mCallback.printState(writer, packageName, userId);
+            } catch (NameNotFoundException e) {
+                commandHandler.getErrPrintWriter().println(
+                        "Error: package " + packageName + " unavailable");
+                return false;
+            }
+            writer.decreaseIndent();
+            return true;
+        }
+    }
+
+    // pm reset-app-links [--user USER_ID] [<PACKAGE>]
+    private boolean runResetAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+        Integer userId = null;
+
+        String option;
+        while ((option = commandHandler.getNextOption()) != null) {
+            if (option.equals("--user")) {
+                userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+            } else {
+                commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+                return false;
+            }
+        }
+
+        userId = userId == null ? null : translateUserId(userId, "runResetAppLinks");
+
+        List<String> packageNames;
+        String pkgNameArg = commandHandler.peekNextArg();
+        if (TextUtils.isEmpty(pkgNameArg)) {
+            commandHandler.getErrPrintWriter().println("Error: no package specified");
+            return false;
+        } else if (pkgNameArg.equalsIgnoreCase("all")) {
+            packageNames = null;
+        } else {
+            packageNames = Arrays.asList(commandHandler.peekRemainingArgs());
+        }
+
+        if (userId != null) {
+            mCallback.clearUserSelections(packageNames, userId);
+        } else {
+            mCallback.clearDomainVerificationState(packageNames);
+        }
+
+        return true;
+    }
+
+    // pm verify-app-links [--re-verify] [<PACKAGE>]
+    private boolean runVerifyAppLinks(@NonNull BasicShellCommandHandler commandHandler) {
+        boolean reVerify = false;
+        String option;
+        while ((option = commandHandler.getNextOption()) != null) {
+            if (option.equals("--re-verify")) {
+                reVerify = true;
+            } else {
+                commandHandler.getErrPrintWriter().println("Error: unknown option: " + option);
+                return false;
+            }
+        }
+
+        List<String> packageNames = null;
+        String pkgNameArg = commandHandler.getNextArg();
+        if (!TextUtils.isEmpty(pkgNameArg)) {
+            packageNames = Collections.singletonList(pkgNameArg);
+        }
+
+        mCallback.verifyPackages(packageNames, reVerify);
+
+        return true;
+    }
+
+    // pm set-app-links-allowed [--package <PACKAGE>] [--user <USER_ID>] <ALLOWED>
+    private boolean runSetAppLinksAllowed(@NonNull BasicShellCommandHandler commandHandler) {
+        String packageName = null;
+        Integer userId = null;
+        Boolean allowed = null;
+        String option;
+        while ((option = commandHandler.getNextOption()) != null) {
+            if (option.equals("--package")) {
+                packageName = commandHandler.getNextArgRequired();
+            } if (option.equals("--user")) {
+                userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
+            } else if (allowed == null) {
+                allowed = Boolean.valueOf(option);
+            } else {
+                commandHandler.getErrPrintWriter().println("Error: unexpected option: " + option);
+                return false;
+            }
+        }
+
+        if (TextUtils.isEmpty(packageName)) {
+            commandHandler.getErrPrintWriter().println("Error: no package specified");
+            return false;
+        } else if (packageName.equalsIgnoreCase("all")) {
+            packageName = null;
+        }
+
+        if (userId == null) {
+            commandHandler.getErrPrintWriter().println("Error: user ID not specified");
+            return false;
+        }
+
+        if (allowed == null) {
+            commandHandler.getErrPrintWriter().println("Error: allowed setting not specified");
+            return false;
+        }
+
+        userId = translateUserId(userId, "runSetAppLinksAllowed");
+
+        try {
+            mCallback.setDomainVerificationLinkHandlingAllowedInternal(packageName, allowed,
+                    userId);
+        } catch (NameNotFoundException e) {
+            commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
+            return false;
+        }
+
+        return true;
+    }
+
+    private ArrayList<String> getRemainingArgs(@NonNull BasicShellCommandHandler commandHandler) {
+        ArrayList<String> args = new ArrayList<>();
+        String arg;
+        while ((arg = commandHandler.getNextArg()) != null) {
+            args.add(arg);
+        }
+        return args;
+    }
+
+    private int translateUserId(@UserIdInt int userId, @NonNull String logContext) {
+        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, true, logContext, "pm command");
+    }
+
+    /**
+     * Separated interface from {@link DomainVerificationManagerInternal} to hide methods that are
+     * even more internal, and so that testing is easier.
+     */
+    public interface Callback {
+
+        /**
+         * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+         * the state for a domain.
+         *
+         * @param packageName the package whose state to change, or all packages if none is
+         *                    specified
+         * @param state       the new state code, valid values are
+         *                    {@link DomainVerificationState#STATE_NO_RESPONSE},
+         *                    {@link DomainVerificationState#STATE_SUCCESS}, {@link
+         *                    DomainVerificationState#STATE_APPROVED}, and {@link
+         *                    DomainVerificationState#STATE_DENIED}
+         * @param domains     the set of domains to change, or null to change all of them
+         */
+        void setDomainVerificationStatusInternal(@Nullable String packageName, int state,
+                @Nullable ArraySet<String> domains) throws PackageManager.NameNotFoundException;
+
+        /**
+         * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+         * the state for a domain.
+         *
+         * @param packageName the package whose state to change, or all packages if non is
+         *                    specified
+         * @param enabled     whether the domain is now approved by the user
+         * @param domains     the set of domains to change
+         */
+        void setDomainVerificationUserSelectionInternal(@UserIdInt int userId,
+                @Nullable String packageName, boolean enabled, @NonNull ArraySet<String> domains)
+                throws PackageManager.NameNotFoundException;
+
+        /**
+         * @see DomainVerificationManager#getDomainVerificationUserSelection(String)
+         */
+        @Nullable
+        DomainVerificationUserSelection getDomainVerificationUserSelection(
+                @NonNull String packageName, @UserIdInt int userId)
+                throws PackageManager.NameNotFoundException;
+
+        /**
+         * Variant for use by PackageManagerShellCommand to allow the system/developer to override
+         * the setting for a package.
+         *
+         * @param packageName the package whose state to change, or all packages if non is
+         *                    specified
+         * @param allowed     whether the package is allowed to automatically open links through
+         *                    domain verification
+         */
+        void setDomainVerificationLinkHandlingAllowedInternal(@Nullable String packageName,
+                boolean allowed, @UserIdInt int userId) throws NameNotFoundException;
+
+        /**
+         * Reset all the domain verification states for all domains for the given package names, or
+         * all package names if null is provided.
+         */
+        void clearDomainVerificationState(@Nullable List<String> packageNames);
+
+        /**
+         * Reset all the user selections for the given package names, or all package names if null
+         * is provided.
+         */
+        void clearUserSelections(@Nullable List<String> packageNames, @UserIdInt int userId);
+
+        /**
+         * Broadcast a verification request for the given package names, or all package names if
+         * null is provided. By default only re-broadcasts if a package has not recorded a
+         * response.
+         *
+         * @param reVerify send even if the package has previously recorded a response
+         */
+        void verifyPackages(@Nullable List<String> packageNames, boolean reVerify);
+
+        /**
+         * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer)
+         */
+        void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
+                @Nullable @UserIdInt Integer userId) throws NameNotFoundException;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
new file mode 100644
index 0000000..474f822
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain;
+
+import android.annotation.CheckResult;
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+
+import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+final class DomainVerificationUtils {
+
+    /**
+     * Consolidates package exception messages. A generic unavailable message is included since the
+     * caller doesn't bother to check why the package isn't available.
+     */
+    @CheckResult
+    static NameNotFoundException throwPackageUnavailable(@NonNull String packageName)
+            throws NameNotFoundException {
+        throw new NameNotFoundException("Package " + packageName + " unavailable");
+    }
+
+    static boolean isDomainVerificationIntent(Intent intent) {
+        return intent.isWebIntent()
+                && intent.hasCategory(Intent.CATEGORY_BROWSABLE)
+                && intent.hasCategory(Intent.CATEGORY_DEFAULT);
+    }
+
+    static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg,
+            long changeId) {
+        //noinspection ConstantConditions
+        return Binder.withCleanCallingIdentity(
+                () -> platformCompat.isChangeEnabled(changeId, buildMockAppInfo(pkg)));
+    }
+
+    /**
+     * Passed to {@link PlatformCompat} because this can be invoked mid-install process or when
+     * {@link PackageManagerService#mLock} is being held, and {@link PlatformCompat} will not be
+     * able to query the pending {@link ApplicationInfo} from {@link PackageManager}.
+     * <p>
+     * TODO(b/177613575): Can a different API be used?
+     */
+    @NonNull
+    private static ApplicationInfo buildMockAppInfo(@NonNull AndroidPackage pkg) {
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = pkg.getPackageName();
+        appInfo.targetSdkVersion = pkg.getTargetSdkVersion();
+        return appInfo;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
new file mode 100644
index 0000000..c6c9791
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "PackageManagerServiceUnitTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.pm.test.verify.domain"
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
new file mode 100644
index 0000000..48099aa
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain.models;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * State for a single package for the domain verification APIs. Stores the state of each individual
+ * domain declared by the package, including its verification state and user selection state.
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public class DomainVerificationPkgState {
+
+    @NonNull
+    private final String mPackageName;
+
+    @NonNull
+    private UUID mId;
+
+    /**
+     * Whether or not the package declares any autoVerify domains. This is separate from an empty
+     * check on the map itself, because an empty map means no response recorded, not necessarily no
+     * domains declared. When this is false, {@link #mStateMap} will be empty, but
+     * {@link #mUserSelectionStates} may contain any domains the user has explicitly chosen to
+     * allow this package to open, which may or may not be marked autoVerify.
+     */
+    private final boolean mHasAutoVerifyDomains;
+
+    /**
+     * Map of domains to state integers. Only domains that are not set to the default value of
+     * {@link DomainVerificationState#STATE_NO_RESPONSE} are included.
+     *
+     * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+     *  such as storing no state when the package is marked as a linked app in SystemConfig.
+     */
+    @NonNull
+    private final ArrayMap<String, Integer> mStateMap;
+
+    @NonNull
+    private final SparseArray<DomainVerificationUserState> mUserSelectionStates;
+
+    public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
+            boolean hasAutoVerifyDomains) {
+        this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0));
+    }
+
+    @Nullable
+    public DomainVerificationUserState getUserSelectionState(@UserIdInt int userId) {
+        return mUserSelectionStates.get(userId);
+    }
+
+    @Nullable
+    public DomainVerificationUserState getOrCreateUserSelectionState(@UserIdInt int userId) {
+        DomainVerificationUserState userState = mUserSelectionStates.get(userId);
+        if (userState == null) {
+            userState = new DomainVerificationUserState(userId);
+            mUserSelectionStates.put(userId, userState);
+        }
+        return userState;
+    }
+
+    public void setId(@NonNull UUID id) {
+        mId = id;
+    }
+
+    public void removeUser(@UserIdInt int userId) {
+        mUserSelectionStates.remove(userId);
+    }
+
+    public void removeAllUsers() {
+        mUserSelectionStates.clear();
+    }
+
+    private int userSelectionStatesHashCode() {
+        return mUserSelectionStates.contentHashCode();
+    }
+
+    private boolean userSelectionStatesEquals(
+            @NonNull SparseArray<DomainVerificationUserState> other) {
+        return mUserSelectionStates.contentEquals(other);
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new DomainVerificationPkgState.
+     *
+     * @param stateMap
+     *   Map of domains to state integers. Only domains that are not set to the default value of
+     *   {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+     *
+     *   TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+     *    such as storing no state when the package is marked as a linked app in SystemConfig.
+     */
+    @DataClass.Generated.Member
+    public DomainVerificationPkgState(
+            @NonNull String packageName,
+            @NonNull UUID id,
+            boolean hasAutoVerifyDomains,
+            @NonNull ArrayMap<String,Integer> stateMap,
+            @NonNull SparseArray<DomainVerificationUserState> userSelectionStates) {
+        this.mPackageName = packageName;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPackageName);
+        this.mId = id;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mId);
+        this.mHasAutoVerifyDomains = hasAutoVerifyDomains;
+        this.mStateMap = stateMap;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mStateMap);
+        this.mUserSelectionStates = userSelectionStates;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mUserSelectionStates);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull String getPackageName() {
+        return mPackageName;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull UUID getId() {
+        return mId;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isHasAutoVerifyDomains() {
+        return mHasAutoVerifyDomains;
+    }
+
+    /**
+     * Map of domains to state integers. Only domains that are not set to the default value of
+     * {@link DomainVerificationManager#STATE_NO_RESPONSE} are included.
+     *
+     * TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
+     *  such as storing no state when the package is marked as a linked app in SystemConfig.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArrayMap<String,Integer> getStateMap() {
+        return mStateMap;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull SparseArray<DomainVerificationUserState> getUserSelectionStates() {
+        return mUserSelectionStates;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "DomainVerificationPkgState { " +
+                "packageName = " + mPackageName + ", " +
+                "id = " + mId + ", " +
+                "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
+                "stateMap = " + mStateMap + ", " +
+                "userSelectionStates = " + mUserSelectionStates +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(DomainVerificationPkgState other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        DomainVerificationPkgState that = (DomainVerificationPkgState) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mPackageName, that.mPackageName)
+                && Objects.equals(mId, that.mId)
+                && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
+                && Objects.equals(mStateMap, that.mStateMap)
+                && userSelectionStatesEquals(that.mUserSelectionStates);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mPackageName);
+        _hash = 31 * _hash + Objects.hashCode(mId);
+        _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains);
+        _hash = 31 * _hash + Objects.hashCode(mStateMap);
+        _hash = 31 * _hash + userSelectionStatesHashCode();
+        return _hash;
+    }
+
+    @DataClass.Generated(
+            time = 1608234185474L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationPkgState.java",
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final  boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState> mUserSelectionStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getUserSelectionState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationUserState getOrCreateUserSelectionState(int)\npublic  void setId(java.util.UUID)\npublic  void removeUser(int)\npublic  void removeAllUsers()\nprivate  int userSelectionStatesHashCode()\nprivate  boolean userSelectionStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java
new file mode 100644
index 0000000..88ccd83
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationStateMap.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain.models;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * A feature specific implementation of a multi-key map, since lookups by both a {@link String}
+ * package name and {@link UUID} domain set ID should be supported.
+ *
+ * @param <ValueType> stored object type
+ */
+public class DomainVerificationStateMap<ValueType> {
+
+    private static final String TAG = "DomainVerificationStateMap";
+
+    @NonNull
+    private final ArrayMap<String, ValueType> mPackageNameMap = new ArrayMap<>();
+
+    @NonNull
+    private final ArrayMap<UUID, ValueType> mDomainSetIdMap = new ArrayMap<>();
+
+    public int size() {
+        return mPackageNameMap.size();
+    }
+
+    @NonNull
+    public ValueType valueAt(@IntRange(from = 0) int index) {
+        return mPackageNameMap.valueAt(index);
+    }
+
+    @Nullable
+    public ValueType get(@NonNull String packageName) {
+        return mPackageNameMap.get(packageName);
+    }
+
+    @Nullable
+    public ValueType get(@NonNull UUID domainSetId) {
+        return mDomainSetIdMap.get(domainSetId);
+    }
+
+    public void put(@NonNull String packageName, @NonNull UUID domainSetId,
+            @NonNull ValueType valueType) {
+        if (mPackageNameMap.containsKey(packageName)) {
+            remove(packageName);
+        }
+
+        mPackageNameMap.put(packageName, valueType);
+        mDomainSetIdMap.put(domainSetId, valueType);
+    }
+
+    @Nullable
+    public ValueType remove(@NonNull String packageName) {
+        ValueType valueRemoved = mPackageNameMap.remove(packageName);
+        if (valueRemoved != null) {
+            int index = mDomainSetIdMap.indexOfValue(valueRemoved);
+            if (index >= 0) {
+                mDomainSetIdMap.removeAt(index);
+            }
+        }
+        return valueRemoved;
+    }
+
+    @Nullable
+    public ValueType remove(@NonNull UUID domainSetId) {
+        ValueType valueRemoved = mDomainSetIdMap.remove(domainSetId);
+        if (valueRemoved != null) {
+            int index = mPackageNameMap.indexOfValue(valueRemoved);
+            if (index >= 0) {
+                mPackageNameMap.removeAt(index);
+            }
+        }
+        return valueRemoved;
+    }
+
+    @NonNull
+    public List<String> getPackageNames() {
+        return new ArrayList<>(mPackageNameMap.keySet());
+    }
+
+    /**
+     * Exposes the backing values collection of the one of the internal maps. Should only be used
+     * for test assertions.
+     */
+    @VisibleForTesting
+    public Collection<ValueType> values() {
+        return new ArrayList<>(mPackageNameMap.values());
+    }
+
+    @Override
+    public String toString() {
+        return "DomainVerificationStateMap{"
+                + "packageNameMap=" + mPackageNameMap
+                + ", domainSetIdMap=" + mDomainSetIdMap
+                + '}';
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
new file mode 100644
index 0000000..8e82608
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain.models;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.util.ArraySet;
+
+import com.android.internal.util.DataClass;
+
+import java.util.Set;
+
+/**
+ * Tracks which domains have been explicitly enabled by the user, allowing it to automatically open
+ * that domain when a web URL Intent is sent ft.
+ */
+@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true)
+public class DomainVerificationUserState {
+
+    @UserIdInt
+    private final int mUserId;
+
+    /** List of domains which have been enabled by the user. **/
+    @NonNull
+    private final ArraySet<String> mEnabledHosts;
+
+    /** Whether to disallow this package from automatically opening links by auto verification. */
+    private boolean mDisallowLinkHandling;
+
+    public DomainVerificationUserState(@UserIdInt int userId) {
+        mUserId = userId;
+        mEnabledHosts = new ArraySet<>();
+    }
+
+    public DomainVerificationUserState addHosts(@NonNull ArraySet<String> newHosts) {
+        mEnabledHosts.addAll(newHosts);
+        return this;
+    }
+
+    public DomainVerificationUserState addHosts(@NonNull Set<String> newHosts) {
+        mEnabledHosts.addAll(newHosts);
+        return this;
+    }
+
+    public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) {
+        mEnabledHosts.removeAll(newHosts);
+        return this;
+    }
+
+    public DomainVerificationUserState removeHosts(@NonNull Set<String> newHosts) {
+        mEnabledHosts.removeAll(newHosts);
+        return this;
+    }
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new DomainVerificationUserState.
+     *
+     * @param enabledHosts
+     *   List of domains which have been enabled by the user. *
+     */
+    @DataClass.Generated.Member
+    public DomainVerificationUserState(
+            @UserIdInt int userId,
+            @NonNull ArraySet<String> enabledHosts,
+            boolean disallowLinkHandling) {
+        this.mUserId = userId;
+        com.android.internal.util.AnnotationValidations.validate(
+                UserIdInt.class, null, mUserId);
+        this.mEnabledHosts = enabledHosts;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mEnabledHosts);
+        this.mDisallowLinkHandling = disallowLinkHandling;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @UserIdInt int getUserId() {
+        return mUserId;
+    }
+
+    /**
+     * List of domains which have been enabled by the user. *
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArraySet<String> getEnabledHosts() {
+        return mEnabledHosts;
+    }
+
+    @DataClass.Generated.Member
+    public boolean isDisallowLinkHandling() {
+        return mDisallowLinkHandling;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) {
+        mDisallowLinkHandling = value;
+        return this;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "DomainVerificationUserState { " +
+                "userId = " + mUserId + ", " +
+                "enabledHosts = " + mEnabledHosts + ", " +
+                "disallowLinkHandling = " + mDisallowLinkHandling +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(DomainVerificationUserState other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        DomainVerificationUserState that = (DomainVerificationUserState) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mUserId == that.mUserId
+                && java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts)
+                && mDisallowLinkHandling == that.mDisallowLinkHandling;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mUserId;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts);
+        _hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling);
+        return _hash;
+    }
+
+    @DataClass.Generated(
+            time = 1608234273324L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java",
+            inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate  boolean mDisallowLinkHandling\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
new file mode 100644
index 0000000..715d8fb
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxy.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 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.pm.verify.domain.proxy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.DeviceIdleInternal;
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+import com.android.server.pm.verify.domain.DomainVerificationCollector;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+
+import java.util.Objects;
+import java.util.Set;
+
+// TODO(b/170321181): Combine the proxy versions for supporting v1 and v2 at once
+public interface DomainVerificationProxy {
+
+    String TAG = "DomainVerificationProxy";
+
+    boolean DEBUG_PROXIES = false;
+
+    static <ConnectionType extends DomainVerificationProxyV1.Connection
+            & DomainVerificationProxyV2.Connection> DomainVerificationProxy makeProxy(
+            @Nullable ComponentName componentV1, @Nullable ComponentName componentV2,
+            @NonNull Context context, @NonNull DomainVerificationManagerInternal manager,
+            @NonNull DomainVerificationCollector collector, @NonNull ConnectionType connection) {
+        if (DEBUG_PROXIES) {
+            Slog.d(TAG, "Intent filter verification agent: " + componentV1);
+            Slog.d(TAG, "Domain verification agent: " + componentV2);
+        }
+
+        if (componentV2 != null && componentV1 != null
+                && !Objects.equals(componentV2.getPackageName(), componentV1.getPackageName())) {
+            // Only allow a legacy verifier if it's in the same package as the v2 verifier
+            componentV1 = null;
+        }
+
+        DomainVerificationProxy proxyV1 = null;
+        DomainVerificationProxy proxyV2 = null;
+
+        if (componentV1 != null) {
+            proxyV1 = new DomainVerificationProxyV1(context, manager, collector, connection,
+                    componentV1);
+        }
+
+        if (componentV2 != null) {
+            proxyV2 = new DomainVerificationProxyV2(context, connection, componentV2);
+        }
+
+        if (proxyV1 != null && proxyV2 != null) {
+            return new DomainVerificationProxyCombined(proxyV1, proxyV2);
+        }
+
+        if (proxyV1 != null) {
+            return proxyV1;
+        }
+
+        if (proxyV2 != null) {
+            return proxyV2;
+        }
+
+        return new DomainVerificationProxyUnavailable();
+    }
+
+    default void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+    }
+
+    /**
+     * Runs a message on the caller's Handler as a result of {@link BaseConnection#schedule(int,
+     * Object)}. Abstracts the actual scheduling/running from the manager class. This is also
+     * necessary so that different what codes can be used depending on the verifier proxy on device,
+     * to allow backporting v1. The backport proxy may schedule more or less messages than the v2
+     * proxy.
+     *
+     * @param messageCode One of the values in {@link DomainVerificationMessageCodes}.
+     * @param object      Arbitrary object that was originally included.
+     */
+    default boolean runMessage(int messageCode, Object object) {
+        return false;
+    }
+
+    default boolean isCallerVerifier(int callingUid) {
+        return false;
+    }
+
+    @Nullable
+    default ComponentName getComponentName() {
+        return null;
+    }
+
+    interface BaseConnection {
+
+        /**
+         * Schedule something to be run later. The implementation is left up to the caller.
+         *
+         * @param code   One of the values in {@link DomainVerificationMessageCodes}.
+         * @param object Arbitrary object to include with the message.
+         */
+        void schedule(int code, @Nullable Object object);
+
+        long getPowerSaveTempWhitelistAppDuration();
+
+        DeviceIdleInternal getDeviceIdleInternal();
+
+        boolean isCallerPackage(int callingUid, @NonNull String packageName);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
new file mode 100644
index 0000000..8571c08
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyCombined.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 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.pm.verify.domain.proxy;
+
+import android.annotation.NonNull;
+
+import java.util.Set;
+
+class DomainVerificationProxyCombined implements DomainVerificationProxy {
+
+    @NonNull
+    private final DomainVerificationProxy mProxyV1;
+    @NonNull
+    private final DomainVerificationProxy mProxyV2;
+
+    DomainVerificationProxyCombined(@NonNull DomainVerificationProxy proxyV1,
+            @NonNull DomainVerificationProxy proxyV2) {
+        mProxyV1 = proxyV1;
+        mProxyV2 = proxyV2;
+    }
+
+    @Override
+    public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+        mProxyV2.sendBroadcastForPackages(packageNames);
+        mProxyV1.sendBroadcastForPackages(packageNames);
+    }
+
+    @Override
+    public boolean runMessage(int messageCode, Object object) {
+        // Both proxies must run, so cannot use a direct ||, which may skip the right hand side
+        boolean resultV2 = mProxyV2.runMessage(messageCode, object);
+        boolean resultV1 = mProxyV1.runMessage(messageCode, object);
+        return resultV2 || resultV1;
+    }
+
+    @Override
+    public boolean isCallerVerifier(int callingUid) {
+        return mProxyV2.isCallerVerifier(callingUid) || mProxyV1.isCallerVerifier(callingUid);
+    }
+}
diff --git a/apex/permission/service/java/com/android/role/package-info.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
similarity index 73%
copy from apex/permission/service/java/com/android/role/package-info.java
copy to services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
index 8b5b251..bd77983 100644
--- a/apex/permission/service/java/com/android/role/package-info.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyUnavailable.java
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-/**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
- */
-@android.annotation.Hide
-package com.android.role;
+package com.android.server.pm.verify.domain.proxy;
+
+/** Stub implementation for when the verification agent is unavailable */
+public class DomainVerificationProxyUnavailable implements DomainVerificationProxy {
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
new file mode 100644
index 0000000..eab89e98
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2021 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.pm.verify.domain.proxy;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.BroadcastOptions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainVerificationInfo;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationState;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.verify.domain.DomainVerificationCollector;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+public class DomainVerificationProxyV1 implements DomainVerificationProxy {
+
+    private static final String TAG = "DomainVerificationProxyV1";
+
+    private static final boolean DEBUG_BROADCASTS = false;
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final Connection mConnection;
+
+    @NonNull
+    private final ComponentName mVerifierComponent;
+
+    @NonNull
+    private final DomainVerificationManagerInternal mManager;
+
+    @NonNull
+    private final DomainVerificationCollector mCollector;
+
+    @NonNull
+    private final Object mLock = new Object();
+
+    @NonNull
+    @GuardedBy("mLock")
+    private final ArrayMap<Integer, Pair<UUID, String>> mRequests = new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    private int mVerificationToken = 0;
+
+    public DomainVerificationProxyV1(@NonNull Context context,
+            @NonNull DomainVerificationManagerInternal manager,
+            @NonNull DomainVerificationCollector collector, @NonNull Connection connection,
+            @NonNull ComponentName verifierComponent) {
+        mContext = context;
+        mConnection = connection;
+        mVerifierComponent = verifierComponent;
+        mManager = manager;
+        mCollector = collector;
+    }
+
+    public static void queueLegacyVerifyResult(@NonNull Context context,
+            @NonNull DomainVerificationProxyV1.Connection connection, int verificationId,
+            int verificationCode, @Nullable List<String> failedDomains, int callingUid) {
+        context.enforceCallingOrSelfPermission(
+                Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
+                "Only the intent filter verification agent can verify applications");
+
+        connection.schedule(DomainVerificationMessageCodes.LEGACY_ON_INTENT_FILTER_VERIFIED,
+                new Response(callingUid, verificationId, verificationCode, failedDomains));
+    }
+
+    @Override
+    public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+        synchronized (mLock) {
+            int size = mRequests.size();
+            for (int index = size - 1; index >= 0; index--) {
+                Pair<UUID, String> pair = mRequests.valueAt(index);
+                if (packageNames.contains(pair.second)) {
+                    mRequests.removeAt(index);
+                }
+            }
+        }
+        mConnection.schedule(DomainVerificationMessageCodes.LEGACY_SEND_REQUEST, packageNames);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public boolean runMessage(int messageCode, Object object) {
+        switch (messageCode) {
+            case DomainVerificationMessageCodes.LEGACY_SEND_REQUEST:
+                @SuppressWarnings("unchecked") Set<String> packageNames = (Set<String>) object;
+                if (DEBUG_BROADCASTS) {
+                    Slog.d(TAG, "Requesting domain verification for " + packageNames);
+                }
+
+                ArrayMap<Integer, Pair<UUID, String>> newRequests = new ArrayMap<>(
+                        packageNames.size());
+                synchronized (mLock) {
+                    for (String packageName : packageNames) {
+                        UUID domainSetId = mManager.getDomainVerificationInfoId(packageName);
+                        if (domainSetId == null) {
+                            continue;
+                        }
+
+                        newRequests.put(mVerificationToken++,
+                                Pair.create(domainSetId, packageName));
+                    }
+                    mRequests.putAll(newRequests);
+                }
+
+                sendBroadcasts(newRequests);
+                return true;
+            case DomainVerificationMessageCodes.LEGACY_ON_INTENT_FILTER_VERIFIED:
+                Response response = (Response) object;
+
+                Pair<UUID, String> pair = mRequests.get(response.verificationId);
+                if (pair == null) {
+                    return true;
+                }
+
+                UUID domainSetId = pair.first;
+                String packageName = pair.second;
+                DomainVerificationInfo set;
+                try {
+                    set = mManager.getDomainVerificationInfo(packageName);
+                } catch (PackageManager.NameNotFoundException ignored) {
+                    return true;
+                }
+
+                if (!Objects.equals(domainSetId, set.getIdentifier())) {
+                    return true;
+                }
+
+                Set<String> successfulDomains = new ArraySet<>(set.getHostToStateMap().keySet());
+                successfulDomains.removeAll(response.failedDomains);
+
+                int callingUid = response.callingUid;
+                try {
+                    mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+                            successfulDomains, DomainVerificationState.STATE_SUCCESS);
+                } catch (DomainVerificationManager.InvalidDomainSetException
+                        | PackageManager.NameNotFoundException ignored) {
+                }
+                try {
+                    mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
+                            new ArraySet<>(response.failedDomains),
+                            DomainVerificationState.STATE_LEGACY_FAILURE);
+                } catch (DomainVerificationManager.InvalidDomainSetException
+                        | PackageManager.NameNotFoundException ignored) {
+                }
+
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean isCallerVerifier(int callingUid) {
+        return mConnection.isCallerPackage(callingUid, mVerifierComponent.getPackageName());
+    }
+
+    @SuppressWarnings("deprecation")
+    private void sendBroadcasts(@NonNull ArrayMap<Integer, Pair<UUID, String>> verifications) {
+        final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration();
+        mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(),
+                mVerifierComponent.getPackageName(), allowListTimeout,
+                UserHandle.USER_SYSTEM, true, "domain verification agent");
+
+        int size = verifications.size();
+        for (int index = 0; index < size; index++) {
+            int verificationId = verifications.keyAt(index);
+            String packageName = verifications.valueAt(index).second;
+            AndroidPackage pkg = mConnection.getPackage(packageName);
+
+            String hostsString = buildHostsString(pkg);
+
+            Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
+                    .setComponent(mVerifierComponent)
+                    .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID,
+                            verificationId)
+                    .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME,
+                            IntentFilter.SCHEME_HTTPS)
+                    .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS,
+                            hostsString)
+                    .putExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME,
+                            packageName)
+                    .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setTemporaryAppWhitelistDuration(allowListTimeout);
+            mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle());
+        }
+    }
+
+    @NonNull
+    private String buildHostsString(@NonNull AndroidPackage pkg) {
+        // The collector itself handles the v1 vs v2 behavior, which is based on targetSdkVersion,
+        // not the version of the verification agent on device.
+        ArraySet<String> domains = mCollector.collectAutoVerifyDomains(pkg);
+        return TextUtils.join(" ", domains);
+    }
+
+    private static class Response {
+        public final int callingUid;
+        public final int verificationId;
+        public final int verificationCode;
+        @NonNull
+        public final List<String> failedDomains;
+
+        private Response(int callingUid, int verificationId, int verificationCode,
+                @Nullable List<String> failedDomains) {
+            this.callingUid = callingUid;
+            this.verificationId = verificationId;
+            this.verificationCode = verificationCode;
+            this.failedDomains = failedDomains == null ? Collections.emptyList() : failedDomains;
+        }
+    }
+
+    public interface Connection extends BaseConnection {
+
+        @Nullable
+        AndroidPackage getPackage(@NonNull String packageName);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java
new file mode 100644
index 0000000..9fcbce2
--- /dev/null
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021 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.pm.verify.domain.proxy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.BroadcastOptions;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.verify.domain.DomainVerificationManager;
+import android.content.pm.verify.domain.DomainVerificationRequest;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import com.android.server.pm.verify.domain.DomainVerificationMessageCodes;
+
+import java.util.Set;
+
+public class DomainVerificationProxyV2 implements DomainVerificationProxy {
+
+    private static final String TAG = "DomainVerificationProxyV2";
+
+    private static final boolean DEBUG_BROADCASTS = true;
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final Connection mConnection;
+
+    @NonNull
+    private final ComponentName mVerifierComponent;
+
+    public DomainVerificationProxyV2(@NonNull Context context, @NonNull Connection connection,
+            @NonNull ComponentName verifierComponent) {
+        mContext = context;
+        mConnection = connection;
+        mVerifierComponent = verifierComponent;
+    }
+
+    @Override
+    public void sendBroadcastForPackages(@NonNull Set<String> packageNames) {
+        mConnection.schedule(com.android.server.pm.verify.domain.DomainVerificationMessageCodes.SEND_REQUEST, packageNames);
+    }
+
+    @Override
+    public boolean runMessage(int messageCode, Object object) {
+        switch (messageCode) {
+            case DomainVerificationMessageCodes.SEND_REQUEST:
+                @SuppressWarnings("unchecked") Set<String> packageNames = (Set<String>) object;
+                DomainVerificationRequest request = new DomainVerificationRequest(packageNames);
+
+                final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration();
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setTemporaryAppWhitelistDuration(allowListTimeout);
+
+                mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(),
+                        mVerifierComponent.getPackageName(), allowListTimeout,
+                        UserHandle.USER_SYSTEM, true, "domain verification agent");
+
+                Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION)
+                        .setComponent(mVerifierComponent)
+                        .putExtra(DomainVerificationManager.EXTRA_VERIFICATION_REQUEST, request)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+                if (DEBUG_BROADCASTS) {
+                    Slog.d(TAG, "Requesting domain verification for " + packageNames);
+                }
+
+                mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle());
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean isCallerVerifier(int callingUid) {
+        return mConnection.isCallerPackage(callingUid, mVerifierComponent.getPackageName());
+    }
+
+    @Nullable
+    @Override
+    public ComponentName getComponentName() {
+        return mVerifierComponent;
+    }
+
+    public interface Connection extends BaseConnection {
+    }
+}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index e901f66f..ac358db 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -34,10 +34,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
+import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.policy.devicestate.config.Conditions;
-import com.android.server.policy.devicestate.config.DeviceState;
 import com.android.server.policy.devicestate.config.DeviceStateConfig;
 import com.android.server.policy.devicestate.config.LidSwitchCondition;
 import com.android.server.policy.devicestate.config.NumericRange;
@@ -54,6 +55,7 @@
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BooleanSupplier;
@@ -82,7 +84,7 @@
     private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
 
     @VisibleForTesting
-    static final int DEFAULT_DEVICE_STATE = 0;
+    static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
 
     private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
     private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
@@ -116,31 +118,38 @@
     @VisibleForTesting
     static DeviceStateProviderImpl createFromConfig(@NonNull Context context,
             @Nullable ReadableConfig readableConfig) {
-        SparseArray<Conditions> conditionsForState = new SparseArray<>();
+        List<DeviceState> deviceStateList = new ArrayList<>();
+        List<Conditions> conditionsList = new ArrayList<>();
+
         if (readableConfig != null) {
             DeviceStateConfig config = parseConfig(readableConfig);
             if (config != null) {
-                for (DeviceState stateConfig : config.getDeviceState()) {
-                    int state = stateConfig.getIdentifier().intValue();
-                    Conditions conditions = stateConfig.getConditions();
-                    conditionsForState.put(state, conditions);
+                for (com.android.server.policy.devicestate.config.DeviceState stateConfig :
+                        config.getDeviceState()) {
+                    final int state = stateConfig.getIdentifier().intValue();
+                    final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
+                    deviceStateList.add(new DeviceState(state, name));
+
+                    final Conditions condition = stateConfig.getConditions();
+                    conditionsList.add(condition);
                 }
             }
         }
 
-        if (conditionsForState.size() == 0) {
-            conditionsForState.put(DEFAULT_DEVICE_STATE, null);
+        if (deviceStateList.size() == 0) {
+            deviceStateList.add(DEFAULT_DEVICE_STATE);
+            conditionsList.add(null);
         }
-        return new DeviceStateProviderImpl(context, conditionsForState);
+        return new DeviceStateProviderImpl(context, deviceStateList, conditionsList);
     }
 
     // Lock for internal state.
     private final Object mLock = new Object();
     private final Context mContext;
-    // List of supported states in ascending order.
-    private final int[] mOrderedStates;
-    // Map of state to a boolean supplier that returns true when all required conditions are met for
-    // the device to be in the state.
+    // List of supported states in ascending order based on their identifier.
+    private final DeviceState[] mOrderedStates;
+    // Map of state identifier to a boolean supplier that returns true when all required conditions
+    // are met for the device to be in the state.
     private final SparseArray<BooleanSupplier> mStateConditions;
 
     @Nullable
@@ -155,12 +164,16 @@
     private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
 
     private DeviceStateProviderImpl(@NonNull Context context,
-            @NonNull SparseArray<Conditions> conditionsForState) {
+            @NonNull List<DeviceState> deviceStates,
+            @NonNull List<Conditions> stateConditions) {
+        Preconditions.checkArgument(deviceStates.size() == stateConditions.size(),
+                "Number of device states must be equal to the number of device state conditions.");
+
         mContext = context;
-        mOrderedStates = new int[conditionsForState.size()];
-        for (int i = 0; i < conditionsForState.size(); i++) {
-            mOrderedStates[i] = conditionsForState.keyAt(i);
-        }
+
+        DeviceState[] orderedStates = deviceStates.toArray(new DeviceState[deviceStates.size()]);
+        Arrays.sort(orderedStates, Comparator.comparingInt(DeviceState::getIdentifier));
+        mOrderedStates = orderedStates;
 
         // Whether or not this instance should register to receive lid switch notifications from
         // InputManagerInternal. If there are no device state conditions that are based on the lid
@@ -171,9 +184,9 @@
         final ArraySet<Sensor> sensorsToListenTo = new ArraySet<>();
 
         mStateConditions = new SparseArray<>();
-        for (int i = 0; i < mOrderedStates.length; i++) {
-            int state = mOrderedStates[i];
-            Conditions conditions = conditionsForState.get(state);
+        for (int i = 0; i < stateConditions.size(); i++) {
+            final int state = deviceStates.get(i).getIdentifier();
+            final Conditions conditions = stateConditions.get(i);
             if (conditions == null) {
                 mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
                 continue;
@@ -261,7 +274,7 @@
 
     /** Notifies the listener that the set of supported device states has changed. */
     private void notifySupportedStatesChanged() {
-        int[] supportedStates;
+        DeviceState[] supportedStates;
         synchronized (mLock) {
             if (mListener == null) {
                 return;
@@ -281,9 +294,9 @@
                 return;
             }
 
-            int newState = mOrderedStates[0];
+            int newState = mOrderedStates[0].getIdentifier();
             for (int i = 0; i < mOrderedStates.length; i++) {
-                int state = mOrderedStates[i];
+                int state = mOrderedStates[i].getIdentifier();
                 if (mStateConditions.get(state).getAsBoolean()) {
                     newState = state;
                     break;
diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
index ff51237..82fc22c 100644
--- a/services/core/java/com/android/server/policy/DisplayFoldController.java
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -74,8 +74,8 @@
         mHandler = handler;
 
         DeviceStateManager deviceStateManager = context.getSystemService(DeviceStateManager.class);
-        deviceStateManager.registerDeviceStateListener(new DeviceStateListener(context),
-                new HandlerExecutor(handler));
+        deviceStateManager.addDeviceStateListener(new HandlerExecutor(handler),
+                new DeviceStateListener(context));
     }
 
     void finishedGoingToSleep() {
@@ -209,16 +209,23 @@
      * resource.
      */
     private class DeviceStateListener implements DeviceStateManager.DeviceStateListener {
-        private final int mFoldedDeviceState;
+        private final int[] mFoldedDeviceStates;
 
         DeviceStateListener(Context context) {
-            mFoldedDeviceState = context.getResources().getInteger(
-                    com.android.internal.R.integer.config_foldedDeviceState);
+            mFoldedDeviceStates = context.getResources().getIntArray(
+                    com.android.internal.R.array.config_foldedDeviceStates);
         }
 
         @Override
         public void onDeviceStateChanged(int deviceState) {
-            setDeviceFolded(deviceState == mFoldedDeviceState);
+            boolean folded = false;
+            for (int i = 0; i < mFoldedDeviceStates.length; i++) {
+                if (deviceState == mFoldedDeviceStates[i]) {
+                    folded = true;
+                    break;
+                }
+            }
+            setDeviceFolded(folded);
         }
     }
 }
diff --git a/services/core/java/com/android/server/policy/IconUtilities.java b/services/core/java/com/android/server/policy/IconUtilities.java
deleted file mode 100644
index 884d7d4..0000000
--- a/services/core/java/com/android/server/policy/IconUtilities.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2008 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.policy;
-
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.PaintDrawable;
-import android.graphics.drawable.StateListDrawable;
-import android.graphics.Bitmap;
-import android.graphics.BlurMaskFilter;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrix;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.TableMaskFilter;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.content.res.Resources;
-import android.content.Context;
-
-/**
- * Various utilities shared amongst the Launcher's classes.
- */
-public final class IconUtilities {
-
-    private int mIconWidth = -1;
-    private int mIconHeight = -1;
-    private int mIconTextureWidth = -1;
-    private int mIconTextureHeight = -1;
-
-    private final Rect mOldBounds = new Rect();
-    private final Canvas mCanvas = new Canvas();
-    private final DisplayMetrics mDisplayMetrics;
-
-    private ColorFilter mDisabledColorFilter;
-
-    public IconUtilities(Context context) {
-        final Resources resources = context.getResources();
-        DisplayMetrics metrics = mDisplayMetrics = resources.getDisplayMetrics();
-        final float density = metrics.density;
-        final float blurPx = 5 * density;
-
-        mIconWidth = mIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size);
-        mIconTextureWidth = mIconTextureHeight = mIconWidth + (int)(blurPx*2);
-        mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
-                Paint.FILTER_BITMAP_FLAG));
-    }
-
-    /**
-     * Returns a bitmap suitable for the all apps view.  The bitmap will be a power
-     * of two sized ARGB_8888 bitmap that can be used as a gl texture.
-     */
-    public Bitmap createIconBitmap(Drawable icon) {
-        int width = mIconWidth;
-        int height = mIconHeight;
-
-        if (icon instanceof PaintDrawable) {
-            PaintDrawable painter = (PaintDrawable) icon;
-            painter.setIntrinsicWidth(width);
-            painter.setIntrinsicHeight(height);
-        } else if (icon instanceof BitmapDrawable) {
-            // Ensure the bitmap has a density.
-            BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
-            Bitmap bitmap = bitmapDrawable.getBitmap();
-            if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
-                bitmapDrawable.setTargetDensity(mDisplayMetrics);
-            }
-        }
-        int sourceWidth = icon.getIntrinsicWidth();
-        int sourceHeight = icon.getIntrinsicHeight();
-
-        if (sourceWidth > 0 && sourceHeight > 0) {
-            // There are intrinsic sizes.
-            if (width < sourceWidth || height < sourceHeight) {
-                // It's too big, scale it down.
-                final float ratio = (float) sourceWidth / sourceHeight;
-                if (sourceWidth > sourceHeight) {
-                    height = (int) (width / ratio);
-                } else if (sourceHeight > sourceWidth) {
-                    width = (int) (height * ratio);
-                }
-            } else if (sourceWidth < width && sourceHeight < height) {
-                // It's small, use the size they gave us.
-                width = sourceWidth;
-                height = sourceHeight;
-            }
-        }
-
-        // no intrinsic size --> use default size
-        int textureWidth = mIconTextureWidth;
-        int textureHeight = mIconTextureHeight;
-
-        final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
-                Bitmap.Config.ARGB_8888);
-        final Canvas canvas = mCanvas;
-        canvas.setBitmap(bitmap);
-
-        final int left = (textureWidth-width) / 2;
-        final int top = (textureHeight-height) / 2;
-
-        mOldBounds.set(icon.getBounds());
-        icon.setBounds(left, top, left+width, top+height);
-        icon.draw(canvas);
-        icon.setBounds(mOldBounds);
-
-        return bitmap;
-    }
-
-    public ColorFilter getDisabledColorFilter() {
-        if (mDisabledColorFilter != null) {
-            return mDisabledColorFilter;
-        }
-        ColorMatrix brightnessMatrix = new ColorMatrix();
-        float brightnessF = 0.5f;
-        int brightnessI = (int) (255 * brightnessF);
-        // Brightness: C-new = C-old*(1-amount) + amount
-        float scale = 1f - brightnessF;
-        float[] mat = brightnessMatrix.getArray();
-        mat[0] = scale;
-        mat[6] = scale;
-        mat[12] = scale;
-        mat[4] = brightnessI;
-        mat[9] = brightnessI;
-        mat[14] = brightnessI;
-
-        ColorMatrix filterMatrix = new ColorMatrix();
-        filterMatrix.setSaturation(0);
-        filterMatrix.preConcat(brightnessMatrix);
-
-        mDisabledColorFilter = new ColorMatrixColorFilter(filterMatrix);
-        return mDisabledColorFilter;
-    }
-}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c073b43..bc81961 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -188,7 +188,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
 import android.view.autofill.AutofillManagerInternal;
 
@@ -201,7 +200,9 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
 import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.policy.LogDecelerateInterpolator;
 import com.android.internal.policy.PhoneWindow;
+import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.ExtconStateObserver;
@@ -221,6 +222,7 @@
 import com.android.server.wm.DisplayRotation;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+import com.android.server.wm.WindowManagerService;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -228,7 +230,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.HashSet;
-import java.util.List;
 
 /**
  * WindowManagerPolicy implementation for the Android phone UI.  This
@@ -582,7 +583,7 @@
     private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions =
             new SparseArray<KeyCharacterMap.FallbackAction>();
 
-    private final LogDecelerateInterpolator mLogDecelerateInterpolator
+    private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator
             = new LogDecelerateInterpolator(100, 0);
 
     private final MutableBoolean mTmpBoolean = new MutableBoolean(false);
@@ -1913,6 +1914,7 @@
                 handleStartTransitionForKeyguardLw(keyguardGoingAway, 0 /* duration */);
             }
         });
+
         mKeyguardDelegate = new KeyguardServiceDelegate(mContext,
                 new StateCallback() {
                     @Override
@@ -2484,28 +2486,8 @@
     @Override
     public Animation createHiddenByKeyguardExit(boolean onWallpaper,
             boolean goingToNotificationShade, boolean subtleAnimation) {
-        if (goingToNotificationShade) {
-            return AnimationUtils.loadAnimation(mContext, R.anim.lock_screen_behind_enter_fade_in);
-        }
-
-        final int resource;
-        if (subtleAnimation) {
-            resource = R.anim.lock_screen_behind_enter_subtle;
-        } else if (onWallpaper) {
-            resource = R.anim.lock_screen_behind_enter_wallpaper;
-        } else  {
-            resource = R.anim.lock_screen_behind_enter;
-        }
-
-        AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(mContext, resource);
-
-        // TODO: Use XML interpolators when we have log interpolators available in XML.
-        final List<Animation> animations = set.getAnimations();
-        for (int i = animations.size() - 1; i >= 0; --i) {
-            animations.get(i).setInterpolator(mLogDecelerateInterpolator);
-        }
-
-        return set;
+        return TransitionAnimation.createHiddenByKeyguardExit(mContext,
+                mLogDecelerateInterpolator, onWallpaper, goingToNotificationShade, subtleAnimation);
     }
 
 
@@ -3156,7 +3138,7 @@
     private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) {
         final int res = applyKeyguardOcclusionChange();
         if (res != 0) return res;
-        if (keyguardGoingAway) {
+        if (!WindowManagerService.sEnableRemoteKeyguardAnimation && keyguardGoingAway) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
             startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
         }
@@ -3491,15 +3473,27 @@
     /** {@inheritDoc} */
     @Override
     public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+        final int keyCode = event.getKeyCode();
+        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+        boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
+                || event.isWakeKey();
+
         if (!mSystemBooted) {
             // If we have not yet booted, don't let key events do anything.
+            // Exception: Wake and power key events are forwarded to PowerManager to allow it to
+            // wake from quiescent mode during boot.
+            if (down && (keyCode == KeyEvent.KEYCODE_POWER
+                    || keyCode == KeyEvent.KEYCODE_TV_POWER)) {
+                wakeUpFromPowerKey(event.getDownTime());
+            } else if (down && (isWakeKey || keyCode == KeyEvent.KEYCODE_WAKEUP)
+                    && isWakeKeyWhenScreenOff(keyCode)) {
+                wakeUpFromWakeKey(event);
+            }
             return 0;
         }
 
         final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
-        final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
         final boolean canceled = event.isCanceled();
-        final int keyCode = event.getKeyCode();
         final int displayId = event.getDisplayId();
         final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;
 
@@ -3518,8 +3512,6 @@
 
         // Basic policy based on interactive state.
         int result;
-        boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
-                || event.isWakeKey();
         if (interactive || (isInjected && !isWakeKey)) {
             // When the device is interactive or the key is injected pass the
             // key to the application.
@@ -4740,7 +4732,7 @@
         }
         startedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
         finishedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
-        screenTurningOn(DEFAULT_DISPLAY, null);
+        screenTurningOn(DEFAULT_DISPLAY, mDefaultDisplayPolicy.getScreenOnListener());
         screenTurnedOn(DEFAULT_DISPLAY);
     }
 
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index e9d6440..db33e75 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -417,8 +417,7 @@
             WindowManagerFuncs windowManagerFuncs);
 
     /**
-     * Check permissions when adding a window or a window token from
-     * {@link android.app.WindowContext}.
+     * Check permissions when adding a window.
      *
      * @param type The window type
      * @param isRoundedCornerOverlay {@code true} to indicate the adding window is
@@ -431,7 +430,6 @@
      *      else an error code, usually
      *      {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
      *
-     * @see IWindowManager#addWindowTokenWithOptions(IBinder, int, int, Bundle, String)
      * @see WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY
      */
     int checkAddPermission(int type, boolean isRoundedCornerOverlay, String packageName,
@@ -1158,7 +1156,7 @@
      * @param startTime the start time of the animation in uptime milliseconds
      * @param fadeoutDuration the duration of the exit animation, in milliseconds
      */
-    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
+    void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
 
     /**
      * Called when System UI has been started.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index c2a1c79..a95628f 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -29,6 +29,7 @@
 import com.android.internal.policy.IKeyguardService;
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult;
+import com.android.server.wm.WindowManagerService;
 
 import java.io.PrintWriter;
 
@@ -398,7 +399,7 @@
     }
 
     public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
-        if (mKeyguardService != null) {
+        if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) {
             mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
         }
     }
diff --git a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
index 6477552..2fc3e40 100644
--- a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
+++ b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
@@ -25,7 +25,6 @@
 import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.display.DisplayGroup;
 
 /**
  * Responsible for creating {@link DisplayPowerRequest}s and associating them with
@@ -110,8 +109,8 @@
             DisplayManagerInternal displayManagerInternal, Handler handler) {
         mDisplayManagerInternal = displayManagerInternal;
         displayManager.registerDisplayListener(mDisplayListener, handler);
-        mDisplayPowerRequests.append(DisplayGroup.DEFAULT, new DisplayPowerRequest());
-        mDisplayGroupIds.append(Display.DEFAULT_DISPLAY, DisplayGroup.DEFAULT);
+        mDisplayPowerRequests.append(Display.DEFAULT_DISPLAY_GROUP, new DisplayPowerRequest());
+        mDisplayGroupIds.append(Display.DEFAULT_DISPLAY, Display.DEFAULT_DISPLAY_GROUP);
     }
 
     DisplayPowerRequest get(int displayId) {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 54d0512..8e0d632 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1036,12 +1036,12 @@
                 userActivityNoUpdateLocked(
                         now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
 
+                updatePowerStateLocked();
                 if (sQuiescent) {
                     goToSleepNoUpdateLocked(mClock.uptimeMillis(),
                             PowerManager.GO_TO_SLEEP_REASON_QUIESCENT,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
                 }
-                updatePowerStateLocked();
             }
         }
     }
@@ -1679,8 +1679,15 @@
             Slog.d(TAG, "wakeUpNoUpdateLocked: eventTime=" + eventTime + ", uid=" + reasonUid);
         }
 
-        if (eventTime < mLastSleepTime || getWakefulnessLocked() == WAKEFULNESS_AWAKE
-                || mForceSuspendActive || !mSystemReady) {
+        if (eventTime < mLastSleepTime || mForceSuspendActive || !mSystemReady) {
+            return false;
+        }
+
+        if (getWakefulnessLocked() == WAKEFULNESS_AWAKE) {
+            if (!mBootCompleted && sQuiescent) {
+                mDirty |= DIRTY_QUIESCENT;
+                return true;
+            }
             return false;
         }
 
@@ -2821,7 +2828,7 @@
      *
      * This function recalculates the display power state each time.
      *
-     * @return True if the display became ready.
+     * @return true if the display became ready.
      */
     private boolean updateDisplayPowerStateLocked(int dirty) {
         final boolean oldDisplayReady = mDisplayReady;
@@ -2830,7 +2837,11 @@
                 | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED |
                 DIRTY_QUIESCENT)) != 0) {
             if ((dirty & DIRTY_QUIESCENT) != 0) {
-                sQuiescent = false;
+                if (mDisplayReady) {
+                    sQuiescent = false;
+                } else {
+                    mDirty |= DIRTY_QUIESCENT;
+                }
             }
 
             final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
@@ -2881,8 +2892,8 @@
                         PowerManager.BRIGHTNESS_INVALID_FLOAT;
             }
 
-            mDisplayReady = mDisplayManagerInternal.requestPowerState(displayPowerRequest,
-                    mRequestWaitForNegativeProximity);
+            mDisplayReady = mDisplayManagerInternal.requestPowerState(Display.DEFAULT_DISPLAY_GROUP,
+                    displayPowerRequest, mRequestWaitForNegativeProximity);
             mRequestWaitForNegativeProximity = false;
 
             if (DEBUG_SPEW) {
@@ -5605,7 +5616,7 @@
      * ignore the proximity sensor.  We don't turn off the proximity sensor because
      * we still want it to be reenabled if it's state changes.
      *
-     * @return True if the proximity sensor was successfully ignored and we should
+     * @return true if the proximity sensor was successfully ignored and we should
      * consume the key event.
      */
     private boolean interceptPowerKeyDownInternal(KeyEvent event) {
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java b/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
index 3e39b4b..9a91848 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsHALWrapper.java
@@ -120,7 +120,7 @@
          * @return List of EnergyMeasurement objects containing energy measurements for all
          *         available energy meters.
          */
-        android.hardware.power.stats.EnergyMeasurement[] readEnergyMeters(int[] channelIds);
+        android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(int[] channelIds);
 
         /**
          * Returns boolean indicating if connection to power stats HAL was established.
@@ -235,13 +235,13 @@
         }
 
         @Override
-        public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeters(int[] channelIds) {
+        public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
             android.hardware.power.stats.EnergyMeasurement[] energyMeasurementHAL = null;
 
             if (sVintfPowerStats != null) {
                 try {
                     energyMeasurementHAL =
-                        sVintfPowerStats.get().readEnergyMeters(channelIds);
+                        sVintfPowerStats.get().readEnergyMeter(channelIds);
                 } catch (RemoteException e) {
                     if (DEBUG) Slog.d(TAG, "Failed to get energy measurements from PowerStats HAL");
                 }
@@ -295,14 +295,14 @@
         @Override
         public android.hardware.power.stats.EnergyConsumer[] getEnergyConsumerInfo() {
             if (DEBUG) Slog.d(TAG, "Energy consumer info is not supported");
-            return null;
+            return new android.hardware.power.stats.EnergyConsumer[0];
         }
 
         @Override
         public android.hardware.power.stats.EnergyConsumerResult[] getEnergyConsumed(
                 int[] energyConsumerIds) {
             if (DEBUG) Slog.d(TAG, "Energy consumer results are not supported");
-            return null;
+            return new android.hardware.power.stats.EnergyConsumerResult[0];
         }
 
         @Override
@@ -311,7 +311,7 @@
         }
 
         @Override
-        public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeters(int[] channelIds) {
+        public android.hardware.power.stats.EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
             return nativeReadEnergyMeters(channelIds);
         }
 
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 78a227e..37fc5a0 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -53,8 +53,9 @@
 public final class PowerStatsLogger extends Handler {
     private static final String TAG = PowerStatsLogger.class.getSimpleName();
     private static final boolean DEBUG = false;
-    protected static final int MSG_LOG_TO_DATA_STORAGE_TIMER = 0;
-    protected static final int MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP = 1;
+    protected static final int MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP = 0;
+    protected static final int MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY = 1;
+    protected static final int MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY = 2;
 
     private final PowerStatsDataStorage mPowerStatsMeterStorage;
     private final PowerStatsDataStorage mPowerStatsModelStorage;
@@ -64,22 +65,33 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-            case MSG_LOG_TO_DATA_STORAGE_TIMER:
-                if (DEBUG) Slog.d(TAG, "Logging to data storage on timer");
+            case MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY:
+                if (DEBUG) Slog.d(TAG, "Logging to data storage on high frequency timer");
 
                 // Log power meter data.
                 EnergyMeasurement[] energyMeasurements =
-                    mPowerStatsHALWrapper.readEnergyMeters(new int[0]);
+                    mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
                 mPowerStatsMeterStorage.write(
                         EnergyMeasurementUtils.getProtoBytes(energyMeasurements));
                 if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements);
 
-                // Log power model data.
-                EnergyConsumerResult[] energyConsumerResults =
+                // Log power model data without attribution data.
+                EnergyConsumerResult[] ecrNoAttribution =
                     mPowerStatsHALWrapper.getEnergyConsumed(new int[0]);
                 mPowerStatsModelStorage.write(
-                        EnergyConsumerResultUtils.getProtoBytes(energyConsumerResults));
-                if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResults);
+                        EnergyConsumerResultUtils.getProtoBytes(ecrNoAttribution, false));
+                if (DEBUG) EnergyConsumerResultUtils.print(ecrNoAttribution);
+                break;
+
+            case MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY:
+                if (DEBUG) Slog.d(TAG, "Logging to data storage on low frequency timer");
+
+                // Log power model data with attribution data.
+                EnergyConsumerResult[] ecrAttribution =
+                    mPowerStatsHALWrapper.getEnergyConsumed(new int[0]);
+                mPowerStatsModelStorage.write(
+                        EnergyConsumerResultUtils.getProtoBytes(ecrAttribution, true));
+                if (DEBUG) EnergyConsumerResultUtils.print(ecrAttribution);
                 break;
 
             case MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP:
@@ -163,7 +175,7 @@
                         // deserialize, then re-serialize.  This is computationally inefficient.
                         EnergyConsumerResult[] energyConsumerResult =
                             EnergyConsumerResultUtils.unpackProtoMessage(data);
-                        EnergyConsumerResultUtils.packProtoMessage(energyConsumerResult, pos);
+                        EnergyConsumerResultUtils.packProtoMessage(energyConsumerResult, pos, true);
                         if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResult);
                     } catch (IOException e) {
                         Slog.e(TAG, "Failed to write energy model data to incident report.");
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index 5bd907f..ea41980 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -21,7 +21,9 @@
 import android.hardware.power.stats.Channel;
 import android.hardware.power.stats.EnergyConsumer;
 import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyMeasurement;
 import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.StateResidencyResult;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Handler;
@@ -69,6 +71,8 @@
     private BatteryTrigger mBatteryTrigger;
     @Nullable
     private TimerTrigger mTimerTrigger;
+    @Nullable
+    private StatsPullAtomCallbackImpl mPullAtomCallback;
 
     @VisibleForTesting
     static class Injector {
@@ -119,6 +123,11 @@
         TimerTrigger createTimerTrigger(Context context, PowerStatsLogger powerStatsLogger) {
             return new TimerTrigger(context, powerStatsLogger, true /* trigger enabled */);
         }
+
+        StatsPullAtomCallbackImpl createStatsPullerImpl(Context context,
+                IPowerStatsHALWrapper powerStatsHALWrapper) {
+            return new StatsPullAtomCallbackImpl(context, powerStatsHALWrapper);
+        }
     }
 
     private final class BinderService extends Binder {
@@ -156,8 +165,10 @@
 
     @Override
     public void onBootPhase(int phase) {
-        if (phase == SystemService.PHASE_BOOT_COMPLETED) {
-            onSystemServiceReady();
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            onSystemServicesReady();
+        } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+            onBootCompleted();
         }
     }
 
@@ -170,7 +181,18 @@
         publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
     }
 
-    private void onSystemServiceReady() {
+    private void onSystemServicesReady() {
+        if (getPowerStatsHal().isInitialized()) {
+            if (DEBUG) Slog.d(TAG, "Starting PowerStatsService statsd pullers");
+
+            // Only start statsd pullers if initialization is successful.
+            mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, getPowerStatsHal());
+        } else {
+            Slog.e(TAG, "Failed to start PowerStatsService statsd pullers");
+        }
+    }
+
+    private void onBootCompleted() {
         if (getPowerStatsHal().isInitialized()) {
             if (DEBUG) Slog.d(TAG, "Starting PowerStatsService loggers");
 
@@ -225,10 +247,50 @@
                             future, energyConsumerIds));
             return future;
         }
+
+        @Override
+        public PowerEntity[] getPowerEntityInfo() {
+            return getPowerStatsHal().getPowerEntityInfo();
+        }
+
+        @Override
+        public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
+                int[] powerEntityIds) {
+            final CompletableFuture<StateResidencyResult[]> future = new CompletableFuture<>();
+            mHandler.sendMessage(
+                    PooledLambda.obtainMessage(PowerStatsService.this::getStateResidencyAsync,
+                            future, powerEntityIds));
+            return future;
+        }
+
+        @Override
+        public Channel[] getEnergyMeterInfo() {
+            return getPowerStatsHal().getEnergyMeterInfo();
+        }
+
+        @Override
+        public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
+                int[] channelIds) {
+            final CompletableFuture<EnergyMeasurement[]> future = new CompletableFuture<>();
+            mHandler.sendMessage(
+                    PooledLambda.obtainMessage(PowerStatsService.this::readEnergyMeterAsync,
+                            future, channelIds));
+            return future;
+        }
     }
 
     private void getEnergyConsumedAsync(CompletableFuture<EnergyConsumerResult[]> future,
             int[] energyConsumerIds) {
         future.complete(getPowerStatsHal().getEnergyConsumed(energyConsumerIds));
     }
+
+    private void getStateResidencyAsync(CompletableFuture<StateResidencyResult[]> future,
+            int[] powerEntityIds) {
+        future.complete(getPowerStatsHal().getStateResidency(powerEntityIds));
+    }
+
+    private void readEnergyMeterAsync(CompletableFuture<EnergyMeasurement[]> future,
+            int[] channelIds) {
+        future.complete(getPowerStatsHal().readEnergyMeter(channelIds));
+    }
 }
diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
index e71b962..bd003d3 100644
--- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
+++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
@@ -18,6 +18,7 @@
 
 import android.hardware.power.stats.Channel;
 import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyMeasurement;
 import android.hardware.power.stats.PowerEntity;
@@ -266,6 +267,7 @@
                 long token = pos.start(PowerStatsServiceMeterProto.CHANNEL);
                 pos.write(ChannelProto.ID, channel[i].id);
                 pos.write(ChannelProto.NAME, channel[i].name);
+                pos.write(ChannelProto.SUBSYSTEM, channel[i].subsystem);
                 pos.end(token);
             }
         }
@@ -275,7 +277,8 @@
 
             for (int i = 0; i < channel.length; i++) {
                 Slog.d(TAG, "ChannelId: " + channel[i].id
-                        + ", ChannelName: " + channel[i].name);
+                        + ", ChannelName: " + channel[i].name
+                        + ", ChannelSubsystem: " + channel[i].subsystem);
             }
         }
 
@@ -284,7 +287,8 @@
 
             for (int i = 0; i < channel.length; i++) {
                 pw.println("ChannelId: " + channel[i].id
-                        + ", ChannelName: " + channel[i].name);
+                        + ", ChannelName: " + channel[i].name
+                        + ", ChannelSubsystem: " + channel[i].subsystem);
             }
         }
     }
@@ -430,23 +434,40 @@
     }
 
     static class EnergyConsumerResultUtils {
-        public static byte[] getProtoBytes(EnergyConsumerResult[] energyConsumerResult) {
+        public static byte[] getProtoBytes(EnergyConsumerResult[] energyConsumerResult,
+                boolean includeAttribution) {
             ProtoOutputStream pos = new ProtoOutputStream();
-            packProtoMessage(energyConsumerResult, pos);
+            packProtoMessage(energyConsumerResult, pos, includeAttribution);
             return pos.getBytes();
         }
 
         public static void packProtoMessage(EnergyConsumerResult[] energyConsumerResult,
-                ProtoOutputStream pos) {
+                ProtoOutputStream pos, boolean includeAttribution) {
             if (energyConsumerResult == null) return;
 
             for (int i = 0; i < energyConsumerResult.length; i++) {
-                long token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
+                long ecrToken = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
                 pos.write(EnergyConsumerResultProto.ID, energyConsumerResult[i].id);
                 pos.write(EnergyConsumerResultProto.TIMESTAMP_MS,
                         energyConsumerResult[i].timestampMs);
                 pos.write(EnergyConsumerResultProto.ENERGY_UWS, energyConsumerResult[i].energyUWs);
-                pos.end(token);
+
+                if (includeAttribution) {
+                    final int attributionLength = energyConsumerResult[i].attribution.length;
+
+                    for (int j = 0; j < attributionLength; j++) {
+                        final EnergyConsumerAttribution energyConsumerAttribution =
+                                energyConsumerResult[i].attribution[j];
+                        final long ecaToken = pos.start(EnergyConsumerResultProto.ATTRIBUTION);
+                        pos.write(EnergyConsumerAttributionProto.UID,
+                                energyConsumerAttribution.uid);
+                        pos.write(EnergyConsumerAttributionProto.ENERGY_UWS,
+                                energyConsumerAttribution.energyUWs);
+                        pos.end(ecaToken);
+                    }
+                }
+
+                pos.end(ecrToken);
             }
         }
 
@@ -477,9 +498,45 @@
             }
         }
 
+        private static EnergyConsumerAttribution unpackEnergyConsumerAttributionProto(
+                ProtoInputStream pis) throws IOException {
+            final EnergyConsumerAttribution energyConsumerAttribution =
+                    new EnergyConsumerAttribution();
+
+            while (true) {
+                try {
+                    switch (pis.nextField()) {
+                        case (int) EnergyConsumerAttributionProto.UID:
+                            energyConsumerAttribution.uid =
+                                pis.readInt(EnergyConsumerAttributionProto.UID);
+                            break;
+
+                        case (int) EnergyConsumerAttributionProto.ENERGY_UWS:
+                            energyConsumerAttribution.energyUWs =
+                                pis.readLong(EnergyConsumerAttributionProto.ENERGY_UWS);
+                            break;
+
+                        case ProtoInputStream.NO_MORE_FIELDS:
+                            return energyConsumerAttribution;
+
+                        default:
+                            Slog.e(TAG, "Unhandled field in EnergyConsumerAttributionProto: "
+                                    + ProtoUtils.currentFieldToString(pis));
+                            break;
+
+                    }
+                } catch (WireTypeMismatchException wtme) {
+                    Slog.e(TAG, "Wire Type mismatch in EnergyConsumerAttributionProto: "
+                            + ProtoUtils.currentFieldToString(pis));
+                }
+            }
+        }
+
         private static EnergyConsumerResult unpackEnergyConsumerResultProto(ProtoInputStream pis)
                 throws IOException {
             EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult();
+            final List<EnergyConsumerAttribution> energyConsumerAttributionList =
+                    new ArrayList<EnergyConsumerAttribution>();
 
             while (true) {
                 try {
@@ -498,7 +555,18 @@
                                 pis.readLong(EnergyConsumerResultProto.ENERGY_UWS);
                             break;
 
+                        case (int) EnergyConsumerResultProto.ATTRIBUTION:
+                            final long token = pis.start(EnergyConsumerResultProto.ATTRIBUTION);
+                            energyConsumerAttributionList.add(
+                                    unpackEnergyConsumerAttributionProto(pis));
+                            pis.end(token);
+                            break;
+
                         case ProtoInputStream.NO_MORE_FIELDS:
+                            energyConsumerResult.attribution =
+                                energyConsumerAttributionList.toArray(
+                                    new EnergyConsumerAttribution[
+                                        energyConsumerAttributionList.size()]);
                             return energyConsumerResult;
 
                         default:
@@ -517,9 +585,16 @@
             if (energyConsumerResult == null) return;
 
             for (int i = 0; i < energyConsumerResult.length; i++) {
-                Slog.d(TAG, "EnergyConsumerId: " + energyConsumerResult[i].id
-                        + ", Timestamp (ms): " + energyConsumerResult[i].timestampMs
-                        + ", Energy (uWs): " + energyConsumerResult[i].energyUWs);
+                final EnergyConsumerResult result = energyConsumerResult[i];
+                Slog.d(TAG, "EnergyConsumerId: " + result.id
+                        + ", Timestamp (ms): " + result.timestampMs
+                        + ", Energy (uWs): " + result.energyUWs);
+                final int attributionLength = result.attribution.length;
+                for (int j = 0; j < attributionLength; j++) {
+                    final EnergyConsumerAttribution attribution = result.attribution[j];
+                    Slog.d(TAG, "  UID: " + attribution.uid
+                            + "  Energy (uWs): " + attribution.energyUWs);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
new file mode 100644
index 0000000..7c6999a
--- /dev/null
+++ b/services/core/java/com/android/server/powerstats/StatsPullAtomCallbackImpl.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 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.powerstats;
+
+import android.app.StatsManager;
+import android.content.Context;
+import android.hardware.power.stats.Channel;
+import android.hardware.power.stats.EnergyMeasurement;
+import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.State;
+import android.hardware.power.stats.StateResidency;
+import android.hardware.power.stats.StateResidencyResult;
+import android.util.StatsEvent;
+
+import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * StatsPullAtomCallbackImpl is responsible implementing the stats pullers for
+ * SUBSYSTEM_SLEEP_STATE and ON_DEVICE_POWER_MEASUREMENT statsd atoms.
+ */
+public class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+    private Context mContext;
+    private IPowerStatsHALWrapper mPowerStatsHALWrapper;
+    private Map<Integer, Channel> mChannels = new HashMap();
+    private Map<Integer, String> mEntityNames = new HashMap();
+    private Map<Integer, Map<Integer, String>> mStateNames = new HashMap();;
+
+    @Override
+    public int onPullAtom(int atomTag, List<StatsEvent> data) {
+        switch (atomTag) {
+            case FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE:
+                return pullSubsystemSleepState(atomTag, data);
+            case FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT:
+                return pullOnDevicePowerMeasurement(atomTag, data);
+            default:
+                throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
+        }
+    }
+
+    private void initPullOnDevicePowerMeasurement() {
+        Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo();
+        if (channels == null) {
+            return;
+        }
+
+        for (int i = 0; i < channels.length; i++) {
+            final Channel channel = channels[i];
+            mChannels.put(channel.id, channel);
+        }
+    }
+
+    private int pullOnDevicePowerMeasurement(int atomTag, List<StatsEvent> events) {
+        EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
+        if (energyMeasurements == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        for (int i = 0; i < energyMeasurements.length; i++) {
+            // Only report energy measurements that have been accumulated since boot
+            final EnergyMeasurement energyMeasurement = energyMeasurements[i];
+            if (energyMeasurement.durationMs == energyMeasurement.timestampMs) {
+                events.add(FrameworkStatsLog.buildStatsEvent(
+                        atomTag,
+                        mChannels.get(energyMeasurement.id).subsystem,
+                        mChannels.get(energyMeasurement.id).name,
+                        energyMeasurement.durationMs,
+                        energyMeasurement.energyUWs));
+            }
+        }
+
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void initSubsystemSleepState() {
+        PowerEntity[] entities = mPowerStatsHALWrapper.getPowerEntityInfo();
+        if (entities == null) {
+            return;
+        }
+
+        for (int i = 0; i < entities.length; i++) {
+            final PowerEntity entity = entities[i];
+            Map<Integer, String> states = new HashMap();
+            for (int j = 0; j < entity.states.length; j++) {
+                final State state = entity.states[j];
+                states.put(state.id, state.name);
+            }
+
+            mEntityNames.put(entity.id, entity.name);
+            mStateNames.put(entity.id, states);
+        }
+    }
+
+    private int pullSubsystemSleepState(int atomTag, List<StatsEvent> events) {
+        StateResidencyResult[] results =  mPowerStatsHALWrapper.getStateResidency(new int[0]);
+        if (results == null) {
+            return StatsManager.PULL_SKIP;
+        }
+        for (int i = 0; i < results.length; i++) {
+            final StateResidencyResult result = results[i];
+            for (int j = 0; j < result.stateResidencyData.length; j++) {
+                final StateResidency stateResidency = result.stateResidencyData[j];
+                events.add(FrameworkStatsLog.buildStatsEvent(
+                        atomTag,
+                        mEntityNames.get(result.id),
+                        mStateNames.get(result.id).get(stateResidency.id),
+                        stateResidency.totalStateEntryCount,
+                        stateResidency.totalTimeInStateMs));
+            }
+        }
+
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    public StatsPullAtomCallbackImpl(Context context, IPowerStatsHALWrapper powerStatsHALWrapper) {
+        mContext = context;
+        mPowerStatsHALWrapper = powerStatsHALWrapper;
+        initPullOnDevicePowerMeasurement();
+        initSubsystemSleepState();
+
+        StatsManager manager = mContext.getSystemService(StatsManager.class);
+        manager.setPullAtomCallback(
+                FrameworkStatsLog.SUBSYSTEM_SLEEP_STATE,
+                null, // use default PullAtomMetadata values
+                ConcurrentUtils.DIRECT_EXECUTOR,
+                this);
+        manager.setPullAtomCallback(
+                FrameworkStatsLog.ON_DEVICE_POWER_MEASUREMENT,
+                null, // use default PullAtomMetadata values
+                ConcurrentUtils.DIRECT_EXECUTOR,
+                this);
+    }
+}
diff --git a/services/core/java/com/android/server/powerstats/TimerTrigger.java b/services/core/java/com/android/server/powerstats/TimerTrigger.java
index 7cba00f..f8a4135 100644
--- a/services/core/java/com/android/server/powerstats/TimerTrigger.java
+++ b/services/core/java/com/android/server/powerstats/TimerTrigger.java
@@ -29,18 +29,30 @@
     private static final String TAG = TimerTrigger.class.getSimpleName();
     private static final boolean DEBUG = false;
     // TODO(b/166689029): Make configurable through global settings.
-    private static final long LOG_PERIOD_MS = 120 * 1000;
+    private static final long LOG_PERIOD_MS_LOW_FREQUENCY = 60 * 60 * 1000; // 1 hour
+    private static final long LOG_PERIOD_MS_HIGH_FREQUENCY = 2 * 60 * 1000; // 2 minutes
 
     private final Handler mHandler;
 
-    private Runnable mLogData = new Runnable() {
+    private Runnable mLogDataLowFrequency = new Runnable() {
         @Override
         public void run() {
             // Do not wake the device for these messages.  Opportunistically log rail data every
-            // LOG_PERIOD_MS.
-            mHandler.postDelayed(mLogData, LOG_PERIOD_MS);
-            if (DEBUG) Slog.d(TAG, "Received delayed message.  Log rail data");
-            logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER);
+            // LOG_PERIOD_MS_LOW_FREQUENCY.
+            mHandler.postDelayed(mLogDataLowFrequency, LOG_PERIOD_MS_LOW_FREQUENCY);
+            if (DEBUG) Slog.d(TAG, "Received delayed message.  Log rail data low frequency");
+            logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
+        }
+    };
+
+    private Runnable mLogDataHighFrequency = new Runnable() {
+        @Override
+        public void run() {
+            // Do not wake the device for these messages.  Opportunistically log rail data every
+            // LOG_PERIOD_MS_HIGH_FREQUENCY.
+            mHandler.postDelayed(mLogDataHighFrequency, LOG_PERIOD_MS_HIGH_FREQUENCY);
+            if (DEBUG) Slog.d(TAG, "Received delayed message.  Log rail data high frequency");
+            logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
         }
     };
 
@@ -50,7 +62,8 @@
         mHandler = mContext.getMainThreadHandler();
 
         if (triggerEnabled) {
-            mLogData.run();
+            mLogDataLowFrequency.run();
+            mLogDataHighFrequency.run();
         }
     }
 }
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index e12991a..9560f59 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -563,6 +563,7 @@
                 params.setRequestDowngrade(true);
                 params.setRequiredInstalledVersionCode(
                         pkgRollbackInfo.getVersionRolledBackFrom().getLongVersionCode());
+                params.setInstallReason(PackageManager.INSTALL_REASON_ROLLBACK);
                 if (isStaged()) {
                     params.setStaged();
                 }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index bd66aa3..a4459d0 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -33,9 +33,9 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
@@ -831,24 +831,24 @@
         }
 
         // Get information about the package to be installed.
-        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-        ParseResult<PackageParser.ApkLite> parseResult = ApkLiteParseUtils.parseApkLite(
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<ApkLite> parseResult = ApkLiteParseUtils.parseApkLite(
                 input.reset(), new File(session.resolvedBaseCodePath), 0);
         if (parseResult.isError()) {
             Slog.e(TAG, "Unable to parse new package: " + parseResult.getErrorMessage(),
                     parseResult.getException());
             return false;
         }
-        PackageParser.ApkLite newPackage = parseResult.getResult();
+        final ApkLite newPackage = parseResult.getResult();
 
-        String packageName = newPackage.packageName;
-        int rollbackDataPolicy = computeRollbackDataPolicy(
-                session.rollbackDataPolicy, newPackage.rollbackDataPolicy);
+        final String packageName = newPackage.getPackageName();
+        final int rollbackDataPolicy = computeRollbackDataPolicy(
+                session.rollbackDataPolicy, newPackage.getRollbackDataPolicy());
         Slog.i(TAG, "Enabling rollback for install of " + packageName
                 + ", session:" + session.sessionId
                 + ", rollbackDataPolicy=" + rollbackDataPolicy);
 
-        String installerPackageName = session.getInstallerPackageName();
+        final String installerPackageName = session.getInstallerPackageName();
         if (!enableRollbackAllowed(installerPackageName, packageName)) {
             Slog.e(TAG, "Installer " + installerPackageName
                     + " is not allowed to enable rollback on " + packageName);
@@ -900,7 +900,7 @@
          * a rollback object is inconsistent because it doesn't count apk-in-apex.
          */
         ApplicationInfo appInfo = pkgInfo.applicationInfo;
-        return rollback.enableForPackage(packageName, newPackage.versionCode,
+        return rollback.enableForPackage(packageName, newPackage.getVersionCode(),
                 pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
                 appInfo.splitSourceDirs, rollbackDataPolicy);
     }
diff --git a/services/core/java/com/android/server/rotationresolver/OWNERS b/services/core/java/com/android/server/rotationresolver/OWNERS
new file mode 100644
index 0000000..81b6f05
--- /dev/null
+++ b/services/core/java/com/android/server/rotationresolver/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/rotationresolver/OWNERS
diff --git a/services/core/java/com/android/server/rotationresolver/RemoteRotationResolverService.java b/services/core/java/com/android/server/rotationresolver/RemoteRotationResolverService.java
index 43f8a3a..b995b19 100644
--- a/services/core/java/com/android/server/rotationresolver/RemoteRotationResolverService.java
+++ b/services/core/java/com/android/server/rotationresolver/RemoteRotationResolverService.java
@@ -21,6 +21,9 @@
 import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_CANCELLED;
 import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_TIMED_OUT;
 
+import static com.android.server.rotationresolver.RotationResolverManagerService.RESOLUTION_FAILURE;
+import static com.android.server.rotationresolver.RotationResolverManagerService.logRotationStats;
+
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
@@ -29,6 +32,7 @@
 import android.os.Handler;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.rotationresolver.RotationResolverInternal;
 import android.service.rotationresolver.IRotationResolverCallback;
 import android.service.rotationresolver.IRotationResolverService;
@@ -41,6 +45,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.ServiceConnector;
 
+import java.lang.ref.WeakReference;
+
 
 /** Manages the connection to the remote rotation resolver service. */
 class RemoteRotationResolverService extends ServiceConnector.Impl<IRotationResolverService> {
@@ -112,6 +118,7 @@
 
         boolean mIsDispatched;
         private final Object mLock = new Object();
+        private final long mRequestStartTimeMillis;
 
         RotationRequest(
                 @NonNull RotationResolverInternal.RotationResolverCallbackInternal
@@ -123,12 +130,20 @@
             mProposedRotation = proposedRotation;
             mCurrentRotation = currentRotation;
             mPackageName = packageName;
-            mIRotationResolverCallback = new RotationResolverCallback();
+            mIRotationResolverCallback = new RotationResolverCallback(this);
             mCancellationSignalInternal = cancellationSignal;
+            mRequestStartTimeMillis = SystemClock.elapsedRealtime();
         }
 
 
         void cancelInternal() {
+            synchronized (mLock) {
+                if (mIsFulfilled) {
+                    Slog.v(TAG, "Trying to cancel the request that has been already fulfilled.");
+                    return;
+                }
+                mIsFulfilled = true;
+            }
             Handler.getMain().post(() -> {
                 synchronized (mLock) {
                     try {
@@ -141,9 +156,6 @@
                     }
                 }
             });
-            synchronized (mLock) {
-                mIsFulfilled = true;
-            }
             mCallbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
         }
 
@@ -154,38 +166,53 @@
             ipw.decreaseIndent();
         }
 
-        private class RotationResolverCallback extends IRotationResolverCallback.Stub {
+        private static class RotationResolverCallback extends IRotationResolverCallback.Stub {
+            private WeakReference<RotationRequest> mRequestWeakReference;
+
+            RotationResolverCallback(RotationRequest request) {
+                this.mRequestWeakReference = new WeakReference<>(request);
+            }
+
             @Override
             public void onSuccess(int rotation) {
-                synchronized (mLock) {
-                    if (mIsFulfilled) {
+                final RotationRequest request = mRequestWeakReference.get();
+                synchronized (request.mLock) {
+                    if (request.mIsFulfilled) {
                         Slog.w(TAG, "Callback received after the rotation request is fulfilled.");
                         return;
                     }
-                    mIsFulfilled = true;
-                    mCallbackInternal.onSuccess(rotation);
-                    logStats(rotation);
+                    request.mIsFulfilled = true;
+                    request.mCallbackInternal.onSuccess(rotation);
+                    final long timeToCalculate =
+                            SystemClock.elapsedRealtime() - request.mRequestStartTimeMillis;
+                    logRotationStats(request.mProposedRotation, request.mCurrentRotation, rotation,
+                            timeToCalculate);
                 }
             }
 
             @Override
             public void onFailure(int error) {
-                synchronized (mLock) {
-                    if (mIsFulfilled) {
+                final RotationRequest request = mRequestWeakReference.get();
+                synchronized (request.mLock) {
+                    if (request.mIsFulfilled) {
                         Slog.w(TAG, "Callback received after the rotation request is fulfilled.");
                         return;
                     }
-                    mIsFulfilled = true;
-                    mCallbackInternal.onFailure(error);
-                    logStats(error);
+                    request.mIsFulfilled = true;
+                    request.mCallbackInternal.onFailure(error);
+                    final long timeToCalculate =
+                            SystemClock.elapsedRealtime() - request.mRequestStartTimeMillis;
+                    logRotationStats(request.mProposedRotation, request.mCurrentRotation,
+                            RESOLUTION_FAILURE, timeToCalculate);
                 }
             }
 
             @Override
             public void onCancellable(@NonNull ICancellationSignal cancellation) {
-                synchronized (mLock) {
-                    mCancellation = cancellation;
-                    if (mCancellationSignalInternal.isCanceled()) {
+                final RotationRequest request = mRequestWeakReference.get();
+                synchronized (request.mLock) {
+                    request.mCancellation = cancellation;
+                    if (request.mCancellationSignalInternal.isCanceled()) {
                         // Dispatch the cancellation signal if the client has cancelled the request.
                         try {
                             cancellation.cancel();
@@ -196,10 +223,6 @@
                 }
 
             }
-
-            private void logStats(int result) {
-                // TODO FrameworkStatsLog
-            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
index f0e2d79..6f7c016 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
@@ -18,7 +18,9 @@
 
 import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_CANCELLED;
 
+import static com.android.server.rotationresolver.RotationResolverManagerService.RESOLUTION_UNAVAILABLE;
 import static com.android.server.rotationresolver.RotationResolverManagerService.getServiceConfigPackage;
+import static com.android.server.rotationresolver.RotationResolverManagerService.logRotationStats;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -98,6 +100,7 @@
         if (!isServiceAvailableLocked()) {
             Slog.w(TAG, "Service is not available at this moment.");
             callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
+            logRotationStats(proposedRotation, currentRotation, RESOLUTION_UNAVAILABLE);
             return;
         }
 
@@ -119,14 +122,9 @@
             }
         });
 
-        if (mRemoteService != null) {
-            mRemoteService.resolveRotationLocked(mCurrentRequest);
-            mCurrentRequest.mIsDispatched = true;
-        } else {
-            Slog.w(TAG, "Remote service is not available at this moment.");
-            callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
-            cancelLocked();
-        }
+
+        mRemoteService.resolveRotationLocked(mCurrentRequest);
+        mCurrentRequest.mIsDispatched = true;
     }
 
     @GuardedBy("mLock")
@@ -156,7 +154,7 @@
         final Intent intent = new Intent(
                 RotationResolverService.SERVICE_INTERFACE).setPackage(resolvedPackage);
 
-        final ResolveInfo resolveInfo = context.getPackageManager().resolveActivityAsUser(intent,
+        final ResolveInfo resolveInfo = context.getPackageManager().resolveServiceAsUser(intent,
                 flags, context.getUserId());
         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
             Slog.wtf(TAG, String.format("Service %s not found in package %s",
@@ -195,15 +193,6 @@
         if (mCurrentRequest == null) {
             return;
         }
-
-        if (mCurrentRequest.mIsFulfilled) {
-            if (isVerbose()) {
-                Slog.d(TAG, "Trying to cancel the request that has been already fulfilled.");
-            }
-            mCurrentRequest = null;
-            return;
-        }
-
         mCurrentRequest.cancelInternal();
         mCurrentRequest = null;
     }
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index 0377d23..03d7664 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -19,6 +19,11 @@
 import static android.provider.DeviceConfig.NAMESPACE_ROTATION_RESOLVER;
 import static android.service.rotationresolver.RotationResolverService.ROTATION_RESULT_FAILURE_CANCELLED;
 
+import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_0;
+import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_180;
+import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_270;
+import static com.android.internal.util.FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_90;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -33,9 +38,11 @@
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
+import android.view.Surface;
 
 import com.android.internal.R;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.SystemService;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -61,6 +68,15 @@
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = false;
 
+    static final int ORIENTATION_UNKNOWN =
+            FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNKNOWN;
+    static final int RESOLUTION_DISABLED =
+            FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__DISABLED;
+    static final int RESOLUTION_UNAVAILABLE =
+            FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__UNAVAILABLE;
+    static final int RESOLUTION_FAILURE =
+            FrameworkStatsLog.AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__FAILURE;
+
     private final Context mContext;
     boolean mIsServiceEnabled;
 
@@ -147,6 +163,7 @@
                 } else {
                     Slog.w(TAG, "Rotation Resolver service is disabled.");
                     callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED);
+                    logRotationStats(proposedRotation, currentRotation, RESOLUTION_DISABLED);
                 }
             }
         }
@@ -174,8 +191,40 @@
                     TAG);
             final RotationResolverManagerPerUserService service = getServiceForUserLocked(
                     UserHandle.getCallingUserId());
-            new RotationResolverShellCommend(service).exec(this, in, out, err, args, callback,
+            new RotationResolverShellCommand(service).exec(this, in, out, err, args, callback,
                     resultReceiver);
         }
     }
+
+    static void logRotationStats(int proposedRotation, int currentRotation,
+            int resolvedRotation, long timeToCalculate) {
+        FrameworkStatsLog.write(FrameworkStatsLog.AUTO_ROTATE_REPORTED,
+                /* previous_orientation= */ surfaceRotationToProto(currentRotation),
+                /* proposed_orientation= */ surfaceRotationToProto(proposedRotation),
+                /* resolved_orientation= */ surfaceRotationToProto(resolvedRotation),
+                /* process_duration_millis= */ timeToCalculate);
+    }
+
+    static void logRotationStats(int proposedRotation, int currentRotation,
+            int resolvedRotation) {
+        FrameworkStatsLog.write(FrameworkStatsLog.AUTO_ROTATE_REPORTED,
+                /* previous_orientation= */ surfaceRotationToProto(currentRotation),
+                /* proposed_orientation= */ surfaceRotationToProto(proposedRotation),
+                /* resolved_orientation= */ surfaceRotationToProto(resolvedRotation));
+    }
+
+    private static int surfaceRotationToProto(@Surface.Rotation int rotationPoseResult) {
+        switch (rotationPoseResult) {
+            case Surface.ROTATION_0:
+                return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_0;
+            case Surface.ROTATION_90:
+                return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_90;
+            case Surface.ROTATION_180:
+                return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_180;
+            case Surface.ROTATION_270:
+                return AUTO_ROTATE_REPORTED__PROPOSED_ORIENTATION__ROTATION_270;
+            default:
+                return ORIENTATION_UNKNOWN;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommend.java b/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
similarity index 95%
rename from services/core/java/com/android/server/rotationresolver/RotationResolverShellCommend.java
rename to services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
index 0a873892..54a9edb 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommend.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
@@ -26,13 +26,13 @@
 
 import java.io.PrintWriter;
 
-final class RotationResolverShellCommend extends ShellCommand {
+final class RotationResolverShellCommand extends ShellCommand {
     private static final int INITIAL_RESULT_CODE = -1;
 
     @NonNull
     private final RotationResolverManagerPerUserService mService;
 
-    RotationResolverShellCommend(@NonNull RotationResolverManagerPerUserService service) {
+    RotationResolverShellCommand(@NonNull RotationResolverManagerPerUserService service) {
         mService = service;
     }
 
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
index 90ac69a..cf7460b 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
@@ -42,7 +42,7 @@
     static final String TAG = "SoundTriggerHw2Enforcer";
 
     final ISoundTriggerHw2 mUnderlying;
-    final Map<Integer, Boolean> mModelStates = new HashMap<>();
+    Map<Integer, Boolean> mModelStates = new HashMap<>();
 
     public SoundTriggerHw2Enforcer(
             ISoundTriggerHw2 underlying) {
@@ -62,12 +62,12 @@
     public int loadSoundModel(ISoundTriggerHw.SoundModel soundModel, Callback callback,
             int cookie) {
         try {
+            int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback),
+                    cookie);
             synchronized (mModelStates) {
-                int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback),
-                        cookie);
                 mModelStates.put(handle, false);
-                return handle;
             }
+            return handle;
         } catch (RuntimeException e) {
             throw handleException(e);
         }
@@ -77,13 +77,13 @@
     public int loadPhraseSoundModel(ISoundTriggerHw.PhraseSoundModel soundModel, Callback callback,
             int cookie) {
         try {
+            int handle = mUnderlying.loadPhraseSoundModel(soundModel,
+                    new CallbackEnforcer(callback),
+                    cookie);
             synchronized (mModelStates) {
-                int handle = mUnderlying.loadPhraseSoundModel(soundModel,
-                        new CallbackEnforcer(callback),
-                        cookie);
                 mModelStates.put(handle, false);
-                return handle;
             }
+            return handle;
         } catch (RuntimeException e) {
             throw handleException(e);
         }
@@ -92,8 +92,8 @@
     @Override
     public void unloadSoundModel(int modelHandle) {
         try {
+            mUnderlying.unloadSoundModel(modelHandle);
             synchronized (mModelStates) {
-                mUnderlying.unloadSoundModel(modelHandle);
                 mModelStates.remove(modelHandle);
             }
         } catch (RuntimeException e) {
@@ -104,8 +104,8 @@
     @Override
     public void stopRecognition(int modelHandle) {
         try {
+            mUnderlying.stopRecognition(modelHandle);
             synchronized (mModelStates) {
-                mUnderlying.stopRecognition(modelHandle);
                 mModelStates.replace(modelHandle, false);
             }
         } catch (RuntimeException e) {
@@ -116,8 +116,8 @@
     @Override
     public void stopAllRecognitions() {
         try {
+            mUnderlying.stopAllRecognitions();
             synchronized (mModelStates) {
-                mUnderlying.stopAllRecognitions();
                 for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) {
                     entry.setValue(false);
                 }
@@ -130,12 +130,14 @@
     @Override
     public void startRecognition(int modelHandle, RecognitionConfig config, Callback callback,
             int cookie) {
+        // It is possible that an event will be sent before the HAL returns from the
+        // startRecognition call, thus it is important to set the state to active before the call.
+        synchronized (mModelStates) {
+            mModelStates.replace(modelHandle, true);
+        }
         try {
-            synchronized (mModelStates) {
-                mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback),
-                        cookie);
-                mModelStates.replace(modelHandle, true);
-            }
+            mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback),
+                    cookie);
         } catch (RuntimeException e) {
             throw handleException(e);
         }
diff --git a/services/core/java/com/android/server/speech/Android.bp b/services/core/java/com/android/server/speech/Android.bp
new file mode 100644
index 0000000..379b075
--- /dev/null
+++ b/services/core/java/com/android/server/speech/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+    name: "services.speech-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.speech",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.speech-sources"],
+    libs: ["services.core"],
+}
diff --git a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
new file mode 100644
index 0000000..96248c3
--- /dev/null
+++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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.speech;
+
+import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.speech.IRecognitionListener;
+import android.speech.IRecognitionService;
+import android.speech.RecognitionService;
+import android.util.Slog;
+
+import com.android.internal.infra.ServiceConnector;
+
+final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecognitionService> {
+    private static final String TAG = RemoteSpeechRecognitionService.class.getSimpleName();
+    private static final boolean DEBUG = true;
+
+    RemoteSpeechRecognitionService(Context context, ComponentName serviceName, int userId) {
+        super(context,
+                new Intent(RecognitionService.SERVICE_INTERFACE).setComponent(serviceName),
+                Context.BIND_AUTO_CREATE
+                        | Context.BIND_FOREGROUND_SERVICE
+                        | Context.BIND_INCLUDE_CAPABILITIES
+                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
+                userId,
+                IRecognitionService.Stub::asInterface);
+
+        if (DEBUG) {
+            Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString());
+        }
+    }
+
+    void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName,
+            String featureId) throws RemoteException {
+        if (DEBUG) {
+            Slog.i(TAG, "#startListening for package: " + packageName + ", feature=" + featureId);
+        }
+        run(service -> service.startListening(recognizerIntent, listener, packageName, featureId));
+    }
+
+    void stopListening(IRecognitionListener listener, String packageName, String featureId)
+            throws RemoteException {
+        if (DEBUG) {
+            Slog.i(TAG, "#stopListening for package: " + packageName + ", feature=" + featureId);
+        }
+        run(service -> service.stopListening(listener, packageName, featureId));
+    }
+
+    void cancel(IRecognitionListener listener, String packageName, String featureId)
+            throws RemoteException {
+        if (DEBUG) {
+            Slog.i(TAG, "#cancel for package: " + packageName + ", feature=" + featureId);
+        }
+        run(service -> service.cancel(listener, packageName, featureId));
+    }
+
+    @Override // from ServiceConnector.Impl
+    protected void onServiceConnectionStatusChanged(
+            IRecognitionService service, boolean connected) {
+        if (!DEBUG) {
+            return;
+        }
+
+        if (connected) {
+            Slog.i(TAG, "Connected to ASR service");
+        } else {
+            Slog.w(TAG, "Disconnected from ASR service");
+        }
+    }
+
+    @Override // from AbstractRemoteService
+    protected long getAutoDisconnectTimeoutMs() {
+        return PERMANENT_BOUND_TIMEOUT_MS;
+    }
+}
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
new file mode 100644
index 0000000..592ba9e
--- /dev/null
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.speech;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.UserHandle;
+import android.speech.IRecognitionServiceManager;
+import android.speech.IRecognitionServiceManagerCallback;
+
+import com.android.internal.R;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+
+/**
+ * System service implementation for Speech Recognizer.
+ *
+ * <p>This service uses RemoteSpeechRecognitionService to bind to a default implementation of
+ * ISpeechRecognition. It relays all the requests from the client to the default system impl of
+ * ISpeechRecognition service (denoted by {@code
+ * R.string.config_defaultOnDeviceSpeechRecognitionService}).
+ */
+public final class SpeechRecognitionManagerService extends
+        AbstractMasterSystemService<SpeechRecognitionManagerService,
+                SpeechRecognitionManagerServiceImpl> {
+    private static final String TAG = SpeechRecognitionManagerService.class.getSimpleName();
+
+    public SpeechRecognitionManagerService(@NonNull Context context) {
+        super(context,
+                new FrameworkResourcesServiceNameResolver(
+                        context,
+                        R.string.config_defaultOnDeviceSpeechRecognitionService),
+                /*disallowProperty=*/ null);
+    }
+
+    @Override // from SystemService
+    public void onStart() {
+        SpeechRecognitionManagerServiceStub serviceStub = new SpeechRecognitionManagerServiceStub();
+        publishBinderService(Context.SPEECH_RECOGNITION_SERVICE, serviceStub);
+    }
+
+    @Override
+    protected SpeechRecognitionManagerServiceImpl newServiceLocked(
+            @UserIdInt int resolvedUserId, boolean disabled) {
+        return new SpeechRecognitionManagerServiceImpl(this, mLock, resolvedUserId, disabled);
+    }
+
+    final class SpeechRecognitionManagerServiceStub extends IRecognitionServiceManager.Stub {
+
+        @Override
+        public void createSession(IRecognitionServiceManagerCallback callback) {
+            int userId = UserHandle.getCallingUserId();
+            synchronized (mLock) {
+                SpeechRecognitionManagerServiceImpl service = getServiceForUserLocked(userId);
+                service.createSessionLocked(callback);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
new file mode 100644
index 0000000..bcaf174
--- /dev/null
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2021 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.speech;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.speech.IRecognitionListener;
+import android.speech.IRecognitionService;
+import android.speech.IRecognitionServiceManagerCallback;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+final class SpeechRecognitionManagerServiceImpl extends
+        AbstractPerUserSystemService<SpeechRecognitionManagerServiceImpl,
+            SpeechRecognitionManagerService> {
+
+    private static final String TAG = SpeechRecognitionManagerServiceImpl.class.getSimpleName();
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSpeechRecognitionService mRemoteService;
+
+    SpeechRecognitionManagerServiceImpl(
+            @NonNull SpeechRecognitionManagerService master,
+            @NonNull Object lock, @UserIdInt int userId, boolean disabled) {
+        super(master, lock, userId);
+        updateRemoteServiceLocked();
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        try {
+            return AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new PackageManager.NameNotFoundException(
+                    "Could not get service for " + serviceComponent);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        updateRemoteServiceLocked();
+        return enabledChanged;
+    }
+
+    /**
+     * Updates the reference to the remote service.
+     */
+    @GuardedBy("mLock")
+    private void updateRemoteServiceLocked() {
+        if (mRemoteService != null) {
+            if (mMaster.debug) {
+                Slog.d(TAG, "updateRemoteService(): destroying old remote service");
+            }
+            mRemoteService.unbind();
+            mRemoteService = null;
+        }
+    }
+
+    void createSessionLocked(IRecognitionServiceManagerCallback callback) {
+        // TODO(b/176578753): check clients have record audio permission.
+        // TODO(b/176578753): verify caller package is the one supplied
+
+        RemoteSpeechRecognitionService service = ensureRemoteServiceLocked();
+
+        if (service == null) {
+            tryRespondWithError(callback);
+            return;
+        }
+
+        service.connect().thenAccept(binderService -> {
+            if (binderService != null) {
+                try {
+                    callback.onSuccess(new IRecognitionService.Stub() {
+                        @Override
+                        public void startListening(Intent recognizerIntent,
+                                IRecognitionListener listener,
+                                String packageName, String featureId) throws RemoteException {
+                            service.startListening(
+                                    recognizerIntent, listener, packageName, featureId);
+                        }
+
+                        @Override
+                        public void stopListening(IRecognitionListener listener,
+                                String packageName,
+                                String featureId) throws RemoteException {
+                            service.stopListening(listener, packageName, featureId);
+                        }
+
+                        @Override
+                        public void cancel(IRecognitionListener listener,
+                                String packageName,
+                                String featureId) throws RemoteException {
+                            service.cancel(listener, packageName, featureId);
+                        }
+                    });
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error creating a speech recognition session", e);
+                    tryRespondWithError(callback);
+                }
+            } else {
+                tryRespondWithError(callback);
+            }
+        });
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSpeechRecognitionService ensureRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            final String serviceName = getComponentNameLocked();
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name.");
+                }
+                return null;
+            }
+            final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+            mRemoteService =
+                    new RemoteSpeechRecognitionService(getContext(), serviceComponent, mUserId);
+        }
+        return mRemoteService;
+    }
+
+    private static void tryRespondWithError(IRecognitionServiceManagerCallback callback) {
+        try {
+            callback.onError();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to respond with error");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 0cc2a6c..263776c 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -407,8 +407,6 @@
         mContext = context;
     }
 
-    private native void nativeInit();
-
     /**
      * Use of this StatsPullAtomCallbackImpl means we avoid one class per tagId, which we would
      * get if we used lambdas.
@@ -681,7 +679,6 @@
         super.onBootPhase(phase);
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             BackgroundThread.getHandler().post(() -> {
-                nativeInit();
                 initializePullersState();
                 registerPullers();
                 registerEventListeners();
diff --git a/services/core/java/com/android/server/timedetector/DeviceConfig.java b/services/core/java/com/android/server/timedetector/DeviceConfig.java
new file mode 100644
index 0000000..7b9ad0f
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/DeviceConfig.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2021 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.timedetector;
+
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+
+import java.time.Duration;
+import java.util.concurrent.Executor;
+
+/**
+ * A helper class for reading / monitoring the {@link
+ * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags.
+ */
+public final class DeviceConfig {
+
+    /**
+     * An annotation used to indicate when a {@link
+     * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} key is required.
+     *
+     * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider
+     * also shares the {@link android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the
+     * prefix "geotz_" on all of its key strings.
+     */
+    @StringDef(prefix = "KEY_", value = {
+            KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED,
+            KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT,
+            KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
+            KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+            KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
+    })
+    @interface DeviceConfigKey {}
+
+    /**
+     * The key to force location time zone detection on for a device. Only intended for use during
+     * release testing with droidfooders. The user can still disable the feature by turning off the
+     * master location switch, or disabling automatic time zone detection.
+     */
+    @DeviceConfigKey
+    public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED =
+            "force_location_time_zone_detection_enabled";
+
+    /**
+     * The key for the default value used to determine whether location time zone detection is
+     * enabled when the user hasn't explicitly set it yet.
+     */
+    @DeviceConfigKey
+    public static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT =
+            "location_time_zone_detection_enabled_default";
+
+    /**
+     * The key for the minimum delay after location time zone detection has been enabled before the
+     * location time zone manager can report it is uncertain about the time zone.
+     */
+    @DeviceConfigKey
+    public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS =
+            "location_time_zone_detection_uncertainty_delay_millis";
+
+    /**
+     * The key for the timeout passed to a location time zone provider that tells it how long it has
+     * to provide an explicit first suggestion without being declared uncertain.
+     */
+    @DeviceConfigKey
+    public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS =
+            "ltpz_init_timeout_millis";
+
+    /**
+     * The key for the extra time added to {@link
+     * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone
+     * manager before the location time zone provider will actually be declared uncertain.
+     */
+    @DeviceConfigKey
+    public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS =
+            "ltpz_init_timeout_fuzz_millis";
+
+    /** Creates an instance. */
+    public DeviceConfig() {}
+
+    /** Adds a listener for the system_time namespace. */
+    public void addListener(
+            @NonNull Executor handlerExecutor, @NonNull Runnable listener) {
+        android.provider.DeviceConfig.addOnPropertiesChangedListener(
+                NAMESPACE_SYSTEM_TIME,
+                handlerExecutor,
+                properties -> listener.run());
+    }
+
+    /**
+     * Returns a boolean value from {@link android.provider.DeviceConfig} from the system_time
+     * namespace, or {@code defaultValue} if there is no explicit value set.
+     */
+    public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) {
+        return android.provider.DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue);
+    }
+
+    /**
+     * Returns a positive duration from {@link android.provider.DeviceConfig} from the system_time
+     * namespace, or {@code defaultValue} if there is no explicit value set.
+     */
+    @Nullable
+    public Duration getDurationFromMillis(
+            @DeviceConfigKey String key, @Nullable Duration defaultValue) {
+        long deviceConfigValue =
+                android.provider.DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1);
+        if (deviceConfigValue < 0) {
+            return defaultValue;
+        }
+        return Duration.ofMillis(deviceConfigValue);
+    }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
similarity index 91%
rename from services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
rename to services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4f3f9dc..5cd1718 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -25,7 +25,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Build;
-import android.os.Environment;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -39,11 +38,11 @@
 import java.util.Objects;
 
 /**
- * The real implementation of {@link TimeDetectorStrategyImpl.Callback} used on device.
+ * The real implementation of {@link TimeDetectorStrategyImpl.Environment} used on device.
  */
-public final class TimeDetectorStrategyCallbackImpl implements TimeDetectorStrategyImpl.Callback {
+public final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
 
-    private final static String TAG = "timedetector.TimeDetectorStrategyCallbackImpl";
+    private static final String TAG = TimeDetectorService.TAG;
 
     private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
 
@@ -52,7 +51,7 @@
      * incorrect for sure.
      */
     private static final Instant TIME_LOWER_BOUND = Instant.ofEpochMilli(
-            Long.max(Environment.getRootDirectory().lastModified(), Build.TIME));
+            Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
 
     /**
      * By default telephony and network only suggestions are accepted and telephony takes
@@ -74,7 +73,7 @@
     @NonNull private final AlarmManager mAlarmManager;
     @NonNull private final int[] mOriginPriorities;
 
-    public TimeDetectorStrategyCallbackImpl(@NonNull Context context) {
+    public EnvironmentImpl(@NonNull Context context) {
         mContext = Objects.requireNonNull(context);
         mContentResolver = Objects.requireNonNull(context.getContentResolver());
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 71b1a49..bbbd19f 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -50,7 +50,7 @@
  * implementation to deal with the logic around time detection.
  */
 public final class TimeDetectorService extends ITimeDetectorService.Stub {
-    private static final String TAG = "TimeDetectorService";
+    static final String TAG = "time_detector";
 
     public static class Lifecycle extends SystemService {
 
@@ -73,8 +73,8 @@
     @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
 
     private static TimeDetectorService create(@NonNull Context context) {
-        TimeDetectorStrategyImpl.Callback callback = new TimeDetectorStrategyCallbackImpl(context);
-        TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl(callback);
+        TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context);
+        TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl(environment);
 
         Handler handler = FgThread.getHandler();
         TimeDetectorService timeDetectorService =
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 2fbeb75..7cd4184 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -40,6 +40,7 @@
 
 import java.time.Instant;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * An implementation of {@link TimeDetectorStrategy} that passes telephony and manual suggestions to
@@ -51,7 +52,7 @@
 public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy {
 
     private static final boolean DBG = false;
-    private static final String LOG_TAG = "SimpleTimeDetectorStrategy";
+    private static final String LOG_TAG = TimeDetectorService.TAG;
 
     /** A score value used to indicate "no score", either due to validation failure or age. */
     private static final int TELEPHONY_INVALID_SCORE = -1;
@@ -88,7 +89,7 @@
     private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
 
     @NonNull
-    private final Callback mCallback;
+    private final Environment mEnvironment;
 
     // Used to store the last time the system clock state was set automatically. It is used to
     // detect (and log) issues with the realtime clock or whether the clock is being set without
@@ -127,7 +128,7 @@
      * moved to {@link TimeDetectorStrategy}. There are similar issues with
      * {@link #systemClockMillis()} while any process can modify the system clock.
      */
-    public interface Callback {
+    public interface Environment {
 
         /**
          * The absolute threshold below which the system clock need not be updated. i.e. if setting
@@ -170,8 +171,8 @@
         void releaseWakeLock();
     }
 
-    TimeDetectorStrategyImpl(@NonNull Callback callback) {
-        mCallback = callback;
+    TimeDetectorStrategyImpl(@NonNull Environment environment) {
+        mEnvironment = Objects.requireNonNull(environment);
     }
 
     @Override
@@ -267,7 +268,7 @@
 
     @Override
     public synchronized void handleAutoTimeConfigChanged() {
-        boolean enabled = mCallback.isAutoTimeDetectionEnabled();
+        boolean enabled = mEnvironment.isAutoTimeDetectionEnabled();
         // When automatic time detection is enabled we update the system clock instantly if we can.
         // Conversely, when automatic time detection is disabled we leave the clock as it is.
         if (enabled) {
@@ -286,20 +287,20 @@
         ipw.increaseIndent(); // level 1
 
         ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
-        ipw.println("mCallback.isAutoTimeDetectionEnabled()="
-                + mCallback.isAutoTimeDetectionEnabled());
-        ipw.println("mCallback.elapsedRealtimeMillis()=" + mCallback.elapsedRealtimeMillis());
-        ipw.println("mCallback.systemClockMillis()=" + mCallback.systemClockMillis());
-        ipw.println("mCallback.systemClockUpdateThresholdMillis()="
-                + mCallback.systemClockUpdateThresholdMillis());
-        ipw.printf("mCallback.autoTimeLowerBound()=%s(%s)\n",
-                mCallback.autoTimeLowerBound(),
-                mCallback.autoTimeLowerBound().toEpochMilli());
+        ipw.println("mEnvironment.isAutoTimeDetectionEnabled()="
+                + mEnvironment.isAutoTimeDetectionEnabled());
+        ipw.println("mEnvironment.elapsedRealtimeMillis()=" + mEnvironment.elapsedRealtimeMillis());
+        ipw.println("mEnvironment.systemClockMillis()=" + mEnvironment.systemClockMillis());
+        ipw.println("mEnvironment.systemClockUpdateThresholdMillis()="
+                + mEnvironment.systemClockUpdateThresholdMillis());
+        ipw.printf("mEnvironment.autoTimeLowerBound()=%s(%s)\n",
+                mEnvironment.autoTimeLowerBound(),
+                mEnvironment.autoTimeLowerBound().toEpochMilli());
         String priorities =
-                Arrays.stream(mCallback.autoOriginPriorities())
+                Arrays.stream(mEnvironment.autoOriginPriorities())
                         .mapToObj(TimeDetectorStrategy::originToString)
                         .collect(joining(",", "[", "]"));
-        ipw.println("mCallback.autoOriginPriorities()=" + priorities);
+        ipw.println("mEnvironment.autoOriginPriorities()=" + priorities);
 
         ipw.println("Time change log:");
         ipw.increaseIndent(); // level 2
@@ -372,7 +373,7 @@
         }
 
         // We can validate the suggestion against the reference time clock.
-        long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+        long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
         if (elapsedRealtimeMillis < newUtcTime.getReferenceTimeMillis()) {
             // elapsedRealtime clock went backwards?
             Slog.w(LOG_TAG, "New reference time is in the future? Ignoring."
@@ -391,7 +392,7 @@
 
     private boolean validateSuggestionAgainstLowerBound(
             @NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion) {
-        Instant lowerBound = mCallback.autoTimeLowerBound();
+        Instant lowerBound = mEnvironment.autoTimeLowerBound();
 
         // Suggestion is definitely wrong if it comes before lower time bound.
         if (lowerBound.isAfter(Instant.ofEpochMilli(newUtcTime.getValue()))) {
@@ -405,13 +406,13 @@
 
     @GuardedBy("this")
     private void doAutoTimeDetection(@NonNull String detectionReason) {
-        if (!mCallback.isAutoTimeDetectionEnabled()) {
+        if (!mEnvironment.isAutoTimeDetectionEnabled()) {
             // Avoid doing unnecessary work with this (race-prone) check.
             return;
         }
 
         // Try the different origins one at a time.
-        int[] originPriorities = mCallback.autoOriginPriorities();
+        int[] originPriorities = mEnvironment.autoOriginPriorities();
         for (int origin : originPriorities) {
             TimestampedValue<Long> newUtcTime = null;
             String cause = null;
@@ -470,7 +471,7 @@
     @GuardedBy("this")
     @Nullable
     private TelephonyTimeSuggestion findBestTelephonySuggestion() {
-        long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+        long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
 
         // Telephony time suggestions are assumed to be derived from NITZ or NITZ-like signals.
         // These have a number of limitations:
@@ -579,7 +580,7 @@
         }
 
         TimestampedValue<Long> utcTime = networkSuggestion.getUtcTime();
-        long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis();
+        long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
         if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) {
             // The latest suggestion is not valid, usually due to its age.
             return null;
@@ -599,7 +600,7 @@
         }
 
         TimestampedValue<Long> utcTime = gnssTimeSuggestion.getUtcTime();
-        long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis();
+        long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
         if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) {
             // The latest suggestion is not valid, usually due to its age.
             return null;
@@ -619,7 +620,7 @@
         }
 
         TimestampedValue<Long> utcTime = externalTimeSuggestion.getUtcTime();
-        long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis();
+        long elapsedRealTimeMillis = mEnvironment.elapsedRealtimeMillis();
         if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) {
             // The latest suggestion is not valid, usually due to its age.
             return null;
@@ -634,7 +635,7 @@
 
         boolean isOriginAutomatic = isOriginAutomatic(origin);
         if (isOriginAutomatic) {
-            if (!mCallback.isAutoTimeDetectionEnabled()) {
+            if (!mEnvironment.isAutoTimeDetectionEnabled()) {
                 if (DBG) {
                     Slog.d(LOG_TAG, "Auto time detection is not enabled."
                             + " origin=" + originToString(origin)
@@ -644,7 +645,7 @@
                 return false;
             }
         } else {
-            if (mCallback.isAutoTimeDetectionEnabled()) {
+            if (mEnvironment.isAutoTimeDetectionEnabled()) {
                 if (DBG) {
                     Slog.d(LOG_TAG, "Auto time detection is enabled."
                             + " origin=" + originToString(origin)
@@ -655,11 +656,11 @@
             }
         }
 
-        mCallback.acquireWakeLock();
+        mEnvironment.acquireWakeLock();
         try {
             return setSystemClockUnderWakeLock(origin, time, cause);
         } finally {
-            mCallback.releaseWakeLock();
+            mEnvironment.releaseWakeLock();
         }
     }
 
@@ -671,9 +672,9 @@
     private boolean setSystemClockUnderWakeLock(
             @Origin int origin, @NonNull TimestampedValue<Long> newTime, @NonNull String cause) {
 
-        long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis();
+        long elapsedRealtimeMillis = mEnvironment.elapsedRealtimeMillis();
         boolean isOriginAutomatic = isOriginAutomatic(origin);
-        long actualSystemClockMillis = mCallback.systemClockMillis();
+        long actualSystemClockMillis = mEnvironment.systemClockMillis();
         if (isOriginAutomatic) {
             // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else
             // may be setting the clock.
@@ -701,7 +702,7 @@
         // Check if the new signal would make sufficient difference to the system clock. If it's
         // below the threshold then ignore it.
         long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis);
-        long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis();
+        long systemClockUpdateThreshold = mEnvironment.systemClockUpdateThresholdMillis();
         if (absTimeDifference < systemClockUpdateThreshold) {
             if (DBG) {
                 Slog.d(LOG_TAG, "Not setting system clock. New time and"
@@ -715,7 +716,7 @@
             return true;
         }
 
-        mCallback.setSystemClock(newSystemClockMillis);
+        mEnvironment.setSystemClock(newSystemClockMillis);
         String logMsg = "Set system clock using time=" + newTime
                 + " cause=" + cause
                 + " elapsedRealtimeMillis=" + elapsedRealtimeMillis
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
similarity index 78%
rename from services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
rename to services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 1357608..f52b9b1 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -33,43 +33,52 @@
 import android.location.LocationManager;
 import android.net.ConnectivityManager;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
+import com.android.server.timedetector.DeviceConfig;
 
 import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
- * The real implementation of {@link TimeZoneDetectorStrategyImpl.Callback}.
+ * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
  */
-public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
+public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment {
 
-    private static final String LOG_TAG = "TimeZoneDetectorCallbackImpl";
+    private static final String LOG_TAG = TimeZoneDetectorService.TAG;
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
     @NonNull private final Context mContext;
     @NonNull private final Handler mHandler;
     @NonNull private final ContentResolver mCr;
     @NonNull private final UserManager mUserManager;
-    @NonNull private final boolean mGeoDetectionFeatureEnabled;
+    @NonNull private final DeviceConfig mDeviceConfig;
+    @NonNull private final boolean mGeoDetectionSupported;
     @NonNull private final LocationManager mLocationManager;
+
     // @NonNull after setConfigChangeListener() is called.
+    @GuardedBy("this")
     private ConfigurationChangeListener mConfigChangeListener;
 
-    TimeZoneDetectorCallbackImpl(@NonNull Context context, @NonNull Handler handler,
-            boolean geoDetectionFeatureEnabled) {
+    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
+            @NonNull DeviceConfig deviceConfig, boolean geoDetectionSupported) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
+        Executor handlerExecutor = new HandlerExecutor(mHandler);
         mCr = context.getContentResolver();
         mUserManager = context.getSystemService(UserManager.class);
         mLocationManager = context.getSystemService(LocationManager.class);
-        mGeoDetectionFeatureEnabled = geoDetectionFeatureEnabled;
+        mDeviceConfig = deviceConfig;
+        mGeoDetectionSupported = geoDetectionSupported;
 
-        // Wire up the change listener. All invocations are performed on the mHandler thread.
+        // Wire up the change listeners. All invocations are performed on the mHandler thread.
 
         // Listen for the user changing / the user's location mode changing.
         IntentFilter filter = new IntentFilter();
@@ -103,18 +112,29 @@
                         handleConfigChangeOnHandlerThread();
                     }
                 }, UserHandle.USER_ALL);
+
+        // Add async callbacks for changes to server-side flags: some of the flags affect device /
+        // user config. All changes can be treated like a config change. If flags that affect config
+        // haven't changed then call will be a no-op.
+        mDeviceConfig.addListener(
+                handlerExecutor,
+                this::handleConfigChangeOnHandlerThread);
     }
 
     private void handleConfigChangeOnHandlerThread() {
-        if (mConfigChangeListener == null) {
-            Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
+        synchronized (this) {
+            if (mConfigChangeListener == null) {
+                Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
+            }
+            mConfigChangeListener.onChange();
         }
-        mConfigChangeListener.onChange();
     }
 
     @Override
     public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
-        mConfigChangeListener = Objects.requireNonNull(listener);
+        synchronized (this) {
+            mConfigChangeListener = Objects.requireNonNull(listener);
+        }
     }
 
     @Override
@@ -174,7 +194,10 @@
             // time zone detection: if we wrote it down then we'd set the value explicitly, which
             // would prevent detecting "default" later. That might influence what happens on later
             // releases that support geo detection on the same hardware.
-            if (isGeoDetectionSupported()) {
+            // Also avoid writing the geo detection enabled setting for devices that are currently
+            // force-enabled: otherwise we might overwrite a droidfood user's real setting
+            // permanently.
+            if (isGeoDetectionSupported() && !isGeoDetectionForceEnabled()) {
                 final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled();
                 setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled);
             }
@@ -191,7 +214,7 @@
     }
 
     private boolean isGeoDetectionSupported() {
-        return mGeoDetectionFeatureEnabled;
+        return mGeoDetectionSupported;
     }
 
     private boolean isAutoDetectionEnabled() {
@@ -213,12 +236,25 @@
     }
 
     private boolean isGeoDetectionEnabled(@UserIdInt int userId) {
-        final boolean geoDetectionEnabledByDefault = false;
+        // We may never use this, but it gives us a way to force location-based time zone detection
+        // on for testers (where their other settings allow).
+        boolean forceEnabled = isGeoDetectionForceEnabled();
+        if (forceEnabled) {
+            return true;
+        }
+
+        final boolean geoDetectionEnabledByDefault = mDeviceConfig.getBoolean(
+                DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, false);
         return Settings.Secure.getIntForUser(mCr,
                 Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
                 (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0;
     }
 
+    private boolean isGeoDetectionForceEnabled() {
+        return mDeviceConfig.getBoolean(
+                DeviceConfig.KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, false);
+    }
+
     private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) {
         // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500
         if (isGeoDetectionEnabled(userId) != enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
index b63df05..4eb1b99 100644
--- a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
+++ b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
@@ -19,8 +19,11 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.os.TimestampedValue;
 import android.util.IndentingPrintWriter;
 
+import java.time.Duration;
 import java.util.ArrayDeque;
 
 /**
@@ -49,18 +52,15 @@
  */
 public final class ReferenceWithHistory<V> {
 
-    private static final Object NULL_MARKER = "{null marker}";
-
     /** The maximum number of references to store. */
     private final int mMaxHistorySize;
 
-    /**
-     * The history storage. Note that ArrayDeque doesn't support {@code null} so this stores Object
-     * and not V. Use {@link #packNullIfRequired(Object)} and {@link #unpackNullIfRequired(Object)}
-     * to convert to / from the storage object.
-     */
+    /** The number of times {@link #set(Object)} has been called. */
+    private int mSetCount;
+
+    /** The history storage. */
     @Nullable
-    private ArrayDeque<Object> mValues;
+    private ArrayDeque<TimestampedValue<V>> mValues;
 
     /**
      * Creates an instance that records, at most, the specified number of values.
@@ -78,8 +78,8 @@
         if (mValues == null || mValues.isEmpty()) {
             return null;
         }
-        Object value = mValues.getFirst();
-        return unpackNullIfRequired(value);
+        TimestampedValue<V> valueHolder = mValues.getFirst();
+        return valueHolder.getValue();
     }
 
     /**
@@ -98,8 +98,10 @@
 
         V previous = get();
 
-        Object nullSafeValue = packNullIfRequired(newValue);
-        mValues.addFirst(nullSafeValue);
+        TimestampedValue<V> valueHolder =
+                new TimestampedValue<>(SystemClock.elapsedRealtime(), newValue);
+        mValues.addFirst(valueHolder);
+        mSetCount++;
         return previous;
     }
 
@@ -110,10 +112,13 @@
         if (mValues == null) {
             ipw.println("{Empty}");
         } else {
-            int i = 0;
-            for (Object value : mValues) {
-                ipw.println(i + ": " + unpackNullIfRequired(value));
-                i++;
+            int i = mSetCount;
+            for (TimestampedValue<V> valueHolder : mValues) {
+                ipw.print(--i);
+                ipw.print("@");
+                ipw.print(Duration.ofMillis(valueHolder.getReferenceTimeMillis()).toString());
+                ipw.print(": ");
+                ipw.println(valueHolder.getValue());
             }
         }
         ipw.flush();
@@ -130,23 +135,4 @@
     public String toString() {
         return String.valueOf(get());
     }
-
-    /**
-     * Turns a non-nullable Object into a nullable value. See also
-     * {@link #packNullIfRequired(Object)}.
-     */
-    @SuppressWarnings("unchecked")
-    @Nullable
-    private V unpackNullIfRequired(@NonNull Object value) {
-        return value == NULL_MARKER ? null : (V) value;
-    }
-
-    /**
-     * Turns a nullable value into a non-nullable Object. See also
-     * {@link #unpackNullIfRequired(Object)}.
-     */
-    @NonNull
-    private Object packNullIfRequired(@Nullable V value) {
-        return value == null ? NULL_MARKER : value;
-    }
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 2ead3be..bd71ddf 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -59,7 +59,7 @@
 public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
         implements IBinder.DeathRecipient {
 
-    private static final String TAG = "TimeZoneDetectorService";
+    static final String TAG = "time_zone_detector";
 
     /**
      * A "feature switch" for location-based time zone detection. If this is {@code false}. It is
@@ -67,19 +67,19 @@
      * is important.
      */
     @Nullable
-    private static Boolean sGeoLocationTimeZoneDetectionEnabled;
+    private static Boolean sGeoLocationTimeZoneDetectionSupported;
 
     /** Returns {@code true} if the location-based time zone detection feature is enabled. */
-    public static boolean isGeoLocationTimeZoneDetectionEnabled(Context context) {
-        if (sGeoLocationTimeZoneDetectionEnabled == null) {
+    public static boolean isGeoLocationTimeZoneDetectionSupported(Context context) {
+        if (sGeoLocationTimeZoneDetectionSupported == null) {
             // The config value is expected to be the main switch. Platform developers can also
             // enable the feature using a persistent system property.
-            sGeoLocationTimeZoneDetectionEnabled = context.getResources().getBoolean(
+            sGeoLocationTimeZoneDetectionSupported = context.getResources().getBoolean(
                     com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection)
                     || SystemProperties.getBoolean(
                             "persist.sys.location_time_zone_detection_feature_enabled", false);
         }
-        return sGeoLocationTimeZoneDetectionEnabled;
+        return sGeoLocationTimeZoneDetectionSupported;
     }
 
     /**
@@ -98,11 +98,11 @@
             Context context = getContext();
             Handler handler = FgThread.getHandler();
 
-            boolean geolocationTimeZoneDetectionEnabled =
-                    isGeoLocationTimeZoneDetectionEnabled(context);
+            boolean geolocationTimeZoneDetectionSupported =
+                    isGeoLocationTimeZoneDetectionSupported(context);
             TimeZoneDetectorStrategy timeZoneDetectorStrategy =
                     TimeZoneDetectorStrategyImpl.create(
-                            context, handler, geolocationTimeZoneDetectionEnabled);
+                            context, handler, geolocationTimeZoneDetectionSupported);
 
             // Create and publish the local service for use by internal callers.
             TimeZoneDetectorInternal internal =
@@ -330,7 +330,7 @@
     boolean isGeoTimeZoneDetectionSupported() {
         enforceManageTimeZoneDetectorPermission();
 
-        return isGeoLocationTimeZoneDetectionEnabled(mContext);
+        return isGeoLocationTimeZoneDetectionSupported(mContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 781668b..c464b74 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -38,6 +38,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.timedetector.DeviceConfig;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -59,7 +60,7 @@
      * conditions.
      */
     @VisibleForTesting
-    public interface Callback {
+    public interface Environment {
 
         /**
          * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
@@ -97,7 +98,7 @@
         void storeConfiguration(@UserIdInt int userId, TimeZoneConfiguration newConfiguration);
     }
 
-    private static final String LOG_TAG = "TimeZoneDetectorStrategy";
+    private static final String LOG_TAG = TimeZoneDetectorService.TAG;
     private static final boolean DBG = false;
 
     /**
@@ -164,7 +165,7 @@
     private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10;
 
     @NonNull
-    private final Callback mCallback;
+    private final Environment mEnvironment;
 
     @GuardedBy("this")
     @NonNull
@@ -203,17 +204,18 @@
      */
     public static TimeZoneDetectorStrategyImpl create(
             @NonNull Context context, @NonNull Handler handler,
-            boolean geolocationTimeZoneDetectionEnabled) {
+            boolean geoDetectionSupported) {
 
-        TimeZoneDetectorCallbackImpl callback = new TimeZoneDetectorCallbackImpl(
-                context, handler, geolocationTimeZoneDetectionEnabled);
-        return new TimeZoneDetectorStrategyImpl(callback);
+        DeviceConfig deviceConfig = new DeviceConfig();
+        EnvironmentImpl environment = new EnvironmentImpl(
+                context, handler, deviceConfig, geoDetectionSupported);
+        return new TimeZoneDetectorStrategyImpl(environment);
     }
 
     @VisibleForTesting
-    public TimeZoneDetectorStrategyImpl(@NonNull Callback callback) {
-        mCallback = Objects.requireNonNull(callback);
-        mCallback.setConfigChangeListener(this::handleConfigChanged);
+    public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
+        mEnvironment = Objects.requireNonNull(environment);
+        mEnvironment.setConfigChangeListener(this::handleConfigChanged);
     }
 
     /**
@@ -230,13 +232,13 @@
     @Override
     @NonNull
     public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
-        return mCallback.getConfigurationInternal(userId);
+        return mEnvironment.getConfigurationInternal(userId);
     }
 
     @Override
     @NonNull
     public synchronized ConfigurationInternal getCurrentUserConfigurationInternal() {
-        int currentUserId = mCallback.getCurrentUserId();
+        int currentUserId = mEnvironment.getCurrentUserId();
         return getConfigurationInternal(currentUserId);
     }
 
@@ -257,9 +259,9 @@
             return false;
         }
 
-        // Store the configuration / notify as needed. This will cause the mCallback to invoke
+        // Store the configuration / notify as needed. This will cause the mEnvironment to invoke
         // handleConfigChanged() asynchronously.
-        mCallback.storeConfiguration(userId, newConfiguration);
+        mEnvironment.storeConfiguration(userId, newConfiguration);
 
         String logMsg = "Configuration changed:"
                 + " oldConfiguration=" + oldConfiguration
@@ -275,8 +277,9 @@
     public synchronized void suggestGeolocationTimeZone(
             @NonNull GeolocationTimeZoneSuggestion suggestion) {
 
-        int currentUserId = mCallback.getCurrentUserId();
-        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+        int currentUserId = mEnvironment.getCurrentUserId();
+        ConfigurationInternal currentUserConfig =
+                mEnvironment.getConfigurationInternal(currentUserId);
         if (DBG) {
             Slog.d(LOG_TAG, "Geolocation suggestion received."
                     + " currentUserConfig=" + currentUserConfig
@@ -299,7 +302,7 @@
     public synchronized boolean suggestManualTimeZone(
             @UserIdInt int userId, @NonNull ManualTimeZoneSuggestion suggestion) {
 
-        int currentUserId = mCallback.getCurrentUserId();
+        int currentUserId = mEnvironment.getCurrentUserId();
         if (userId != currentUserId) {
             Slog.w(LOG_TAG, "Manual suggestion received but user != current user, userId=" + userId
                     + " suggestion=" + suggestion);
@@ -332,8 +335,9 @@
     public synchronized void suggestTelephonyTimeZone(
             @NonNull TelephonyTimeZoneSuggestion suggestion) {
 
-        int currentUserId = mCallback.getCurrentUserId();
-        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+        int currentUserId = mEnvironment.getCurrentUserId();
+        ConfigurationInternal currentUserConfig =
+                mEnvironment.getConfigurationInternal(currentUserId);
         if (DBG) {
             Slog.d(LOG_TAG, "Telephony suggestion received. currentUserConfig=" + currentUserConfig
                     + " newSuggestion=" + suggestion);
@@ -423,7 +427,7 @@
         String zoneId;
 
         // Introduce bias towards the device's current zone when there are multiple zone suggested.
-        String deviceTimeZone = mCallback.getDeviceTimeZone();
+        String deviceTimeZone = mEnvironment.getDeviceTimeZone();
         if (zoneIds.contains(deviceTimeZone)) {
             if (DBG) {
                 Slog.d(LOG_TAG,
@@ -486,7 +490,7 @@
 
     @GuardedBy("this")
     private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
-        String currentZoneId = mCallback.getDeviceTimeZone();
+        String currentZoneId = mEnvironment.getDeviceTimeZone();
 
         // Avoid unnecessary changes / intents.
         if (newZoneId.equals(currentZoneId)) {
@@ -501,7 +505,7 @@
             return;
         }
 
-        mCallback.setDeviceTimeZone(newZoneId);
+        mEnvironment.setDeviceTimeZone(newZoneId);
         String msg = "Set device time zone."
                 + ", currentZoneId=" + currentZoneId
                 + ", newZoneId=" + newZoneId
@@ -572,8 +576,9 @@
         // This method is called whenever the user changes or the config for any user changes. We
         // don't know what happened, so we capture the current user's config, check to see if we
         // need to clear state associated with a previous user, and rerun detection.
-        int currentUserId = mCallback.getCurrentUserId();
-        ConfigurationInternal currentUserConfig = mCallback.getConfigurationInternal(currentUserId);
+        int currentUserId = mEnvironment.getCurrentUserId();
+        ConfigurationInternal currentUserConfig =
+                mEnvironment.getConfigurationInternal(currentUserId);
 
         GeolocationTimeZoneSuggestion latestGeoLocationSuggestion =
                 mLatestGeoLocationSuggestion.get();
@@ -603,14 +608,14 @@
         ipw.println("TimeZoneDetectorStrategy:");
 
         ipw.increaseIndent(); // level 1
-        int currentUserId = mCallback.getCurrentUserId();
-        ipw.println("mCallback.getCurrentUserId()=" + currentUserId);
-        ConfigurationInternal configuration = mCallback.getConfigurationInternal(currentUserId);
-        ipw.println("mCallback.getConfiguration(currentUserId)=" + configuration);
+        int currentUserId = mEnvironment.getCurrentUserId();
+        ipw.println("mEnvironment.getCurrentUserId()=" + currentUserId);
+        ConfigurationInternal configuration = mEnvironment.getConfigurationInternal(currentUserId);
+        ipw.println("mEnvironment.getConfiguration(currentUserId)=" + configuration);
         ipw.println("[Capabilities=" + configuration.createCapabilitiesAndConfig() + "]");
-        ipw.println("mCallback.isDeviceTimeZoneInitialized()="
-                + mCallback.isDeviceTimeZoneInitialized());
-        ipw.println("mCallback.getDeviceTimeZone()=" + mCallback.getDeviceTimeZone());
+        ipw.println("mEnvironment.isDeviceTimeZoneInitialized()="
+                + mEnvironment.isDeviceTimeZoneInitialized());
+        ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
 
         ipw.println("Time zone change log:");
         ipw.increaseIndent(); // level 2
diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
similarity index 89%
rename from services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
rename to services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index 210fb5c..c0c9e6d 100644
--- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
-import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java
similarity index 96%
rename from services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java
rename to services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java
index cd9aa2f..46eaad0 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.NonNull;
 
diff --git a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
similarity index 68%
rename from services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
rename to services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
index d896f6e..a54288f 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.NonNull;
 
 import com.android.server.LocalServices;
+import com.android.server.timedetector.DeviceConfig;
 import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.TimeZoneDetectorInternal;
@@ -32,18 +33,22 @@
  */
 class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment {
 
-    private static final Duration PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
-    private static final Duration PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
-    private static final Duration PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
+    private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
+    private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ =
+            Duration.ofMinutes(1);
+    private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5);
 
     @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal;
     @NonNull private final LocationTimeZoneProviderController mController;
+    @NonNull private final DeviceConfig mDeviceConfig;
     @NonNull private final ConfigurationChangeListener mConfigurationChangeListener;
 
     ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
+            @NonNull DeviceConfig deviceConfig,
             @NonNull LocationTimeZoneProviderController controller) {
         super(threadingDomain);
         mController = Objects.requireNonNull(controller);
+        mDeviceConfig = Objects.requireNonNull(deviceConfig);
         mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class);
 
         // Listen for configuration changes.
@@ -66,18 +71,24 @@
     @Override
     @NonNull
     Duration getProviderInitializationTimeout() {
-        return PROVIDER_INITIALIZATION_TIMEOUT;
+        return mDeviceConfig.getDurationFromMillis(
+                DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS,
+                DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT);
     }
 
     @Override
     @NonNull
     Duration getProviderInitializationTimeoutFuzz() {
-        return PROVIDER_INITIALIZATION_TIMEOUT_FUZZ;
+        return mDeviceConfig.getDurationFromMillis(
+                DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+                DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ);
     }
 
     @Override
     @NonNull
     Duration getUncertaintyDelay() {
-        return PROVIDER_UNCERTAINTY_DELAY;
+        return mDeviceConfig.getDurationFromMillis(
+                DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS,
+                DEFAULT_PROVIDER_UNCERTAINTY_DELAY);
     }
 }
diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java
similarity index 94%
rename from services/core/java/com/android/server/location/timezone/ControllerImpl.java
rename to services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java
index 0d284fc..fb2a184 100644
--- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java
@@ -14,20 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
-import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
-import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
-import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
-import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
-import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
+import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
+import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
 
 import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
@@ -36,9 +36,9 @@
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
 
 import java.time.Duration;
 import java.util.List;
diff --git a/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java b/services/core/java/com/android/server/timezonedetector/location/HandlerThreadingDomain.java
similarity index 98%
rename from services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java
rename to services/core/java/com/android/server/timezonedetector/location/HandlerThreadingDomain.java
index 3055ff8..0dd2922 100644
--- a/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java
+++ b/services/core/java/com/android/server/timezonedetector/location/HandlerThreadingDomain.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
similarity index 95%
rename from services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index 54535eb..364eaf8 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
 import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED;
@@ -43,6 +43,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.FgThread;
 import com.android.server.SystemService;
+import com.android.server.timedetector.DeviceConfig;
 import com.android.server.timezonedetector.TimeZoneDetectorInternal;
 import com.android.server.timezonedetector.TimeZoneDetectorService;
 
@@ -74,11 +75,17 @@
  * mode" where the real binder clients are replaced by {@link
  * SimulatedLocationTimeZoneProviderProxy}. This means that the real client providers are never
  * bound (ensuring no real location events will be received) and simulated events / behaviors
- * can be injected via the command line. To enter simulation mode for a provider, use
- * "{@code adb shell setprop persist.sys.location_tz_simulation_mode.<provider name> 1}" and reboot.
- * e.g. "{@code adb shell setprop persist.sys.location_tz_simulation_mode.primary 1}}"
- * Then use "{@code adb shell cmd location_time_zone_manager help}" for injection. Set the system
- * properties to "0" and reboot to return to exit simulation mode.
+ * can be injected via the command line.
+ *
+ * <p>To enter simulation mode for a provider, use {@code adb shell cmd location_time_zone_manager
+ * set_provider_mode_override &lt;provider name&gt; simulated} and restart the service with {@code
+ * adb shell cmd location_time_zone_manager stop} and {@code adb shell cmd
+ * location_time_zone_manager start}.
+ *
+ * <p>e.g. {@code adb shell cmd location_time_zone_manager set_provider_mode_override primary
+ * simulated}.
+ *
+ * <p>See {@code adb shell cmd location_time_zone_manager help}" for more options.
  */
 public class LocationTimeZoneManagerService extends Binder {
 
@@ -96,7 +103,7 @@
         @Override
         public void onStart() {
             Context context = getContext();
-            if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionEnabled(context)) {
+            if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) {
                 mService = new LocationTimeZoneManagerService(context);
 
                 // The service currently exposes no LocalService or Binder API, but it extends
@@ -110,7 +117,7 @@
         @Override
         public void onBootPhase(int phase) {
             Context context = getContext();
-            if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionEnabled(context)) {
+            if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) {
                 if (phase == PHASE_SYSTEM_SERVICES_READY) {
                     // The location service must be functioning after this boot phase.
                     mService.onSystemReady();
@@ -221,10 +228,10 @@
                     LocationTimeZoneProvider secondary = createSecondaryProvider();
                     mLocationTimeZoneDetectorController =
                             new ControllerImpl(mThreadingDomain, primary, secondary);
-                    ControllerCallbackImpl callback = new ControllerCallbackImpl(
-                            mThreadingDomain);
+                    DeviceConfig deviceConfig = new DeviceConfig();
                     mEnvironment = new ControllerEnvironmentImpl(
-                            mThreadingDomain, mLocationTimeZoneDetectorController);
+                            mThreadingDomain, deviceConfig, mLocationTimeZoneDetectorController);
+                    ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain);
                     mLocationTimeZoneDetectorController.initialize(mEnvironment, callback);
                 }
             }
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
similarity index 95%
rename from services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
index b1dd55f..113926a 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
-import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
similarity index 92%
rename from services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
index 6f9863c..b53150c 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
 import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME;
@@ -28,13 +28,13 @@
 import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_START;
 import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_STOP;
 
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN;
 
 import android.annotation.NonNull;
 import android.app.time.GeolocationTimeZoneSuggestionProto;
@@ -47,8 +47,8 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.dump.DualDumpOutputStream;
-import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
similarity index 94%
rename from services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index 9a7b775..ef2f357 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -14,22 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
 
-import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog;
-import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
-import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
-import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
-import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
+import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
+import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
@@ -42,11 +42,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
-import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.Dumpable;
 import com.android.server.timezonedetector.ReferenceWithHistory;
+import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
+import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
 
 import java.time.Duration;
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
similarity index 97%
rename from services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
index ec2bc13..b4aff3e 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
 import android.os.Handler;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.Dumpable;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
 
 import java.time.Duration;
 import java.util.Objects;
diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java
similarity index 98%
rename from services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
rename to services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java
index 8368b5e..43b1b5f0 100644
--- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
similarity index 98%
rename from services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProviderProxy.java
rename to services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
index c2abbf9..1f45e82 100644
--- a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
similarity index 98%
rename from services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
rename to services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
index 0904ba4..38211ef 100644
--- a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
 
-import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog;
+import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
 
 import android.Manifest;
 import android.annotation.NonNull;
diff --git a/services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/SimulatedLocationTimeZoneProviderProxy.java
similarity index 99%
rename from services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.java
rename to services/core/java/com/android/server/timezonedetector/location/SimulatedLocationTimeZoneProviderProxy.java
index 66ccaed..02b0a84 100644
--- a/services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/SimulatedLocationTimeZoneProviderProxy.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND;
 import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_UNBIND;
diff --git a/services/core/java/com/android/server/location/timezone/TestCommand.java b/services/core/java/com/android/server/timezonedetector/location/TestCommand.java
similarity index 98%
rename from services/core/java/com/android/server/location/timezone/TestCommand.java
rename to services/core/java/com/android/server/timezonedetector/location/TestCommand.java
index 0df3ca0..21482ea 100644
--- a/services/core/java/com/android/server/location/timezone/TestCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TestCommand.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.NonNull;
 import android.net.Uri;
diff --git a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java b/services/core/java/com/android/server/timezonedetector/location/ThreadingDomain.java
similarity index 98%
rename from services/core/java/com/android/server/location/timezone/ThreadingDomain.java
rename to services/core/java/com/android/server/timezonedetector/location/ThreadingDomain.java
index 4ada6f5..9e3497f 100644
--- a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ThreadingDomain.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
diff --git a/services/core/java/com/android/server/location/timezone/TimeZoneProviderEvent.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEvent.java
similarity index 98%
rename from services/core/java/com/android/server/location/timezone/TimeZoneProviderEvent.java
rename to services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEvent.java
index 2d6f8ad..3e224e0 100644
--- a/services/core/java/com/android/server/location/timezone/TimeZoneProviderEvent.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEvent.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
diff --git a/services/core/java/com/android/server/location/timezone/TimeZoneProviderRequest.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java
similarity index 98%
rename from services/core/java/com/android/server/location/timezone/TimeZoneProviderRequest.java
rename to services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java
index 649a74b..1482031 100644
--- a/services/core/java/com/android/server/location/timezone/TimeZoneProviderRequest.java
+++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 38ae51f..83085cc 100755
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -977,7 +977,7 @@
             AudioPortConfig sourceConfig = mAudioSource.activeConfig();
             List<AudioPortConfig> sinkConfigs = new ArrayList<>();
             AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
-            boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
+            boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated || mAudioPatch == null;
 
             for (AudioDevicePort audioSink : mAudioSink) {
                 AudioPortConfig sinkConfig = audioSink.activeConfig();
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index e1feb5a..b6ddd93 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -18,13 +18,27 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilities.NetCapability;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
 import android.os.Handler;
 import android.os.ParcelUuid;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+
+import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Tracks a set of Networks underpinning a VcnGatewayConnection.
@@ -35,52 +49,401 @@
  *
  * @hide
  */
-public class UnderlyingNetworkTracker extends Handler {
+public class UnderlyingNetworkTracker {
     @NonNull private static final String TAG = UnderlyingNetworkTracker.class.getSimpleName();
 
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
+    @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities;
     @NonNull private final UnderlyingNetworkTrackerCallback mCb;
     @NonNull private final Dependencies mDeps;
+    @NonNull private final Handler mHandler;
+    @NonNull private final ConnectivityManager mConnectivityManager;
+
+    @NonNull private final Map<Integer, NetworkCallback> mCellBringupCallbacks = new ArrayMap<>();
+    @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback();
+    @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback();
+
+    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
+    private boolean mIsRunning = true;
+
+    @Nullable private UnderlyingNetworkRecord mCurrentRecord;
+    @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress;
 
     public UnderlyingNetworkTracker(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
             @NonNull UnderlyingNetworkTrackerCallback cb) {
-        this(vcnContext, subscriptionGroup, cb, new Dependencies());
+        this(
+                vcnContext,
+                subscriptionGroup,
+                snapshot,
+                requiredUnderlyingNetworkCapabilities,
+                cb,
+                new Dependencies());
     }
 
     private UnderlyingNetworkTracker(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities,
             @NonNull UnderlyingNetworkTrackerCallback cb,
             @NonNull Dependencies deps) {
-        super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
-        mVcnContext = vcnContext;
+        mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
+        mRequiredUnderlyingNetworkCapabilities =
+                Objects.requireNonNull(
+                        requiredUnderlyingNetworkCapabilities,
+                        "Missing requiredUnderlyingNetworkCapabilities");
         mCb = Objects.requireNonNull(cb, "Missing cb");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+        mHandler = new Handler(mVcnContext.getLooper());
+
+        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
+
+        registerNetworkRequests();
+    }
+
+    private void registerNetworkRequests() {
+        // register bringup requests for underlying Networks
+        mConnectivityManager.requestBackgroundNetwork(
+                getWifiNetworkRequest(), mHandler, mWifiBringupCallback);
+        updateSubIdsAndCellularRequests();
+
+        // register Network-selection request used to decide selected underlying Network
+        mConnectivityManager.requestBackgroundNetwork(
+                getNetworkRequestBase().build(), mHandler, mRouteSelectionCallback);
+    }
+
+    private NetworkRequest getWifiNetworkRequest() {
+        return getNetworkRequestBase().addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
+    }
+
+    private NetworkRequest getCellNetworkRequestForSubId(int subId) {
+        return getNetworkRequestBase()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
+                .build();
+    }
+
+    private NetworkRequest.Builder getNetworkRequestBase() {
+        NetworkRequest.Builder requestBase = new NetworkRequest.Builder();
+        for (@NetCapability int capability : mRequiredUnderlyingNetworkCapabilities) {
+            requestBase.addCapability(capability);
+        }
+
+        return requestBase
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+    }
+
+    /**
+     * Update the current subIds and Cellular bringup requests for this UnderlyingNetworkTracker.
+     */
+    private void updateSubIdsAndCellularRequests() {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        // Don't bother re-filing NetworkRequests if this Tracker has been torn down.
+        if (!mIsRunning) {
+            return;
+        }
+
+        final Set<Integer> subIdsInSubGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
+
+        // new subIds to track = (updated list of subIds) - (currently tracked subIds)
+        final Set<Integer> subIdsToRegister = new ArraySet<>(subIdsInSubGroup);
+        subIdsToRegister.removeAll(mCellBringupCallbacks.keySet());
+
+        // subIds to stop tracking = (currently tracked subIds) - (updated list of subIds)
+        final Set<Integer> subIdsToUnregister = new ArraySet<>(mCellBringupCallbacks.keySet());
+        subIdsToUnregister.removeAll(subIdsInSubGroup);
+
+        for (final int subId : subIdsToRegister) {
+            final NetworkBringupCallback cb = new NetworkBringupCallback();
+            mCellBringupCallbacks.put(subId, cb);
+
+            mConnectivityManager.requestBackgroundNetwork(
+                    getCellNetworkRequestForSubId(subId), mHandler, cb);
+        }
+
+        for (final int subId : subIdsToUnregister) {
+            final NetworkCallback cb = mCellBringupCallbacks.remove(subId);
+            mConnectivityManager.unregisterNetworkCallback(cb);
+        }
+    }
+
+    /**
+     * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot.
+     *
+     * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to
+     * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered
+     * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change.
+     */
+    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        Objects.requireNonNull(snapshot, "Missing snapshot");
+
+        mLastSnapshot = snapshot;
+        updateSubIdsAndCellularRequests();
     }
 
     /** Tears down this Tracker, and releases all underlying network requests. */
-    public void teardown() {}
+    public void teardown() {
+        mVcnContext.ensureRunningOnLooperThread();
 
-    /** An record of a single underlying network, caching relevant fields. */
+        mConnectivityManager.unregisterNetworkCallback(mWifiBringupCallback);
+        mConnectivityManager.unregisterNetworkCallback(mRouteSelectionCallback);
+
+        for (final NetworkCallback cb : mCellBringupCallbacks.values()) {
+            mConnectivityManager.unregisterNetworkCallback(cb);
+        }
+        mCellBringupCallbacks.clear();
+
+        mIsRunning = false;
+    }
+
+    /** Returns whether the currently selected Network matches the given network. */
+    private static boolean isSameNetwork(
+            @Nullable UnderlyingNetworkRecord.Builder recordInProgress, @NonNull Network network) {
+        return recordInProgress != null && recordInProgress.getNetwork().equals(network);
+    }
+
+    /** Notify the Callback if a full UnderlyingNetworkRecord exists. */
+    private void maybeNotifyCallback() {
+        // Only forward this update if a complete record has been received
+        if (!mRecordInProgress.isValid()) {
+            return;
+        }
+
+        // Only forward this update if the updated record differs form the current record
+        UnderlyingNetworkRecord updatedRecord = mRecordInProgress.build();
+        if (!updatedRecord.equals(mCurrentRecord)) {
+            mCurrentRecord = updatedRecord;
+
+            mCb.onSelectedUnderlyingNetworkChanged(mCurrentRecord);
+        }
+    }
+
+    private void handleNetworkAvailable(@NonNull Network network) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        mRecordInProgress = new UnderlyingNetworkRecord.Builder(network);
+    }
+
+    private void handleNetworkLost(@NonNull Network network) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Non-underlying Network lost");
+            return;
+        }
+
+        mRecordInProgress = null;
+        mCurrentRecord = null;
+        mCb.onSelectedUnderlyingNetworkChanged(null /* underlyingNetworkRecord */);
+    }
+
+    private void handleCapabilitiesChanged(
+            @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Invalid update to NetworkCapabilities");
+            return;
+        }
+
+        mRecordInProgress.setNetworkCapabilities(networkCapabilities);
+
+        maybeNotifyCallback();
+    }
+
+    private void handleNetworkSuspended(@NonNull Network network, boolean isSuspended) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Invalid update to isSuspended");
+            return;
+        }
+
+        final NetworkCapabilities newCaps =
+                new NetworkCapabilities(mRecordInProgress.getNetworkCapabilities());
+        if (isSuspended) {
+            newCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+        } else {
+            newCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
+        }
+
+        handleCapabilitiesChanged(network, newCaps);
+    }
+
+    private void handlePropertiesChanged(
+            @NonNull Network network, @NonNull LinkProperties linkProperties) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Invalid update to LinkProperties");
+            return;
+        }
+
+        mRecordInProgress.setLinkProperties(linkProperties);
+
+        maybeNotifyCallback();
+    }
+
+    private void handleNetworkBlocked(@NonNull Network network, boolean isBlocked) {
+        mVcnContext.ensureRunningOnLooperThread();
+
+        if (!isSameNetwork(mRecordInProgress, network)) {
+            Slog.wtf(TAG, "Invalid update to isBlocked");
+            return;
+        }
+
+        mRecordInProgress.setIsBlocked(isBlocked);
+
+        maybeNotifyCallback();
+    }
+
+    /**
+     * NetworkBringupCallback is used to keep background, VCN-managed Networks from being reaped.
+     *
+     * <p>NetworkBringupCallback only exists to prevent matching (VCN-managed) Networks from being
+     * reaped, and no action is taken on any events firing.
+     */
+    @VisibleForTesting
+    class NetworkBringupCallback extends NetworkCallback {}
+
+    /**
+     * RouteSelectionCallback is used to select the "best" underlying Network.
+     *
+     * <p>The "best" network is determined by ConnectivityService, which is treated as a source of
+     * truth.
+     */
+    @VisibleForTesting
+    class RouteSelectionCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            handleNetworkAvailable(network);
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            handleNetworkLost(network);
+        }
+
+        @Override
+        public void onCapabilitiesChanged(
+                @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+            handleCapabilitiesChanged(network, networkCapabilities);
+        }
+
+        @Override
+        public void onNetworkSuspended(@NonNull Network network) {
+            handleNetworkSuspended(network, true /* isSuspended */);
+        }
+
+        @Override
+        public void onNetworkResumed(@NonNull Network network) {
+            handleNetworkSuspended(network, false /* isSuspended */);
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(
+                @NonNull Network network, @NonNull LinkProperties linkProperties) {
+            handlePropertiesChanged(network, linkProperties);
+        }
+
+        @Override
+        public void onBlockedStatusChanged(@NonNull Network network, boolean isBlocked) {
+            handleNetworkBlocked(network, isBlocked);
+        }
+    }
+
+    /** A record of a single underlying network, caching relevant fields. */
     public static class UnderlyingNetworkRecord {
         @NonNull public final Network network;
         @NonNull public final NetworkCapabilities networkCapabilities;
         @NonNull public final LinkProperties linkProperties;
-        public final boolean blocked;
+        public final boolean isBlocked;
 
-        private UnderlyingNetworkRecord(
+        @VisibleForTesting(visibility = Visibility.PRIVATE)
+        UnderlyingNetworkRecord(
                 @NonNull Network network,
                 @NonNull NetworkCapabilities networkCapabilities,
                 @NonNull LinkProperties linkProperties,
-                boolean blocked) {
+                boolean isBlocked) {
             this.network = network;
             this.networkCapabilities = networkCapabilities;
             this.linkProperties = linkProperties;
-            this.blocked = blocked;
+            this.isBlocked = isBlocked;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof UnderlyingNetworkRecord)) return false;
+            final UnderlyingNetworkRecord that = (UnderlyingNetworkRecord) o;
+
+            return network.equals(that.network)
+                    && networkCapabilities.equals(that.networkCapabilities)
+                    && linkProperties.equals(that.linkProperties)
+                    && isBlocked == that.isBlocked;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(network, networkCapabilities, linkProperties, isBlocked);
+        }
+
+        /** Builder to incrementally construct an UnderlyingNetworkRecord. */
+        private static class Builder {
+            @NonNull private final Network mNetwork;
+
+            @Nullable private NetworkCapabilities mNetworkCapabilities;
+            @Nullable private LinkProperties mLinkProperties;
+            boolean mIsBlocked;
+            boolean mWasIsBlockedSet;
+
+            private Builder(@NonNull Network network) {
+                mNetwork = network;
+            }
+
+            @NonNull
+            private Network getNetwork() {
+                return mNetwork;
+            }
+
+            private void setNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+                mNetworkCapabilities = networkCapabilities;
+            }
+
+            @Nullable
+            private NetworkCapabilities getNetworkCapabilities() {
+                return mNetworkCapabilities;
+            }
+
+            private void setLinkProperties(@NonNull LinkProperties linkProperties) {
+                mLinkProperties = linkProperties;
+            }
+
+            private void setIsBlocked(boolean isBlocked) {
+                mIsBlocked = isBlocked;
+                mWasIsBlockedSet = true;
+            }
+
+            private boolean isValid() {
+                return mNetworkCapabilities != null && mLinkProperties != null && mWasIsBlockedSet;
+            }
+
+            private UnderlyingNetworkRecord build() {
+                return new UnderlyingNetworkRecord(
+                        mNetwork, mNetworkCapabilities, mLinkProperties, mIsBlocked);
+            }
         }
     }
 
@@ -91,9 +454,10 @@
          *
          * <p>This callback does NOT signal a mobility event.
          *
-         * @param underlying The details of the new underlying network
+         * @param underlyingNetworkRecord The details of the new underlying network
          */
-        void onSelectedUnderlyingNetworkChanged(@Nullable UnderlyingNetworkRecord underlying);
+        void onSelectedUnderlyingNetworkChanged(
+                @Nullable UnderlyingNetworkRecord underlyingNetworkRecord);
     }
 
     private static class Dependencies {}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index 9d21b92..5ec527a 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -27,9 +27,18 @@
 import android.os.ParcelUuid;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Represents an single instance of a VCN.
@@ -63,41 +72,86 @@
      */
     private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1;
 
+    /**
+     * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed.
+     *
+     * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections.
+     *
+     * @param obj TelephonySubscriptionSnapshot
+     */
+    private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2;
+
     /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */
     private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE;
 
+    /**
+     * Causes this VCN to immediately enter Safemode.
+     *
+     * <p>Upon entering Safemode, the VCN will unregister its RequestListener, tear down all of its
+     * VcnGatewayConnections, and notify VcnManagementService that it is in Safemode.
+     */
+    private static final int MSG_CMD_ENTER_SAFEMODE = MSG_CMD_BASE + 1;
+
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final Dependencies mDeps;
     @NonNull private final VcnNetworkRequestListener mRequestListener;
+    @NonNull private final VcnSafemodeCallback mVcnSafemodeCallback;
 
     @NonNull
     private final Map<VcnGatewayConnectionConfig, VcnGatewayConnection> mVcnGatewayConnections =
             new HashMap<>();
 
     @NonNull private VcnConfig mConfig;
+    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
 
-    private boolean mIsRunning = true;
+    /**
+     * Whether this Vcn instance is active and running.
+     *
+     * <p>The value will be {@code true} while running. It will be {@code false} if the VCN has been
+     * shut down or has entered safe mode.
+     *
+     * <p>This AtomicBoolean is required in order to ensure consistency and correctness across
+     * multiple threads. Unlike the rest of the Vcn, this is queried synchronously on Binder threads
+     * from VcnManagementService, and therefore cannot rely on guarantees of running on the VCN
+     * Looper.
+     */
+    // TODO(b/179429339): update when exiting safemode (when a new VcnConfig is provided)
+    private final AtomicBoolean mIsActive = new AtomicBoolean(true);
 
     public Vcn(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
-            @NonNull VcnConfig config) {
-        this(vcnContext, subscriptionGroup, config, new Dependencies());
+            @NonNull VcnConfig config,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull VcnSafemodeCallback vcnSafemodeCallback) {
+        this(
+                vcnContext,
+                subscriptionGroup,
+                config,
+                snapshot,
+                vcnSafemodeCallback,
+                new Dependencies());
     }
 
-    private Vcn(
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Vcn(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull VcnConfig config,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull VcnSafemodeCallback vcnSafemodeCallback,
             @NonNull Dependencies deps) {
         super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
         mVcnContext = vcnContext;
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
+        mVcnSafemodeCallback =
+                Objects.requireNonNull(vcnSafemodeCallback, "Missing vcnSafemodeCallback");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
         mRequestListener = new VcnNetworkRequestListener();
 
         mConfig = Objects.requireNonNull(config, "Missing config");
+        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
 
         // Register to receive cached and future NetworkRequests
         mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
@@ -110,11 +164,29 @@
         sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config));
     }
 
+    /** Asynchronously updates the Subscription snapshot for this VCN. */
+    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        Objects.requireNonNull(snapshot, "Missing snapshot");
+
+        sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot));
+    }
+
     /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */
     public void teardownAsynchronously() {
         sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN));
     }
 
+    /** Synchronously checks whether this Vcn is active. */
+    public boolean isActive() {
+        return mIsActive.get();
+    }
+
+    /** Get current Gateways for testing purposes */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public Set<VcnGatewayConnection> getVcnGatewayConnections() {
+        return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values()));
+    }
+
     private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener {
         @Override
         public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {
@@ -126,7 +198,7 @@
 
     @Override
     public void handleMessage(@NonNull Message msg) {
-        if (!mIsRunning) {
+        if (!isActive()) {
             return;
         }
 
@@ -137,9 +209,15 @@
             case MSG_EVENT_NETWORK_REQUESTED:
                 handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
                 break;
+            case MSG_EVENT_SUBSCRIPTIONS_CHANGED:
+                handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj);
+                break;
             case MSG_CMD_TEARDOWN:
                 handleTeardown();
                 break;
+            case MSG_CMD_ENTER_SAFEMODE:
+                handleEnterSafemode();
+                break;
             default:
                 Slog.wtf(getLogTag(), "Unknown msg.what: " + msg.what);
         }
@@ -161,15 +239,21 @@
             gatewayConnection.teardownAsynchronously();
         }
 
-        mIsRunning = false;
+        mIsActive.set(false);
+    }
+
+    private void handleEnterSafemode() {
+        handleTeardown();
+
+        mVcnSafemodeCallback.onEnteredSafemode();
     }
 
     private void handleNetworkRequested(
             @NonNull NetworkRequest request, int score, int providerId) {
         if (score > getNetworkScore()) {
             Slog.v(getLogTag(),
-                    "Request " + request.requestId + " already satisfied by higher-scoring ("
-                            + score + ") network from provider " + providerId);
+                    "Request already satisfied by higher-scoring (" + score + ") network from "
+                            + "provider " + providerId + ": " + request);
             return;
         }
 
@@ -177,8 +261,7 @@
         for (VcnGatewayConnectionConfig gatewayConnectionConfig : mVcnGatewayConnections.keySet()) {
             if (requestSatisfiedByGatewayConnectionConfig(request, gatewayConnectionConfig)) {
                 Slog.v(getLogTag(),
-                        "Request " + request.requestId
-                                + " satisfied by existing VcnGatewayConnection");
+                        "Request already satisfied by existing VcnGatewayConnection: " + request);
                 return;
             }
         }
@@ -193,21 +276,35 @@
                         "Bringing up new VcnGatewayConnection for request " + request.requestId);
 
                 final VcnGatewayConnection vcnGatewayConnection =
-                        new VcnGatewayConnection(
-                                mVcnContext, mSubscriptionGroup, gatewayConnectionConfig);
+                        mDeps.newVcnGatewayConnection(
+                                mVcnContext,
+                                mSubscriptionGroup,
+                                mLastSnapshot,
+                                gatewayConnectionConfig,
+                                new VcnGatewayStatusCallbackImpl());
                 mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection);
             }
         }
     }
 
+    private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        mLastSnapshot = snapshot;
+
+        if (isActive()) {
+            for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) {
+                gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
+            }
+        }
+    }
+
     private boolean requestSatisfiedByGatewayConnectionConfig(
             @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) {
-        final NetworkCapabilities configCaps = new NetworkCapabilities();
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
         for (int cap : config.getAllExposedCapabilities()) {
-            configCaps.addCapability(cap);
+            builder.addCapability(cap);
         }
 
-        return request.networkCapabilities.satisfiedByNetworkCapabilities(configCaps);
+        return request.canBeSatisfiedBy(builder.build());
     }
 
     private String getLogTag() {
@@ -215,11 +312,43 @@
     }
 
     /** Retrieves the network score for a VCN Network */
-    private int getNetworkScore() {
+    // Package visibility for use in VcnGatewayConnection
+    static int getNetworkScore() {
         // TODO: STOPSHIP (b/173549607): Make this use new NetworkSelection, or some magic "max in
         //                               subGrp" value
         return 52;
     }
 
-    private static class Dependencies {}
+    /** Callback used for passing status signals from a VcnGatewayConnection to its managing Vcn. */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public interface VcnGatewayStatusCallback {
+        /** Called by a VcnGatewayConnection to indicate that it has entered Safemode. */
+        void onEnteredSafemode();
+    }
+
+    private class VcnGatewayStatusCallbackImpl implements VcnGatewayStatusCallback {
+        @Override
+        public void onEnteredSafemode() {
+            sendMessage(obtainMessage(MSG_CMD_ENTER_SAFEMODE));
+        }
+    }
+
+    /** External dependencies used by Vcn, for injection in tests */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class Dependencies {
+        /** Builds a new VcnGatewayConnection */
+        public VcnGatewayConnection newVcnGatewayConnection(
+                VcnContext vcnContext,
+                ParcelUuid subscriptionGroup,
+                TelephonySubscriptionSnapshot snapshot,
+                VcnGatewayConnectionConfig connectionConfig,
+                VcnGatewayStatusCallback gatewayStatusCallback) {
+            return new VcnGatewayConnection(
+                    vcnContext,
+                    subscriptionGroup,
+                    snapshot,
+                    connectionConfig,
+                    gatewayStatusCallback);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index dba59bd..7399e56 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -55,4 +55,15 @@
     public VcnNetworkProvider getVcnNetworkProvider() {
         return mVcnNetworkProvider;
     }
+
+    /**
+     * Verifies that the caller is running on the VcnContext Thread.
+     *
+     * @throwsIllegalStateException if the caller is not running on the VcnContext Thread.
+     */
+    public void ensureRunningOnLooperThread() {
+        if (getLooper().getThread() != Thread.currentThread()) {
+            throw new IllegalStateException("Not running on VcnMgmtSvc thread");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 4e0c0c5..9ecdf1b 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -17,14 +17,16 @@
 package com.android.server.vcn;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static com.android.server.VcnManagementService.VDBG;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.IpSecManager;
@@ -35,10 +37,10 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
 import android.net.RouteInfo;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.annotations.PolicyDirection;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
@@ -50,25 +52,31 @@
 import android.net.ipsec.ike.exceptions.IkeException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
 import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnTransportInfo;
+import android.net.wifi.WifiInfo;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.ParcelUuid;
-import android.telephony.TelephonyManager;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
 import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
+import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
 
 import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.util.Arrays;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -116,6 +124,9 @@
 public class VcnGatewayConnection extends StateMachine {
     private static final String TAG = VcnGatewayConnection.class.getSimpleName();
 
+    private static final int[] MERGED_CAPABILITIES =
+            new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING};
+
     private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0");
     private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE;
 
@@ -126,7 +137,9 @@
     private static final int TOKEN_ALL = Integer.MIN_VALUE;
 
     private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30;
-    private static final int TEARDOWN_TIMEOUT_SECONDS = 5;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final int TEARDOWN_TIMEOUT_SECONDS = 5;
 
     private interface EventInfo {}
 
@@ -360,16 +373,43 @@
      */
     private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8;
 
-    @NonNull private final DisconnectedState mDisconnectedState = new DisconnectedState();
-    @NonNull private final DisconnectingState mDisconnectingState = new DisconnectingState();
-    @NonNull private final ConnectingState mConnectingState = new ConnectingState();
-    @NonNull private final ConnectedState mConnectedState = new ConnectedState();
-    @NonNull private final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState();
+    /**
+     * Sent when this VcnGatewayConnection is notified of a change in TelephonySubscriptions.
+     *
+     * <p>Relevant in all states.
+     *
+     * @param arg1 The "all" token; this signal is always honored.
+     */
+    // TODO(b/178426520): implement handling of this event
+    private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9;
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final DisconnectedState mDisconnectedState = new DisconnectedState();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final DisconnectingState mDisconnectingState = new DisconnectingState();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final ConnectingState mConnectingState = new ConnectingState();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final ConnectedState mConnectedState = new ConnectedState();
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    @NonNull
+    final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState();
+
+    @NonNull private TelephonySubscriptionSnapshot mLastSnapshot;
 
     @NonNull private final VcnContext mVcnContext;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
     @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
+    @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback;
     @NonNull private final Dependencies mDeps;
 
     @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback;
@@ -403,13 +443,6 @@
     private int mCurrentToken = -1;
 
     /**
-     * The next usable token.
-     *
-     * <p>A new token MUST be used for all new IKE sessions.
-     */
-    private int mNextToken = 0;
-
-    /**
      * The number of unsuccessful attempts since the last successful connection.
      *
      * <p>This number MUST be incremented each time the RetryTimeout state is entered, and cleared
@@ -430,7 +463,7 @@
      * <p>Set in Connecting or Migrating States, always @NonNull in Connecting, Connected, and
      * Migrating states, null otherwise.
      */
-    private IkeSession mIkeSession;
+    private VcnIkeSession mIkeSession;
 
     /**
      * The last known child configuration.
@@ -451,26 +484,45 @@
     public VcnGatewayConnection(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
-            @NonNull VcnGatewayConnectionConfig connectionConfig) {
-        this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies());
+            @NonNull TelephonySubscriptionSnapshot snapshot,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
+            @NonNull VcnGatewayStatusCallback gatewayStatusCallback) {
+        this(
+                vcnContext,
+                subscriptionGroup,
+                snapshot,
+                connectionConfig,
+                gatewayStatusCallback,
+                new Dependencies());
     }
 
-    private VcnGatewayConnection(
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnGatewayConnection(
             @NonNull VcnContext vcnContext,
             @NonNull ParcelUuid subscriptionGroup,
+            @NonNull TelephonySubscriptionSnapshot snapshot,
             @NonNull VcnGatewayConnectionConfig connectionConfig,
+            @NonNull VcnGatewayStatusCallback gatewayStatusCallback,
             @NonNull Dependencies deps) {
         super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper());
         mVcnContext = vcnContext;
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
         mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
+        mGatewayStatusCallback =
+                Objects.requireNonNull(gatewayStatusCallback, "Missing gatewayStatusCallback");
         mDeps = Objects.requireNonNull(deps, "Missing deps");
 
+        mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
+
         mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback();
 
         mUnderlyingNetworkTracker =
                 mDeps.newUnderlyingNetworkTracker(
-                        mVcnContext, subscriptionGroup, mUnderlyingNetworkTrackerCallback);
+                        mVcnContext,
+                        subscriptionGroup,
+                        mLastSnapshot,
+                        mConnectionConfig.getAllUnderlyingCapabilities(),
+                        mUnderlyingNetworkTrackerCallback);
         mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
 
         IpSecTunnelInterface iface;
@@ -508,7 +560,6 @@
                 EVENT_DISCONNECT_REQUESTED,
                 TOKEN_ALL,
                 new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN));
-        quit();
 
         // TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this
         // is also called asynchronously when a NetworkAgent becomes unwanted
@@ -524,10 +575,28 @@
         mUnderlyingNetworkTracker.teardown();
     }
 
+    /**
+     * Notify this Gateway that subscriptions have changed.
+     *
+     * <p>This snapshot should be used to update any keepalive requests necessary for potential
+     * underlying Networks in this Gateway's subscription group.
+     */
+    public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) {
+        Objects.requireNonNull(snapshot, "Missing snapshot");
+        mVcnContext.ensureRunningOnLooperThread();
+
+        mLastSnapshot = snapshot;
+        mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot);
+
+        sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
+    }
+
     private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback {
         @Override
         public void onSelectedUnderlyingNetworkChanged(
                 @Nullable UnderlyingNetworkRecord underlying) {
+            // TODO(b/179091925): Move the delayed-message handling to BaseState
+
             // If underlying is null, all underlying networks have been lost. Disconnect VCN after a
             // timeout.
             if (underlying == null) {
@@ -633,6 +702,22 @@
 
         protected abstract void processStateMsg(Message msg) throws Exception;
 
+        @Override
+        public void exit() {
+            try {
+                exitState();
+            } catch (Exception e) {
+                Slog.wtf(TAG, "Uncaught exception", e);
+                sendMessage(
+                        EVENT_DISCONNECT_REQUESTED,
+                        TOKEN_ALL,
+                        new EventDisconnectRequestedInfo(
+                                DISCONNECT_REASON_INTERNAL_ERROR + e.toString()));
+            }
+        }
+
+        protected void exitState() throws Exception {}
+
         protected void logUnhandledMessage(Message msg) {
             // Log as unexpected all known messages, and log all else as unknown.
             switch (msg.what) {
@@ -643,7 +728,8 @@
                 case EVENT_TRANSFORM_CREATED: // Fallthrough
                 case EVENT_SETUP_COMPLETED: // Fallthrough
                 case EVENT_DISCONNECT_REQUESTED: // Fallthrough
-                case EVENT_TEARDOWN_TIMEOUT_EXPIRED:
+                case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough
+                case EVENT_SUBSCRIPTIONS_CHANGED:
                     logUnexpectedEvent(msg.what);
                     break;
                 default:
@@ -654,21 +740,16 @@
 
         protected void teardownNetwork() {
             if (mNetworkAgent != null) {
-                mNetworkAgent.sendNetworkInfo(buildNetworkInfo(false /* isConnected */));
+                mNetworkAgent.unregister();
                 mNetworkAgent = null;
             }
         }
 
-        protected void teardownIke() {
-            if (mIkeSession != null) {
-                mIkeSession.close();
-            }
-        }
-
         protected void handleDisconnectRequested(String msg) {
             Slog.v(TAG, "Tearing down. Cause: " + msg);
+            mIsRunning = false;
+
             teardownNetwork();
-            teardownIke();
 
             if (mIkeSession == null) {
                 // Already disconnected, go straight to DisconnectedState
@@ -697,7 +778,37 @@
      */
     private class DisconnectedState extends BaseState {
         @Override
-        protected void processStateMsg(Message msg) {}
+        protected void enterState() {
+            if (!mIsRunning) {
+                quitNow(); // Ignore all queued events; cleanup is complete.
+            }
+
+            if (mIkeSession != null || mNetworkAgent != null) {
+                Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState");
+            }
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED:
+                    // First network found; start tunnel
+                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+                    if (mUnderlying != null) {
+                        transitionTo(mConnectingState);
+                    }
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    mIsRunning = false;
+
+                    quitNow();
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
     }
 
     private abstract class ActiveBaseState extends BaseState {
@@ -731,8 +842,91 @@
      * does not complete teardown in a timely fashion, it will be killed (forcibly closed).
      */
     private class DisconnectingState extends ActiveBaseState {
+        /**
+         * Whether to skip the RetryTimeoutState and go straight to the ConnectingState.
+         *
+         * <p>This is used when an underlying network change triggered a restart on a new network.
+         *
+         * <p>Reset (to false) upon exit of the DisconnectingState.
+         */
+        private boolean mSkipRetryTimeout = false;
+
+        // TODO(b/178441390): Remove this in favor of resetting retry timers on UND_NET change.
+        public void setSkipRetryTimeout(boolean shouldSkip) {
+            mSkipRetryTimeout = shouldSkip;
+        }
+
         @Override
-        protected void processStateMsg(Message msg) {}
+        protected void enterState() throws Exception {
+            if (mIkeSession == null) {
+                Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state.");
+                sendMessage(EVENT_SESSION_CLOSED, mCurrentToken);
+                return;
+            }
+
+            // If underlying network has already been lost, save some time and just kill the session
+            if (mUnderlying == null) {
+                // Will trigger a EVENT_SESSION_CLOSED as IkeSession shuts down.
+                mIkeSession.kill();
+                return;
+            }
+
+            mIkeSession.close();
+            sendMessageDelayed(
+                    EVENT_TEARDOWN_TIMEOUT_EXPIRED,
+                    mCurrentToken,
+                    TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough
+                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+                    // If we received a new underlying network, continue.
+                    if (mUnderlying != null) {
+                        break;
+                    }
+
+                    // Fallthrough; no network exists to send IKE close session requests.
+                case EVENT_TEARDOWN_TIMEOUT_EXPIRED:
+                    // Grace period ended. Kill session, triggering EVENT_SESSION_CLOSED
+                    mIkeSession.kill();
+
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    teardownNetwork();
+
+                    String reason = ((EventDisconnectRequestedInfo) msg.obj).reason;
+                    if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) {
+                        // Will trigger EVENT_SESSION_CLOSED immediately.
+                        mIkeSession.kill();
+                        break;
+                    }
+
+                    // Otherwise we are already in the process of shutting down.
+                    break;
+                case EVENT_SESSION_CLOSED:
+                    mIkeSession = null;
+
+                    if (mIsRunning && mUnderlying != null) {
+                        transitionTo(mSkipRetryTimeout ? mConnectingState : mRetryTimeoutState);
+                    } else {
+                        teardownNetwork();
+                        transitionTo(mDisconnectedState);
+                    }
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
+
+        @Override
+        protected void exitState() throws Exception {
+            mSkipRetryTimeout = false;
+        }
     }
 
     /**
@@ -743,10 +937,175 @@
      */
     private class ConnectingState extends ActiveBaseState {
         @Override
-        protected void processStateMsg(Message msg) {}
+        protected void enterState() {
+            if (mIkeSession != null) {
+                Slog.wtf(TAG, "ConnectingState entered with active session");
+
+                // Attempt to recover.
+                mIkeSession.kill();
+                mIkeSession = null;
+            }
+
+            mIkeSession = buildIkeSession();
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED:
+                    final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
+                    mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+                    if (oldUnderlying == null) {
+                        // This should never happen, but if it does, there's likely a nasty bug.
+                        Slog.wtf(TAG, "Old underlying network was null in connected state. Bug?");
+                    }
+
+                    // If new underlying is null, all underlying networks have been lost; disconnect
+                    if (mUnderlying == null) {
+                        transitionTo(mDisconnectingState);
+                        break;
+                    }
+
+                    if (oldUnderlying != null
+                            && mUnderlying.network.equals(oldUnderlying.network)) {
+                        break; // Only network properties have changed; continue connecting.
+                    }
+                    // Else, retry on the new network.
+
+                    // Immediately come back to the ConnectingState (skip RetryTimeout, since this
+                    // isn't a failure)
+                    mDisconnectingState.setSkipRetryTimeout(true);
+
+                    // fallthrough - disconnect, and retry on new network.
+                case EVENT_SESSION_LOST:
+                    transitionTo(mDisconnectingState);
+                    break;
+                case EVENT_SESSION_CLOSED:
+                    // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this
+                    // message may not be posted again. Defer to ensure immediate shutdown.
+                    deferMessage(msg);
+
+                    transitionTo(mDisconnectingState);
+                    break;
+                case EVENT_SETUP_COMPLETED: // fallthrough
+                case EVENT_TRANSFORM_CREATED:
+                    // Child setup complete; move to ConnectedState for NetworkAgent registration
+                    deferMessage(msg);
+                    transitionTo(mConnectedState);
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
     }
 
-    private abstract class ConnectedStateBase extends ActiveBaseState {}
+    private abstract class ConnectedStateBase extends ActiveBaseState {
+        protected void updateNetworkAgent(
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull NetworkAgent agent,
+                @NonNull ChildSessionConfiguration childConfig) {
+            final NetworkCapabilities caps =
+                    buildNetworkCapabilities(mConnectionConfig, mUnderlying);
+            final LinkProperties lp =
+                    buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);
+
+            agent.sendNetworkCapabilities(caps);
+            agent.sendLinkProperties(lp);
+        }
+
+        protected NetworkAgent buildNetworkAgent(
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull ChildSessionConfiguration childConfig) {
+            final NetworkCapabilities caps =
+                    buildNetworkCapabilities(mConnectionConfig, mUnderlying);
+            final LinkProperties lp =
+                    buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig);
+
+            final NetworkAgent agent =
+                    new NetworkAgent(
+                            mVcnContext.getContext(),
+                            mVcnContext.getLooper(),
+                            TAG,
+                            caps,
+                            lp,
+                            Vcn.getNetworkScore(),
+                            new NetworkAgentConfig(),
+                            mVcnContext.getVcnNetworkProvider()) {
+                        @Override
+                        public void unwanted() {
+                            teardownAsynchronously();
+                        }
+                    };
+
+            agent.register();
+            agent.markConnected();
+
+            return agent;
+        }
+
+        protected void applyTransform(
+                int token,
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull Network underlyingNetwork,
+                @NonNull IpSecTransform transform,
+                int direction) {
+            try {
+                // TODO: Set underlying network of tunnel interface
+
+                // Transforms do not need to be persisted; the IkeSession will keep them alive
+                mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
+            } catch (IOException e) {
+                Slog.d(TAG, "Transform application failed for network " + token, e);
+                sessionLost(token, e);
+            }
+        }
+
+        protected void setupInterface(
+                int token,
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull ChildSessionConfiguration childConfig) {
+            setupInterface(token, tunnelIface, childConfig, null);
+        }
+
+        protected void setupInterface(
+                int token,
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull ChildSessionConfiguration childConfig,
+                @Nullable ChildSessionConfiguration oldChildConfig) {
+            try {
+                final Set<LinkAddress> newAddrs =
+                        new ArraySet<>(childConfig.getInternalAddresses());
+                final Set<LinkAddress> existingAddrs = new ArraySet<>();
+                if (oldChildConfig != null) {
+                    existingAddrs.addAll(oldChildConfig.getInternalAddresses());
+                }
+
+                final Set<LinkAddress> toAdd = new ArraySet<>();
+                toAdd.addAll(newAddrs);
+                toAdd.removeAll(existingAddrs);
+
+                final Set<LinkAddress> toRemove = new ArraySet<>();
+                toRemove.addAll(existingAddrs);
+                toRemove.removeAll(newAddrs);
+
+                for (LinkAddress address : toAdd) {
+                    tunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
+                }
+
+                for (LinkAddress address : toRemove) {
+                    tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength());
+                }
+            } catch (IOException e) {
+                Slog.d(TAG, "Adding address to tunnel failed for token " + token, e);
+                sessionLost(token, e);
+            }
+        }
+    }
 
     /**
      * Stable state representing a VCN that has a functioning connection to the mobility anchor.
@@ -756,7 +1115,89 @@
      */
     class ConnectedState extends ConnectedStateBase {
         @Override
-        protected void processStateMsg(Message msg) {}
+        protected void enterState() throws Exception {
+            // Successful connection, clear failed attempt counter
+            mFailedAttempts = 0;
+        }
+
+        @Override
+        protected void processStateMsg(Message msg) {
+            switch (msg.what) {
+                case EVENT_UNDERLYING_NETWORK_CHANGED:
+                    handleUnderlyingNetworkChanged(msg);
+                    break;
+                case EVENT_SESSION_CLOSED:
+                    // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this
+                    // message may not be posted again. Defer to ensure immediate shutdown.
+                    deferMessage(msg);
+                    transitionTo(mDisconnectingState);
+                    break;
+                case EVENT_SESSION_LOST:
+                    transitionTo(mDisconnectingState);
+                    break;
+                case EVENT_TRANSFORM_CREATED:
+                    final EventTransformCreatedInfo transformCreatedInfo =
+                            (EventTransformCreatedInfo) msg.obj;
+
+                    applyTransform(
+                            mCurrentToken,
+                            mTunnelIface,
+                            mUnderlying.network,
+                            transformCreatedInfo.transform,
+                            transformCreatedInfo.direction);
+                    break;
+                case EVENT_SETUP_COMPLETED:
+                    mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig;
+
+                    setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig);
+                    break;
+                case EVENT_DISCONNECT_REQUESTED:
+                    handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason);
+                    break;
+                default:
+                    logUnhandledMessage(msg);
+                    break;
+            }
+        }
+
+        private void handleUnderlyingNetworkChanged(@NonNull Message msg) {
+            final UnderlyingNetworkRecord oldUnderlying = mUnderlying;
+            mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying;
+
+            if (mUnderlying == null) {
+                // Ignored for now; a new network may be coming up. If none does, the delayed
+                // NETWORK_LOST disconnect will be fired, and tear down the session + network.
+                return;
+            }
+
+            // mUnderlying assumed non-null, given check above.
+            // If network changed, migrate. Otherwise, update any existing networkAgent.
+            if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) {
+                mIkeSession.setNetwork(mUnderlying.network);
+            } else {
+                // oldUnderlying is non-null & underlying network itself has not changed
+                // (only network properties were changed).
+
+                // Network not yet set up, or child not yet connected.
+                if (mNetworkAgent != null && mChildConfig != null) {
+                    // If only network properties changed and agent is active, update properties
+                    updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig);
+                }
+            }
+        }
+
+        protected void setupInterfaceAndNetworkAgent(
+                int token,
+                @NonNull IpSecTunnelInterface tunnelIface,
+                @NonNull ChildSessionConfiguration childConfig) {
+            setupInterface(token, tunnelIface, childConfig);
+
+            if (mNetworkAgent == null) {
+                mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig);
+            } else {
+                updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig);
+            }
+        }
     }
 
     /**
@@ -769,35 +1210,68 @@
         protected void processStateMsg(Message msg) {}
     }
 
-    // TODO: Remove this when migrating to new NetworkAgent API
-    private static NetworkInfo buildNetworkInfo(boolean isConnected) {
-        NetworkInfo info =
-                new NetworkInfo(
-                        ConnectivityManager.TYPE_MOBILE,
-                        TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                        "MOBILE",
-                        "VCN");
-        info.setDetailedState(
-                isConnected ? DetailedState.CONNECTED : DetailedState.DISCONNECTED, null, null);
-
-        return info;
-    }
-
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static NetworkCapabilities buildNetworkCapabilities(
-            @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) {
-        final NetworkCapabilities caps = new NetworkCapabilities();
+            @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig,
+            @Nullable UnderlyingNetworkRecord underlying) {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
 
-        caps.addTransportType(TRANSPORT_CELLULAR);
-        caps.addCapability(NET_CAPABILITY_NOT_CONGESTED);
-        caps.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
+        builder.addTransportType(TRANSPORT_CELLULAR);
+        builder.addCapability(NET_CAPABILITY_NOT_CONGESTED);
+        builder.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
 
         // Add exposed capabilities
         for (int cap : gatewayConnectionConfig.getAllExposedCapabilities()) {
-            caps.addCapability(cap);
+            builder.addCapability(cap);
         }
 
-        return caps;
+        if (underlying != null) {
+            final NetworkCapabilities underlyingCaps = underlying.networkCapabilities;
+
+            // Mirror merged capabilities.
+            for (int cap : MERGED_CAPABILITIES) {
+                if (underlyingCaps.hasCapability(cap)) {
+                    builder.addCapability(cap);
+                }
+            }
+
+            // Set admin UIDs for ConnectivityDiagnostics use.
+            final int[] underlyingAdminUids = underlyingCaps.getAdministratorUids();
+            Arrays.sort(underlyingAdminUids); // Sort to allow contains check below.
+
+            final int[] adminUids;
+            if (underlyingCaps.getOwnerUid() > 0 // No owner UID specified
+                    && 0 > Arrays.binarySearch(// Owner UID not found in admin UID list.
+                            underlyingAdminUids, underlyingCaps.getOwnerUid())) {
+                adminUids = Arrays.copyOf(underlyingAdminUids, underlyingAdminUids.length + 1);
+                adminUids[adminUids.length - 1] = underlyingCaps.getOwnerUid();
+                Arrays.sort(adminUids);
+            } else {
+                adminUids = underlyingAdminUids;
+            }
+            builder.setAdministratorUids(adminUids);
+
+            // Set TransportInfo for SysUI use (never parcelled out of SystemServer).
+            if (underlyingCaps.hasTransport(TRANSPORT_WIFI)
+                    && underlyingCaps.getTransportInfo() instanceof WifiInfo) {
+                final WifiInfo wifiInfo = (WifiInfo) underlyingCaps.getTransportInfo();
+                builder.setTransportInfo(new VcnTransportInfo(wifiInfo));
+            } else if (underlyingCaps.hasTransport(TRANSPORT_CELLULAR)
+                    && underlyingCaps.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) {
+                final TelephonyNetworkSpecifier telNetSpecifier =
+                        (TelephonyNetworkSpecifier) underlyingCaps.getNetworkSpecifier();
+                builder.setTransportInfo(new VcnTransportInfo(telNetSpecifier.getSubscriptionId()));
+            } else {
+                Slog.wtf(
+                        TAG,
+                        "Unknown transport type or missing TransportInfo/NetworkSpecifier for"
+                                + " non-null underlying network");
+            }
+        }
+
+        // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs.
+
+        return builder.build();
     }
 
     private static LinkProperties buildConnectedLinkProperties(
@@ -893,31 +1367,143 @@
         }
     }
 
-    /** External dependencies used by VcnGatewayConnection, for injection in tests. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() {
+        return mUnderlyingNetworkTrackerCallback;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    UnderlyingNetworkRecord getUnderlyingNetwork() {
+        return mUnderlying;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setUnderlyingNetwork(@Nullable UnderlyingNetworkRecord record) {
+        mUnderlying = record;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    boolean isRunning() {
+        return mIsRunning;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setIsRunning(boolean isRunning) {
+        mIsRunning = isRunning;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnIkeSession getIkeSession() {
+        return mIkeSession;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    void setIkeSession(@Nullable VcnIkeSession session) {
+        mIkeSession = session;
+    }
+
+    private IkeSessionParams buildIkeParams() {
+        // TODO: Implement this once IkeSessionParams is persisted
+        return null;
+    }
+
+    private ChildSessionParams buildChildParams() {
+        // TODO: Implement this once IkeSessionParams is persisted
+        return null;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    VcnIkeSession buildIkeSession() {
+        final int token = ++mCurrentToken;
+
+        return mDeps.newIkeSession(
+                mVcnContext,
+                buildIkeParams(),
+                buildChildParams(),
+                new IkeSessionCallbackImpl(token),
+                new ChildSessionCallbackImpl(token));
+    }
+
+    /** External dependencies used by VcnGatewayConnection, for injection in tests */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static class Dependencies {
         /** Builds a new UnderlyingNetworkTracker. */
         public UnderlyingNetworkTracker newUnderlyingNetworkTracker(
                 VcnContext vcnContext,
                 ParcelUuid subscriptionGroup,
+                TelephonySubscriptionSnapshot snapshot,
+                Set<Integer> requiredUnderlyingNetworkCapabilities,
                 UnderlyingNetworkTrackerCallback callback) {
-            return new UnderlyingNetworkTracker(vcnContext, subscriptionGroup, callback);
+            return new UnderlyingNetworkTracker(
+                    vcnContext,
+                    subscriptionGroup,
+                    snapshot,
+                    requiredUnderlyingNetworkCapabilities,
+                    callback);
         }
 
         /** Builds a new IkeSession. */
-        public IkeSession newIkeSession(
+        public VcnIkeSession newIkeSession(
                 VcnContext vcnContext,
                 IkeSessionParams ikeSessionParams,
                 ChildSessionParams childSessionParams,
                 IkeSessionCallback ikeSessionCallback,
                 ChildSessionCallback childSessionCallback) {
-            return new IkeSession(
-                    vcnContext.getContext(),
+            return new VcnIkeSession(
+                    vcnContext,
                     ikeSessionParams,
                     childSessionParams,
-                    new HandlerExecutor(new Handler(vcnContext.getLooper())),
                     ikeSessionCallback,
                     childSessionCallback);
         }
     }
+
+    /** Proxy implementation of IKE session, used for testing. */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class VcnIkeSession {
+        private final IkeSession mImpl;
+
+        public VcnIkeSession(
+                VcnContext vcnContext,
+                IkeSessionParams ikeSessionParams,
+                ChildSessionParams childSessionParams,
+                IkeSessionCallback ikeSessionCallback,
+                ChildSessionCallback childSessionCallback) {
+            mImpl =
+                    new IkeSession(
+                            vcnContext.getContext(),
+                            ikeSessionParams,
+                            childSessionParams,
+                            new HandlerExecutor(new Handler(vcnContext.getLooper())),
+                            ikeSessionCallback,
+                            childSessionCallback);
+        }
+
+        /** Creates a new IKE Child session. */
+        public void openChildSession(
+                @NonNull ChildSessionParams childSessionParams,
+                @NonNull ChildSessionCallback childSessionCallback) {
+            mImpl.openChildSession(childSessionParams, childSessionCallback);
+        }
+
+        /** Closes an IKE session as identified by the ChildSessionCallback. */
+        public void closeChildSession(@NonNull ChildSessionCallback childSessionCallback) {
+            mImpl.closeChildSession(childSessionCallback);
+        }
+
+        /** Gracefully closes this IKE Session, waiting for remote acknowledgement. */
+        public void close() {
+            mImpl.close();
+        }
+
+        /** Forcibly kills this IKE Session, without waiting for a closure confirmation. */
+        public void kill() {
+            mImpl.kill();
+        }
+
+        /** Sets the underlying network used by the IkeSession. */
+        public void setNetwork(@NonNull Network network) {
+            mImpl.setNetwork(network);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
index 7f5b23c..fe4ea30 100644
--- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
+++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java
@@ -21,9 +21,12 @@
 import android.net.NetworkProvider;
 import android.net.NetworkRequest;
 import android.os.Looper;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
-import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
 
 import java.util.Objects;
 import java.util.Set;
@@ -40,24 +43,36 @@
     private static final String TAG = VcnNetworkProvider.class.getSimpleName();
 
     private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
-    private final SparseArray<NetworkRequestEntry> mRequests = new SparseArray<>();
+
+    /**
+     * Cache of NetworkRequest(s), scores and network providers, keyed by NetworkRequest
+     *
+     * <p>NetworkRequests are immutable once created, and therefore can be used as stable keys.
+     */
+    private final ArrayMap<NetworkRequest, NetworkRequestEntry> mRequests = new ArrayMap<>();
 
     public VcnNetworkProvider(Context context, Looper looper) {
         super(context, looper, VcnNetworkProvider.class.getSimpleName());
     }
 
-    // Package-private
-    void registerListener(@NonNull NetworkRequestListener listener) {
+    /**
+     * Registers a NetworkRequestListener with this NetworkProvider.
+     *
+     * <p>Upon registering, the provided listener will receive all cached requests.
+     */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public void registerListener(@NonNull NetworkRequestListener listener) {
         mListeners.add(listener);
 
         // Send listener all cached requests
-        for (int i = 0; i < mRequests.size(); i++) {
-            notifyListenerForEvent(listener, mRequests.valueAt(i));
+        for (NetworkRequestEntry entry : mRequests.values()) {
+            notifyListenerForEvent(listener, entry);
         }
     }
 
-    // Package-private
-    void unregisterListener(@NonNull NetworkRequestListener listener) {
+    /** Unregisters the specified listener from receiving future NetworkRequests. */
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public void unregisterListener(@NonNull NetworkRequestListener listener) {
         mListeners.remove(listener);
     }
 
@@ -75,7 +90,9 @@
                         request, score, providerId));
 
         final NetworkRequestEntry entry = new NetworkRequestEntry(request, score, providerId);
-        mRequests.put(request.requestId, entry);
+
+        // NetworkRequests are immutable once created, and therefore can be used as stable keys.
+        mRequests.put(request, entry);
 
         // TODO(b/176939047): Intelligently route requests to prioritized VcnInstances (based on
         // Default Data Sub, or similar)
@@ -86,7 +103,7 @@
 
     @Override
     public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) {
-        mRequests.remove(request.requestId);
+        mRequests.remove(request);
     }
 
     private static class NetworkRequestEntry {
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index fe3b03ab..e0f5408 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -138,6 +138,11 @@
         return mStatus != Status.RUNNING;
     }
 
+    /** Return true is effect is a repeating vibration. */
+    public boolean isRepeating() {
+        return mEffect.getDuration() == Long.MAX_VALUE;
+    }
+
     /** Return the effect that should be played by this vibration. */
     @Nullable
     public CombinedVibrationEffect getEffect() {
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 536375f..8910bdf 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -34,10 +34,12 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.VibratorServiceDumpProto;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -340,6 +342,24 @@
                 + '}';
     }
 
+    /** Write current settings into given {@link ProtoOutputStream}. */
+    public void dumpProto(ProtoOutputStream proto) {
+        synchronized (mLock) {
+            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY,
+                    mHapticFeedbackIntensity);
+            proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultHapticFeedbackIntensity());
+            proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY,
+                    mNotificationIntensity);
+            proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultNotificationVibrationIntensity());
+            proto.write(VibratorServiceDumpProto.RING_INTENSITY,
+                    mRingIntensity);
+            proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY,
+                    mVibrator.getDefaultRingVibrationIntensity());
+        }
+    }
+
     private void notifyListeners() {
         List<OnVibratorSettingsChanged> currentListeners;
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index c36375e..4f2fc86 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -74,6 +74,7 @@
         void onVibrationEnded(long vibrationId, Vibration.Status status);
     }
 
+    private final Object mLock = new Object();
     private final WorkSource mWorkSource = new WorkSource();
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
@@ -81,10 +82,10 @@
     private final VibrationCallbacks mCallbacks;
     private final SparseArray<VibratorController> mVibrators;
 
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     @Nullable
     private VibrateStep mCurrentVibrateStep;
-    @GuardedBy("this")
+    @GuardedBy("mLock")
     private boolean mForceStop;
 
     // TODO(b/159207608): Remove this constructor once VibratorService is removed
@@ -115,6 +116,9 @@
 
     @Override
     public void binderDied() {
+        if (DEBUG) {
+            Slog.d(TAG, "Binder died, cancelling vibration...");
+        }
         cancel();
     }
 
@@ -136,21 +140,28 @@
 
     /** Cancel current vibration and shuts down the thread gracefully. */
     public void cancel() {
-        synchronized (this) {
+        synchronized (mLock) {
             mForceStop = true;
-            notify();
+            mLock.notify();
         }
     }
 
     /** Notify current vibration that a step has completed on given vibrator. */
     public void vibratorComplete(int vibratorId) {
-        synchronized (this) {
+        synchronized (mLock) {
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
+            }
             if (mCurrentVibrateStep != null) {
                 mCurrentVibrateStep.vibratorComplete(vibratorId);
             }
         }
     }
 
+    public Vibration getVibration() {
+        return mVibration;
+    }
+
     @VisibleForTesting
     SparseArray<VibratorController> getVibrators() {
         return mVibrators;
@@ -168,7 +179,7 @@
             final int stepCount = steps.size();
             for (int i = 0; i < stepCount; i++) {
                 Step step = steps.get(i);
-                synchronized (this) {
+                synchronized (mLock) {
                     if (step instanceof VibrateStep) {
                         mCurrentVibrateStep = (VibrateStep) step;
                     } else {
@@ -295,21 +306,48 @@
      * Sleeps until given {@code wakeUpTime}.
      *
      * <p>This stops immediately when {@link #cancel()} is called.
+     *
+     * @return true if waited until wake-up time, false if it was cancelled.
      */
-    private void waitUntil(long wakeUpTime) {
-        synchronized (this) {
+    private boolean waitUntil(long wakeUpTime) {
+        synchronized (mLock) {
             long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
             while (durationRemaining > 0) {
                 try {
-                    VibrationThread.this.wait(durationRemaining);
+                    mLock.wait(durationRemaining);
                 } catch (InterruptedException e) {
                 }
                 if (mForceStop) {
-                    break;
+                    return false;
                 }
                 durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
             }
         }
+        return true;
+    }
+
+    /**
+     * Sleeps until given {@link VibrateStep#isVibrationComplete()}, or until {@code wakeUpTime}.
+     *
+     * <p>This stops immediately when {@link #cancel()} is called.
+     *
+     * @return true if finished on vibration complete, false if it was cancelled or timed out.
+     */
+    private boolean waitForVibrationComplete(VibrateStep step, long wakeUpTime) {
+        synchronized (mLock) {
+            long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
+            while (!step.isVibrationComplete() && durationRemaining > 0) {
+                try {
+                    mLock.wait(durationRemaining);
+                } catch (InterruptedException e) {
+                }
+                if (mForceStop) {
+                    return false;
+                }
+                durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
+            }
+        }
+        return step.isVibrationComplete();
     }
 
     private void noteVibratorOn(long duration) {
@@ -341,6 +379,9 @@
     private interface VibrateStep extends Step {
         /** Callback to notify a vibrator has finished playing a effect. */
         void vibratorComplete(int vibratorId);
+
+        /** Returns true if the vibration played by this step is complete. */
+        boolean isVibrationComplete();
     }
 
     /** Represent a vibration on a single vibrator. */
@@ -348,11 +389,20 @@
         private final VibratorController mVibrator;
         private final VibrationEffect mEffect;
 
+        @GuardedBy("mLock")
+        private boolean mVibrationComplete;
+
         SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) {
             mVibrator = vibrator;
             mEffect = effect;
         }
 
+        @GuardedBy("mLock")
+        @Override
+        public boolean isVibrationComplete() {
+            return mVibrationComplete;
+        }
+
         @Override
         public void vibratorComplete(int vibratorId) {
             if (mVibrator.getVibratorInfo().getId() != vibratorId) {
@@ -364,8 +414,9 @@
                 return;
             }
             mVibrator.off();
-            synchronized (VibrationThread.this) {
-                VibrationThread.this.notify();
+            synchronized (mLock) {
+                mVibrationComplete = true;
+                mLock.notify();
             }
         }
 
@@ -384,12 +435,13 @@
                     noteVibratorOn(duration);
                     // Vibration is playing with no need to control amplitudes, just wait for native
                     // callback or timeout.
-                    waitUntil(startTime + duration + CALLBACKS_EXTRA_TIMEOUT);
-                    if (mForceStop) {
-                        mVibrator.off();
-                        return Vibration.Status.CANCELLED;
+                    if (waitForVibrationComplete(this,
+                            startTime + duration + CALLBACKS_EXTRA_TIMEOUT)) {
+                        return Vibration.Status.FINISHED;
                     }
-                    return Vibration.Status.FINISHED;
+                    // Timed out or vibration cancelled. Stop vibrator anyway.
+                    mVibrator.off();
+                    return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
                 }
 
                 startTime = SystemClock.uptimeMillis();
@@ -407,8 +459,7 @@
                     noteVibratorOn(duration);
                 }
                 while (amplitudeStep != null) {
-                    waitUntil(amplitudeStep.startTime);
-                    if (mForceStop) {
+                    if (!waitUntil(amplitudeStep.startTime)) {
                         mVibrator.off();
                         return Vibration.Status.CANCELLED;
                     }
@@ -422,7 +473,7 @@
                     noteVibratorOff();
                 }
                 if (DEBUG) {
-                    Slog.d(TAG, "SingleVibrateStep step done.");
+                    Slog.d(TAG, "SingleVibrateStep done.");
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
@@ -482,7 +533,7 @@
         private final int mRequiredCapabilities;
         private final int[] mVibratorIds;
 
-        @GuardedBy("VibrationThread.this")
+        @GuardedBy("mLock")
         private int mActiveVibratorCounter;
 
         SyncedVibrateStep(SparseArray<VibrationEffect> effects) {
@@ -496,6 +547,12 @@
             }
         }
 
+        @GuardedBy("mLock")
+        @Override
+        public boolean isVibrationComplete() {
+            return mActiveVibratorCounter <= 0;
+        }
+
         @Override
         public void vibratorComplete(int vibratorId) {
             VibrationEffect effect = mEffects.get(vibratorId);
@@ -508,10 +565,9 @@
                 return;
             }
             mVibrators.get(vibratorId).off();
-            synchronized (VibrationThread.this) {
-                if (--mActiveVibratorCounter <= 0) {
-                    VibrationThread.this.notify();
-                }
+            synchronized (mLock) {
+                --mActiveVibratorCounter;
+                mLock.notify();
             }
         }
 
@@ -532,8 +588,7 @@
 
                 while (!nextSteps.isEmpty()) {
                     AmplitudeStep step = nextSteps.poll();
-                    waitUntil(step.startTime);
-                    if (mForceStop) {
+                    if (!waitUntil(step.startTime)) {
                         stopAllVibrators();
                         return Vibration.Status.CANCELLED;
                     }
@@ -541,7 +596,7 @@
                     AmplitudeStep nextStep = step.nextStep();
                     if (nextStep == null) {
                         // This vibrator has finished playing the effect for this step.
-                        synchronized (VibrationThread.this) {
+                        synchronized (mLock) {
                             mActiveVibratorCounter--;
                         }
                     } else {
@@ -549,19 +604,18 @@
                     }
                 }
 
-                // All OneShot and Waveform effects have finished. Just wait for the other effects
-                // to end via native callbacks before finishing this synced step.
-                synchronized (VibrationThread.this) {
-                    if (mActiveVibratorCounter > 0) {
-                        waitUntil(startTime + timeout + CALLBACKS_EXTRA_TIMEOUT);
+                synchronized (mLock) {
+                    // All OneShot and Waveform effects have finished. Just wait for the other
+                    // effects to end via native callbacks before finishing this synced step.
+                    final long wakeUpTime = startTime + timeout + CALLBACKS_EXTRA_TIMEOUT;
+                    if (mActiveVibratorCounter <= 0 || waitForVibrationComplete(this, wakeUpTime)) {
+                        return Vibration.Status.FINISHED;
                     }
-                }
-                if (mForceStop) {
-                    stopAllVibrators();
-                    return Vibration.Status.CANCELLED;
-                }
 
-                return Vibration.Status.FINISHED;
+                    // Timed out or vibration cancelled. Stop all vibrators anyway.
+                    stopAllVibrators();
+                    return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
+                }
             } finally {
                 if (timeout > 0) {
                     noteVibratorOff();
@@ -774,8 +828,10 @@
                 if (DEBUG) {
                     Slog.d(TAG, "DelayStep of " + mDelay + "ms starting...");
                 }
-                waitUntil(SystemClock.uptimeMillis() + mDelay);
-                return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
+                if (waitUntil(SystemClock.uptimeMillis() + mDelay)) {
+                    return Vibration.Status.FINISHED;
+                }
+                return Vibration.Status.CANCELLED;
             } finally {
                 if (DEBUG) {
                     Slog.d(TAG, "DelayStep done.");
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3198453..5697564 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1094,7 +1094,8 @@
                     return;
                 }
                 if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
-                mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
+                mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
+                        null /* options */);
                 final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
                 try {
                     connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2ecefea..3bbc81a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,12 +16,27 @@
 
 package com.android.server.wm;
 
+import static android.os.Build.IS_USER;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 
+import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
+import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.accessibility.AccessibilityTraceProto.ACCESSIBILITY_SERVICE;
+import static com.android.server.accessibility.AccessibilityTraceProto.CALENDAR_TIME;
+import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_PARAMS;
+import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_PKG;
+import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_STACKS;
+import static com.android.server.accessibility.AccessibilityTraceProto.ELAPSED_REALTIME_NANOS;
+import static com.android.server.accessibility.AccessibilityTraceProto.PROCESS_NAME;
+import static com.android.server.accessibility.AccessibilityTraceProto.THREAD_ID_NAME;
+import static com.android.server.accessibility.AccessibilityTraceProto.WHERE;
+import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.utils.RegionUtils.forEachRect;
@@ -29,7 +44,9 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
+import android.app.Application;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Matrix;
@@ -41,15 +58,20 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TypedValue;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.InsetsSource;
 import android.view.MagnificationSpec;
@@ -64,13 +86,22 @@
 
 import com.android.internal.R;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.util.TraceBuffer;
+import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
 
+import java.io.File;
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -79,26 +110,37 @@
  * This class contains the accessibility related logic of the window manager.
  */
 final class AccessibilityController {
+    private static final String TAG = AccessibilityController.class.getSimpleName();
 
+    private static final Object STATIC_LOCK = new Object();
+    static AccessibilityControllerInternal
+            getAccessibilityControllerInternal(WindowManagerService service) {
+        return AccessibilityControllerInternalImpl.getInstance(service);
+    }
+
+    private final AccessibilityTracing mAccessibilityTracing;
     private final WindowManagerService mService;
-
     private static final Rect EMPTY_RECT = new Rect();
     private static final float[] sTempFloats = new float[9];
 
-    public AccessibilityController(WindowManagerService service) {
+    AccessibilityController(WindowManagerService service) {
         mService = service;
+        mAccessibilityTracing = AccessibilityTracing.getInstance(service);
     }
 
     private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
-
     private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
             new SparseArray<>();
 
     // Set to true if initializing window population complete.
     private boolean mAllObserversInitialized = true;
 
-    public boolean setMagnificationCallbacksLocked(int displayId,
-            MagnificationCallbacks callbacks) {
+    boolean setMagnificationCallbacks(int displayId, MagnificationCallbacks callbacks) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(
+                    TAG + ".setMagnificationCallbacks",
+                    "displayId=" + displayId + "; callbacks={" + callbacks + "}");
+        }
         boolean result = false;
         if (callbacks != null) {
             if (mDisplayMagnifiers.get(displayId) != null) {
@@ -118,7 +160,7 @@
             if (displayMagnifier == null) {
                 throw new IllegalStateException("Magnification callbacks already cleared!");
             }
-            displayMagnifier.destroyLocked();
+            displayMagnifier.destroy();
             mDisplayMagnifiers.remove(displayId);
             result = true;
         }
@@ -133,8 +175,13 @@
      * @param callback The callback.
      * @return {@code false} if display id is not valid or an embedded display.
      */
-    public boolean setWindowsForAccessibilityCallbackLocked(int displayId,
+    boolean setWindowsForAccessibilityCallback(int displayId,
             WindowsForAccessibilityCallback callback) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(
+                    TAG + ".setWindowsForAccessibilityCallback",
+                    "displayId=" + displayId + "; callback={" + callback + "}");
+        }
         final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
         if (dc == null) {
             return false;
@@ -147,7 +194,7 @@
                 // empty, that means this mapping didn't be set, and needs to do this again.
                 // This happened when accessibility window observer is disabled and enabled again.
                 if (mWindowsForAccessibilityObserver.get(displayId) == null) {
-                    handleWindowObserverOfEmbeddedDisplayLocked(displayId, dc.getParentWindow());
+                    handleWindowObserverOfEmbeddedDisplay(displayId, dc.getParentWindow());
                 }
                 return false;
             } else if (mWindowsForAccessibilityObserver.get(displayId) != null) {
@@ -181,7 +228,12 @@
         return true;
     }
 
-    public void performComputeChangedWindowsNotLocked(int displayId, boolean forceSend) {
+    void performComputeChangedWindowsNot(int displayId, boolean forceSend) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(
+                    TAG + ".performComputeChangedWindowsNot",
+                    "displayId=" + displayId + "; forceSend=" + forceSend);
+        }
         WindowsForAccessibilityObserver observer = null;
         synchronized (mService.mGlobalLock) {
             final WindowsForAccessibilityObserver windowsForA11yObserver =
@@ -191,86 +243,119 @@
             }
         }
         if (observer != null) {
-            observer.performComputeChangedWindowsNotLocked(forceSend);
+            observer.performComputeChangedWindows(forceSend);
         }
     }
 
-    public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
+    void setMagnificationSpec(int displayId, MagnificationSpec spec) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".setMagnificationSpec",
+                    "displayId=" + displayId + "; spec={" + spec + "}");
+        }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.setMagnificationSpecLocked(spec);
+            displayMagnifier.setMagnificationSpec(spec);
         }
         final WindowsForAccessibilityObserver windowsForA11yObserver =
                 mWindowsForAccessibilityObserver.get(displayId);
         if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+            windowsForA11yObserver.scheduleComputeChangedWindows();
         }
     }
 
-    public void getMagnificationRegionLocked(int displayId, Region outMagnificationRegion) {
+    void getMagnificationRegion(int displayId, Region outMagnificationRegion) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".getMagnificationRegion",
+                    "displayId=" + displayId + "; outMagnificationRegion={" + outMagnificationRegion
+                            + "}");
+        }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
+            displayMagnifier.getMagnificationRegion(outMagnificationRegion);
         }
     }
 
-    public void onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle) {
+    void onRectangleOnScreenRequested(int displayId, Rect rectangle) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(
+                    TAG + ".onRectangleOnScreenRequested",
+                    "displayId=" + displayId + "; rectangle={" + rectangle + "}");
+        }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
+            displayMagnifier.onRectangleOnScreenRequested(rectangle);
         }
         // Not relevant for the window observer.
     }
 
-    public void onWindowLayersChangedLocked(int displayId) {
+    void onWindowLayersChanged(int displayId) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(
+                    TAG + ".onWindowLayersChanged", "displayId=" + displayId);
+        }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.onWindowLayersChangedLocked();
+            displayMagnifier.onWindowLayersChanged();
         }
         final WindowsForAccessibilityObserver windowsForA11yObserver =
                 mWindowsForAccessibilityObserver.get(displayId);
         if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+            windowsForA11yObserver.scheduleComputeChangedWindows();
         }
     }
 
-    public void onRotationChangedLocked(DisplayContent displayContent) {
+    void onRotationChanged(DisplayContent displayContent) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".onRotationChanged",
+                    "displayContent={" + displayContent + "}");
+        }
         final int displayId = displayContent.getDisplayId();
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.onRotationChangedLocked(displayContent);
+            displayMagnifier.onRotationChanged(displayContent);
         }
         final WindowsForAccessibilityObserver windowsForA11yObserver =
                 mWindowsForAccessibilityObserver.get(displayId);
         if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+            windowsForA11yObserver.scheduleComputeChangedWindows();
         }
     }
 
-    public void onAppWindowTransitionLocked(int displayId, int transition) {
+    void onAppWindowTransition(int displayId, int transition) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".onAppWindowTransition",
+                    "displayId=" + displayId + "; transition=" + transition);
+        }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.onAppWindowTransitionLocked(displayId, transition);
+            displayMagnifier.onAppWindowTransition(displayId, transition);
         }
         // Not relevant for the window observer.
     }
 
-    public void onWindowTransitionLocked(WindowState windowState, int transition) {
+    void onWindowTransition(WindowState windowState, int transition) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".onWindowTransition",
+                    "windowState={" + windowState + "}; transition=" + transition);
+        }
         final int displayId = windowState.getDisplayId();
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.onWindowTransitionLocked(windowState, transition);
+            displayMagnifier.onWindowTransition(windowState, transition);
         }
         final WindowsForAccessibilityObserver windowsForA11yObserver =
                 mWindowsForAccessibilityObserver.get(displayId);
         if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+            windowsForA11yObserver.scheduleComputeChangedWindows();
         }
     }
 
-    public void onWindowFocusChangedNotLocked(int displayId) {
+    void onWindowFocusChangedNot(int displayId) {
         // Not relevant for the display magnifier.
-
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(
+                    TAG + ".onWindowFocusChangedNot", "displayId=" + displayId);
+        }
         WindowsForAccessibilityObserver observer = null;
         synchronized (mService.mGlobalLock) {
             final WindowsForAccessibilityObserver windowsForA11yObserver =
@@ -280,7 +365,7 @@
             }
         }
         if (observer != null) {
-            observer.performComputeChangedWindowsNotLocked(false);
+            observer.performComputeChangedWindows(false);
         }
         // Since we abandon initializing observers if no window has focus, make sure all observers
         // are initialized.
@@ -311,7 +396,7 @@
         boolean areAllObserversInitialized = true;
         for (int i = unInitializedObservers.size() - 1; i >= 0; --i) {
             final  WindowsForAccessibilityObserver observer = unInitializedObservers.get(i);
-            observer.performComputeChangedWindowsNotLocked(true);
+            observer.performComputeChangedWindows(true);
             areAllObserversInitialized &= observer.mInitialized;
         }
         synchronized (mService.mGlobalLock) {
@@ -324,50 +409,89 @@
      * another display is also taken into consideration.
      * @param displayIds the display ids of displays when the situation happens.
      */
-    public void onSomeWindowResizedOrMovedLocked(int... displayIds) {
+    void onSomeWindowResizedOrMoved(int... displayIds) {
+        onSomeWindowResizedOrMovedWithCallingUid(Binder.getCallingUid(), displayIds);
+    }
+
+    void onSomeWindowResizedOrMovedWithCallingUid(int callingUid, int... displayIds) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(
+                    TAG + ".onSomeWindowResizedOrMoved",
+                    "displayIds={" + displayIds.toString() + "}",
+                    "".getBytes(),
+                    callingUid);
+        }
         // Not relevant for the display magnifier.
         for (int i = 0; i < displayIds.length; i++) {
             final WindowsForAccessibilityObserver windowsForA11yObserver =
                     mWindowsForAccessibilityObserver.get(displayIds[i]);
             if (windowsForA11yObserver != null) {
-                windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+                windowsForA11yObserver.scheduleComputeChangedWindows();
             }
         }
     }
 
-    public void drawMagnifiedRegionBorderIfNeededLocked(int displayId,
-            SurfaceControl.Transaction t) {
+    void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(
+                    TAG + ".drawMagnifiedRegionBorderIfNeeded",
+                    "displayId=" + displayId + "; transaction={" + t + "}");
+        }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(t);
+            displayMagnifier.drawMagnifiedRegionBorderIfNeeded(t);
         }
         // Not relevant for the window observer.
     }
 
-    public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
+    MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".getMagnificationSpecForWindow",
+                    "windowState={" + windowState + "}");
+        }
         final int displayId = windowState.getDisplayId();
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            return displayMagnifier.getMagnificationSpecForWindowLocked(windowState);
+            return displayMagnifier.getMagnificationSpecForWindow(windowState);
         }
         return null;
     }
 
-    public boolean hasCallbacksLocked() {
+    boolean hasCallbacks() {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".hasCallbacks");
+        }
         return (mDisplayMagnifiers.size() > 0
                 || mWindowsForAccessibilityObserver.size() > 0);
     }
 
-    public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) {
+    void setForceShowMagnifiableBounds(int displayId, boolean show) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".setForceShowMagnifiableBounds",
+                    "displayId=" + displayId + "; show=" + show);
+        }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.setForceShowMagnifiableBoundsLocked(show);
+            displayMagnifier.setForceShowMagnifiableBounds(show);
             displayMagnifier.showMagnificationBoundsIfNeeded();
         }
     }
 
-    public void handleWindowObserverOfEmbeddedDisplayLocked(int embeddedDisplayId,
+    void handleWindowObserverOfEmbeddedDisplay(int embeddedDisplayId,
             WindowState parentWindow) {
+        handleWindowObserverOfEmbeddedDisplay(
+                embeddedDisplayId, parentWindow, Binder.getCallingUid());
+    }
+
+    void handleWindowObserverOfEmbeddedDisplay(
+            int embeddedDisplayId, WindowState parentWindow, int callingUid) {
+        if (mAccessibilityTracing.isEnabled()) {
+            mAccessibilityTracing.logState(TAG + ".handleWindowObserverOfEmbeddedDisplay",
+                    "embeddedDisplayId=" + embeddedDisplayId + "; parentWindowState={"
+                    + parentWindow + "}",
+                    "".getBytes(),
+                    callingUid);
+        }
         if (embeddedDisplayId == Display.DEFAULT_DISPLAY || parentWindow == null) {
             return;
         }
@@ -390,7 +514,7 @@
         }
     }
 
-    private static void populateTransformationMatrixLocked(WindowState windowState,
+    private static void populateTransformationMatrix(WindowState windowState,
             Matrix outMatrix) {
         windowState.getTransformationMatrix(sTempFloats, outMatrix);
     }
@@ -451,6 +575,7 @@
         private final Handler mHandler;
         private final DisplayContent mDisplayContent;
         private final Display mDisplay;
+        private final AccessibilityTracing mAccessibilityTracing;
 
         private final MagnificationCallbacks mCallbacks;
 
@@ -458,7 +583,7 @@
 
         private boolean mForceShowMagnifiableBounds = false;
 
-        public DisplayMagnifier(WindowManagerService windowManagerService,
+        DisplayMagnifier(WindowManagerService windowManagerService,
                 DisplayContent displayContent,
                 Display display,
                 MagnificationCallbacks callbacks) {
@@ -469,36 +594,58 @@
             mDisplay = display;
             mHandler = new MyHandler(mService.mH.getLooper());
             mMagnifedViewport = new MagnifiedViewport();
+            mAccessibilityTracing = AccessibilityTracing.getInstance(mService);
             mLongAnimationDuration = mDisplayContext.getResources().getInteger(
                     com.android.internal.R.integer.config_longAnimTime);
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".DisplayMagnifier.constructor",
+                        "windowManagerService={" + windowManagerService + "}; displayContent={"
+                                + displayContent + "}; display={" + display + "}; callbacks={"
+                                + callbacks + "}");
+            }
         }
 
-        public void setMagnificationSpecLocked(MagnificationSpec spec) {
-            mMagnifedViewport.updateMagnificationSpecLocked(spec);
-            mMagnifedViewport.recomputeBoundsLocked();
+        void setMagnificationSpec(MagnificationSpec spec) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(
+                        LOG_TAG + ".setMagnificationSpec", "spec={" + spec + "}");
+            }
+            mMagnifedViewport.updateMagnificationSpec(spec);
+            mMagnifedViewport.recomputeBounds();
 
             mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
             mService.scheduleAnimationLocked();
         }
 
-        public void setForceShowMagnifiableBoundsLocked(boolean show) {
+        void setForceShowMagnifiableBounds(boolean show) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(
+                        LOG_TAG + ".setForceShowMagnifiableBounds", "show=" + show);
+            }
             mForceShowMagnifiableBounds = show;
-            mMagnifedViewport.setMagnifiedRegionBorderShownLocked(show, true);
+            mMagnifedViewport.setMagnifiedRegionBorderShown(show, true);
         }
 
-        public boolean isForceShowingMagnifiableBoundsLocked() {
+        boolean isForceShowingMagnifiableBounds() {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".isForceShowingMagnifiableBounds");
+            }
             return mForceShowMagnifiableBounds;
         }
 
-        public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
+        void onRectangleOnScreenRequested(Rect rectangle) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(
+                        LOG_TAG + ".onRectangleOnScreenRequested", "rectangle={" + rectangle + "}");
+            }
             if (DEBUG_RECTANGLE_REQUESTED) {
                 Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
             }
-            if (!mMagnifedViewport.isMagnifyingLocked()) {
+            if (!mMagnifedViewport.isMagnifying()) {
                 return;
             }
             Rect magnifiedRegionBounds = mTempRect2;
-            mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
+            mMagnifedViewport.getMagnifiedFrameInContentCoords(magnifiedRegionBounds);
             if (magnifiedRegionBounds.contains(rectangle)) {
                 return;
             }
@@ -511,31 +658,42 @@
                     args).sendToTarget();
         }
 
-        public void onWindowLayersChangedLocked() {
+        void onWindowLayersChanged() {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".onWindowLayersChanged");
+            }
             if (DEBUG_LAYERS) {
                 Slog.i(LOG_TAG, "Layers changed.");
             }
-            mMagnifedViewport.recomputeBoundsLocked();
+            mMagnifedViewport.recomputeBounds();
             mService.scheduleAnimationLocked();
         }
 
-        public void onRotationChangedLocked(DisplayContent displayContent) {
+        void onRotationChanged(DisplayContent displayContent) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(
+                        LOG_TAG + ".onRotationChanged", "displayContent={" + displayContent + "}");
+            }
             if (DEBUG_ROTATION) {
                 final int rotation = displayContent.getRotation();
                 Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
                         + " displayId: " + displayContent.getDisplayId());
             }
-            mMagnifedViewport.onRotationChangedLocked(displayContent.getPendingTransaction());
+            mMagnifedViewport.onRotationChanged(displayContent.getPendingTransaction());
             mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
         }
 
-        public void onAppWindowTransitionLocked(int displayId, int transition) {
+        void onAppWindowTransition(int displayId, int transition) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".onAppWindowTransition",
+                        "displayId=" + displayId + "; transition=" + transition);
+            }
             if (DEBUG_WINDOW_TRANSITIONS) {
                 Slog.i(LOG_TAG, "Window transition: "
                         + AppTransition.appTransitionOldToString(transition)
                         + " displayId: " + displayId);
             }
-            final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+            final boolean magnifying = mMagnifedViewport.isMagnifying();
             if (magnifying) {
                 switch (transition) {
                     case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
@@ -550,13 +708,17 @@
             }
         }
 
-        public void onWindowTransitionLocked(WindowState windowState, int transition) {
+        void onWindowTransition(WindowState windowState, int transition) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".onWindowTransition",
+                        "windowState={" + windowState + "}; transition=" + transition);
+            }
             if (DEBUG_WINDOW_TRANSITIONS) {
                 Slog.i(LOG_TAG, "Window transition: "
                         + AppTransition.appTransitionOldToString(transition)
                         + " displayId: " + windowState.getDisplayId());
             }
-            final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+            final boolean magnifying = mMagnifedViewport.isMagnifying();
             final int type = windowState.mAttrs.type;
             switch (transition) {
                 case WindowManagerPolicy.TRANSIT_ENTER:
@@ -586,7 +748,7 @@
                         case WindowManager.LayoutParams.TYPE_QS_DIALOG:
                         case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
                             Rect magnifiedRegionBounds = mTempRect2;
-                            mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
+                            mMagnifedViewport.getMagnifiedFrameInContentCoords(
                                     magnifiedRegionBounds);
                             Rect touchableRegionBounds = mTempRect1;
                             windowState.getTouchableRegion(mTempRegion1);
@@ -604,8 +766,12 @@
             }
         }
 
-        public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
-            MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
+        MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".getMagnificationSpecForWindow",
+                        "windowState={" + windowState + "}");
+            }
+            MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
             if (spec != null && !spec.isNop()) {
                 if (!windowState.shouldMagnify()) {
                     return null;
@@ -614,24 +780,38 @@
             return spec;
         }
 
-        public void getMagnificationRegionLocked(Region outMagnificationRegion) {
+        void getMagnificationRegion(Region outMagnificationRegion) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".getMagnificationRegion",
+                        "outMagnificationRegion={" + outMagnificationRegion + "}");
+            }
             // Make sure we're working with the most current bounds
-            mMagnifedViewport.recomputeBoundsLocked();
-            mMagnifedViewport.getMagnificationRegionLocked(outMagnificationRegion);
+            mMagnifedViewport.recomputeBounds();
+            mMagnifedViewport.getMagnificationRegion(outMagnificationRegion);
         }
 
-        public void destroyLocked() {
+        void destroy() {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".destroy");
+            }
             mMagnifedViewport.destroyWindow();
         }
 
         // Can be called outside of a surface transaction
-        public void showMagnificationBoundsIfNeeded() {
+        void showMagnificationBoundsIfNeeded() {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".showMagnificationBoundsIfNeeded");
+            }
             mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
                     .sendToTarget();
         }
 
-        public void drawMagnifiedRegionBorderIfNeededLocked(SurfaceControl.Transaction t) {
-            mMagnifedViewport.drawWindowIfNeededLocked(t);
+        void drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
+                        "transition={" + t + "}");
+            }
+            mMagnifedViewport.drawWindowIfNeeded(t);
         }
 
         void dump(PrintWriter pw, String prefix) {
@@ -665,7 +845,7 @@
             private boolean mFullRedrawNeeded;
             private int mTempLayer = 0;
 
-            public MagnifiedViewport() {
+            MagnifiedViewport() {
                 mBorderWidth = mDisplayContext.getResources().getDimension(
                         com.android.internal.R.dimen.accessibility_magnification_indicator_width);
                 mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
@@ -681,14 +861,14 @@
                     mCircularPath = null;
                 }
 
-                recomputeBoundsLocked();
+                recomputeBounds();
             }
 
-            public void getMagnificationRegionLocked(@NonNull Region outMagnificationRegion) {
+            void getMagnificationRegion(@NonNull Region outMagnificationRegion) {
                 outMagnificationRegion.set(mMagnificationRegion);
             }
 
-            public void updateMagnificationSpecLocked(MagnificationSpec spec) {
+            void updateMagnificationSpec(MagnificationSpec spec) {
                 if (spec != null) {
                     mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
                 } else {
@@ -698,12 +878,12 @@
                 // to show the border. We will do so when the pending message is handled.
                 if (!mHandler.hasMessages(
                         MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
-                    setMagnifiedRegionBorderShownLocked(
-                            isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked(), true);
+                    setMagnifiedRegionBorderShown(
+                            isMagnifying() || isForceShowingMagnifiableBounds(), true);
                 }
             }
 
-            public void recomputeBoundsLocked() {
+            void recomputeBounds() {
                 mDisplay.getRealSize(mTempPoint);
                 final int screenWidth = mTempPoint.x;
                 final int screenHeight = mTempPoint.y;
@@ -721,7 +901,7 @@
 
                 SparseArray<WindowState> visibleWindows = mTempWindowStates;
                 visibleWindows.clear();
-                populateWindowsOnScreenLocked(visibleWindows);
+                populateWindowsOnScreen(visibleWindows);
 
                 final int visibleWindowCount = visibleWindows.size();
                 for (int i = visibleWindowCount - 1; i >= 0; i--) {
@@ -736,7 +916,7 @@
 
                     // Consider the touchable portion of the window
                     Matrix matrix = mTempMatrix;
-                    populateTransformationMatrixLocked(windowState, matrix);
+                    populateTransformationMatrix(windowState, matrix);
                     Region touchableRegion = mTempRegion3;
                     windowState.getTouchableRegion(touchableRegion);
                     Rect touchableFrame = mTempRect1;
@@ -848,24 +1028,24 @@
                 return letterboxBounds;
             }
 
-            public void onRotationChangedLocked(SurfaceControl.Transaction t) {
+            void onRotationChanged(SurfaceControl.Transaction t) {
                 // If we are showing the magnification border, hide it immediately so
                 // the user does not see strange artifacts during rotation. The screenshot
                 // used for rotation already has the border. After the rotation is complete
                 // we will show the border.
-                if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) {
-                    setMagnifiedRegionBorderShownLocked(false, false);
+                if (isMagnifying() || isForceShowingMagnifiableBounds()) {
+                    setMagnifiedRegionBorderShown(false, false);
                     final long delay = (long) (mLongAnimationDuration
                             * mService.getWindowAnimationScaleLocked());
                     Message message = mHandler.obtainMessage(
                             MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
                     mHandler.sendMessageDelayed(message, delay);
                 }
-                recomputeBoundsLocked();
+                recomputeBounds();
                 mWindow.updateSize(t);
             }
 
-            public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
+            void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
                 if (shown) {
                     mFullRedrawNeeded = true;
                     mOldMagnificationRegion.set(0, 0, 0, 0);
@@ -873,31 +1053,31 @@
                 mWindow.setShown(shown, animate);
             }
 
-            public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
+            void getMagnifiedFrameInContentCoords(Rect rect) {
                 MagnificationSpec spec = mMagnificationSpec;
                 mMagnificationRegion.getBounds(rect);
                 rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
                 rect.scale(1.0f / spec.scale);
             }
 
-            public boolean isMagnifyingLocked() {
+            boolean isMagnifying() {
                 return mMagnificationSpec.scale > 1.0f;
             }
 
-            public MagnificationSpec getMagnificationSpecLocked() {
+            MagnificationSpec getMagnificationSpec() {
                 return mMagnificationSpec;
             }
 
-            public void drawWindowIfNeededLocked(SurfaceControl.Transaction t) {
-                recomputeBoundsLocked();
+            void drawWindowIfNeeded(SurfaceControl.Transaction t) {
+                recomputeBounds();
                 mWindow.drawIfNeeded(t);
             }
 
-            public void destroyWindow() {
+            void destroyWindow() {
                 mWindow.releaseSurface();
             }
 
-            private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
+            private void populateWindowsOnScreen(SparseArray<WindowState> outWindows) {
                 mTempLayer = 0;
                 mDisplayContent.forAllWindows((w) -> {
                     if (w.isOnScreen() && w.isVisible()
@@ -929,7 +1109,7 @@
 
                 private boolean mInvalidated;
 
-                public ViewportWindow(Context context) {
+                ViewportWindow(Context context) {
                     SurfaceControl surfaceControl = null;
                     try {
                         mDisplay.getRealSize(mTempPoint);
@@ -971,7 +1151,7 @@
                     mInvalidated = true;
                 }
 
-                public void setShown(boolean shown, boolean animate) {
+                void setShown(boolean shown, boolean animate) {
                     synchronized (mService.mGlobalLock) {
                         if (mShown == shown) {
                             return;
@@ -986,13 +1166,13 @@
 
                 @SuppressWarnings("unused")
                 // Called reflectively from an animator.
-                public int getAlpha() {
+                int getAlpha() {
                     synchronized (mService.mGlobalLock) {
                         return mAlpha;
                     }
                 }
 
-                public void setAlpha(int alpha) {
+                void setAlpha(int alpha) {
                     synchronized (mService.mGlobalLock) {
                         if (mAlpha == alpha) {
                             return;
@@ -1005,7 +1185,7 @@
                     }
                 }
 
-                public void setBounds(Region bounds) {
+                void setBounds(Region bounds) {
                     synchronized (mService.mGlobalLock) {
                         if (mBounds.equals(bounds)) {
                             return;
@@ -1018,7 +1198,7 @@
                     }
                 }
 
-                public void updateSize(SurfaceControl.Transaction t) {
+                void updateSize(SurfaceControl.Transaction t) {
                     synchronized (mService.mGlobalLock) {
                         mDisplay.getRealSize(mTempPoint);
                         t.setBufferSize(mSurfaceControl, mTempPoint.x, mTempPoint.y);
@@ -1026,7 +1206,7 @@
                     }
                 }
 
-                public void invalidate(Rect dirtyRect) {
+                void invalidate(Rect dirtyRect) {
                     if (dirtyRect != null) {
                         mDirtyRect.set(dirtyRect);
                     } else {
@@ -1036,7 +1216,7 @@
                     mService.scheduleAnimationLocked();
                 }
 
-                public void drawIfNeeded(SurfaceControl.Transaction t) {
+                void drawIfNeeded(SurfaceControl.Transaction t) {
                     synchronized (mService.mGlobalLock) {
                         if (!mInvalidated) {
                             return;
@@ -1078,7 +1258,7 @@
                     }
                 }
 
-                public void releaseSurface() {
+                void releaseSurface() {
                     mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
                     mSurface.release();
                 }
@@ -1101,7 +1281,7 @@
 
                     private final ValueAnimator mShowHideFrameAnimator;
 
-                    public AnimationController(Context context, Looper looper) {
+                    AnimationController(Context context, Looper looper) {
                         super(looper);
                         mShowHideFrameAnimator = ObjectAnimator.ofInt(ViewportWindow.this,
                                 PROPERTY_NAME_ALPHA, MIN_ALPHA, MAX_ALPHA);
@@ -1114,7 +1294,7 @@
                         mShowHideFrameAnimator.setDuration(longAnimationDuration);
                     }
 
-                    public void onFrameShownStateChanged(boolean shown, boolean animate) {
+                    void onFrameShownStateChanged(boolean shown, boolean animate) {
                         obtainMessage(MSG_FRAME_SHOWN_STATE_CHANGED,
                                 shown ? 1 : 0, animate ? 1 : 0).sendToTarget();
                     }
@@ -1158,7 +1338,7 @@
             public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
             public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
 
-            public MyHandler(Looper looper) {
+            MyHandler(Looper looper) {
                 super(looper);
             }
 
@@ -1193,9 +1373,9 @@
 
                     case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
                         synchronized (mService.mGlobalLock) {
-                            if (mMagnifedViewport.isMagnifyingLocked()
-                                    || isForceShowingMagnifiableBoundsLocked()) {
-                                mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
+                            if (mMagnifedViewport.isMagnifying()
+                                    || isForceShowingMagnifiableBounds()) {
+                                mMagnifedViewport.setMagnifiedRegionBorderShown(true, true);
                                 mService.scheduleAnimationLocked();
                             }
                         }
@@ -1252,6 +1432,8 @@
 
         private final Handler mHandler;
 
+        private final AccessibilityTracing mAccessibilityTracing;
+
         private final WindowsForAccessibilityCallback mCallback;
 
         private final int mDisplayId;
@@ -1263,24 +1445,32 @@
         // Set to true if initializing window population complete.
         private boolean mInitialized;
 
-        public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
+        WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 int displayId,
                 WindowsForAccessibilityCallback callback) {
             mService = windowManagerService;
             mCallback = callback;
             mDisplayId = displayId;
             mHandler = new MyHandler(mService.mH.getLooper());
+            mAccessibilityTracing = AccessibilityTracing.getInstance(mService);
             mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
                     .getSendRecurringAccessibilityEventsInterval();
             computeChangedWindows(true);
         }
 
-        public void performComputeChangedWindowsNotLocked(boolean forceSend) {
+        void performComputeChangedWindows(boolean forceSend) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".performComputeChangedWindows",
+                        "forceSend=" + forceSend);
+            }
             mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
             computeChangedWindows(forceSend);
         }
 
-        public void scheduleComputeChangedWindowsLocked() {
+        void scheduleComputeChangedWindows() {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(LOG_TAG + ".scheduleComputeChangedWindows");
+            }
             if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
                 mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
                         mRecurringAccessibilityEventsIntervalMillis);
@@ -1307,7 +1497,11 @@
          *
          * @param forceSend Send the windows the accessibility even if they haven't changed.
          */
-        public void computeChangedWindows(boolean forceSend) {
+        void computeChangedWindows(boolean forceSend) {
+            if (mAccessibilityTracing.isEnabled()) {
+                mAccessibilityTracing.logState(
+                        LOG_TAG + ".computeChangedWindows", "forceSend=" + forceSend);
+            }
             if (DEBUG) {
                 Slog.i(LOG_TAG, "computeChangedWindows()");
             }
@@ -1343,7 +1537,7 @@
                 unaccountedSpace.set(0, 0, screenWidth, screenHeight);
 
                 final SparseArray<WindowState> visibleWindows = mTempWindowStates;
-                populateVisibleWindowsOnScreenLocked(visibleWindows);
+                populateVisibleWindowsOnScreen(visibleWindows);
                 Set<IBinder> addedWindows = mTempBinderSet;
                 addedWindows.clear();
 
@@ -1518,7 +1712,7 @@
 
             // Map the frame to get what appears on the screen.
             Matrix matrix = mTempMatrix;
-            populateTransformationMatrixLocked(windowState, matrix);
+            populateTransformationMatrix(windowState, matrix);
 
             forEachRect(touchableRegion, rect -> {
                 // Move to origin as all transforms are captured by the matrix.
@@ -1563,7 +1757,7 @@
                     && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
         }
 
-        private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
+        private void populateVisibleWindowsOnScreen(SparseArray<WindowState> outWindows) {
             final List<WindowState> tempWindowStatesList = new ArrayList<>();
             final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
             if (dc == null) {
@@ -1637,4 +1831,292 @@
             }
         }
     }
+
+    private static final class AccessibilityControllerInternalImpl
+            implements AccessibilityControllerInternal {
+
+        private static AccessibilityControllerInternal sInstance;
+        static AccessibilityControllerInternal getInstance(WindowManagerService service) {
+            synchronized (STATIC_LOCK) {
+                if (sInstance == null) {
+                    sInstance = new AccessibilityControllerInternalImpl(service);
+                }
+                return sInstance;
+            }
+        }
+
+        private final AccessibilityTracing mTracing;
+        private AccessibilityControllerInternalImpl(WindowManagerService service) {
+            mTracing = AccessibilityTracing.getInstance(service);
+        }
+
+        @Override
+        public void startTrace() {
+            mTracing.startTrace();
+        }
+
+        @Override
+        public void stopTrace() {
+            mTracing.stopTrace();
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return mTracing.isEnabled();
+        }
+
+        @Override
+        public void logTrace(
+                String where, String callingParams, byte[] a11yDump, int callingUid,
+                StackTraceElement[] stackTrace) {
+            mTracing.logState(where, callingParams, a11yDump, callingUid, stackTrace);
+        }
+    }
+
+    private static final class AccessibilityTracing {
+        private static AccessibilityTracing sInstance;
+        static AccessibilityTracing getInstance(WindowManagerService service) {
+            synchronized (STATIC_LOCK) {
+                if (sInstance == null) {
+                    sInstance = new AccessibilityTracing(service);
+                }
+                return sInstance;
+            }
+        }
+
+        private static final int BUFFER_CAPACITY = 4096 * 1024;
+        private static final String TRACE_FILENAME = "/data/misc/a11ytrace/a11y_trace.pb";
+        private static final String TRACE_DIRECTORY = "/data/misc/a11ytrace/";
+        private static final String TAG = "AccessibilityTracing";
+        private static final long MAGIC_NUMBER_VALUE =
+                ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+        private final Object mLock = new Object();
+        private final WindowManagerService mService;
+        private final File mTraceFile;
+        private final TraceBuffer mBuffer;
+        private final LogHandler mHandler;
+        private volatile boolean mEnabled;
+
+        AccessibilityTracing(WindowManagerService service) {
+            mService = service;
+            mTraceFile = new File(TRACE_FILENAME);
+            mBuffer = new TraceBuffer(BUFFER_CAPACITY);
+            HandlerThread workThread = new HandlerThread(TAG);
+            workThread.start();
+            mHandler = new LogHandler(workThread.getLooper());
+        }
+
+        /**
+         * Start the trace.
+         */
+        void startTrace() {
+            if (IS_USER) {
+                Slog.e(TAG, "Error: Tracing is not supported on user builds.");
+                return;
+            }
+            synchronized (mLock) {
+                try {
+                    Files.createDirectories(Paths.get(TRACE_DIRECTORY));
+                    mTraceFile.createNewFile();
+                } catch (Exception e) {
+                    Slog.e(TAG, "Error: Failed to create trace file.");
+                    return;
+                }
+                mEnabled = true;
+                mBuffer.resetBuffer();
+            }
+        }
+
+        /**
+         * Stops the trace and write the current buffer to disk
+         */
+        void stopTrace() {
+            if (IS_USER) {
+                Slog.e(TAG, "Error: Tracing is not supported on user builds.");
+                return;
+            }
+            synchronized (mLock) {
+                mEnabled = false;
+                if (mEnabled) {
+                    Slog.e(TAG, "Error: tracing enabled while waiting for flush.");
+                    return;
+                }
+                writeTraceToFile();
+            }
+        }
+
+        boolean isEnabled() {
+            return mEnabled;
+        }
+
+        /**
+         * Write an accessibility trace log entry.
+         */
+        void logState(String where) {
+            if (!mEnabled) {
+                return;
+            }
+            logState(where, "");
+        }
+
+        /**
+         * Write an accessibility trace log entry.
+         */
+        void logState(String where, String callingParams) {
+            if (!mEnabled) {
+                return;
+            }
+            logState(where, callingParams, "".getBytes());
+        }
+
+        /**
+         * Write an accessibility trace log entry.
+         */
+        void logState(String where, String callingParams, byte[] a11yDump) {
+            if (!mEnabled) {
+                return;
+            }
+            logState(where, callingParams, a11yDump, Binder.getCallingUid());
+        }
+
+        /**
+         * Write an accessibility trace log entry.
+         */
+        void logState(
+                String where, String callingParams, byte[] a11yDump, int callingUid) {
+            if (!mEnabled) {
+                return;
+            }
+            StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+
+            logState(where, callingParams, a11yDump, callingUid, stackTraceElements);
+        }
+
+        /**
+         * Write an accessibility trace log entry.
+         */
+        void logState(String where, String callingParams, byte[] a11yDump, int callingUid,
+                StackTraceElement[] stackTrace) {
+            if (!mEnabled) {
+                return;
+            }
+
+            log(where, callingParams, a11yDump, callingUid, stackTrace);
+        }
+
+        private  String toStackTraceString(StackTraceElement[] stackTraceElements) {
+            if (stackTraceElements == null) {
+                return "";
+            }
+            StringBuilder stringBuilder = new StringBuilder();
+            boolean skip = true;
+            for (int i = 0; i < stackTraceElements.length; i++) {
+                if (stackTraceElements[i].toString().contains(
+                            AccessibilityTracing.class.getSimpleName())) {
+                    skip = false;
+                } else if (!skip) {
+                    stringBuilder.append(stackTraceElements[i].toString()).append("\n");
+                }
+            }
+            return stringBuilder.toString();
+        }
+
+        /**
+         * Write the current state to the buffer
+         */
+        private void log(String where, String callingParams, byte[] a11yDump, int callingUid,
+                StackTraceElement[] stackTrace) {
+            SimpleDateFormat fm = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = SystemClock.elapsedRealtimeNanos();
+            args.arg2 = fm.format(new Date()).toString();
+            args.arg3 = where;
+            args.arg4 = Process.myPid() + ":" + Application.getProcessName();
+            args.arg5 = Thread.currentThread().getId() + ":" + Thread.currentThread().getName();
+            args.arg6 = callingUid;
+            args.arg7 = callingParams;
+            args.arg8 = stackTrace;
+            args.arg9 = a11yDump;
+            mHandler.obtainMessage(LogHandler.MESSAGE_LOG_TRACE_ENTRY, args).sendToTarget();
+        }
+
+        /**
+         * Writes the trace buffer to new file for the bugreport.
+         */
+        void writeTraceToFile() {
+            mHandler.sendEmptyMessage(LogHandler.MESSAGE_WRITE_FILE);
+        }
+
+        private class LogHandler extends Handler {
+            public static final int MESSAGE_LOG_TRACE_ENTRY = 1;
+            public static final int MESSAGE_WRITE_FILE = 2;
+
+            LogHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MESSAGE_LOG_TRACE_ENTRY: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        try {
+                            ProtoOutputStream os = new ProtoOutputStream();
+                            PackageManagerInternal pmInternal =
+                                    LocalServices.getService(PackageManagerInternal.class);
+
+                            long tokenOuter = os.start(ENTRY);
+                            String callingStack =
+                                    toStackTraceString((StackTraceElement[]) args.arg8);
+
+                            os.write(ELAPSED_REALTIME_NANOS, (long) args.arg1);
+                            os.write(CALENDAR_TIME, (String) args.arg2);
+                            os.write(WHERE, (String) args.arg3);
+                            os.write(PROCESS_NAME, (String) args.arg4);
+                            os.write(THREAD_ID_NAME, (String) args.arg5);
+                            os.write(CALLING_PKG, pmInternal.getNameForUid((int) args.arg6));
+                            os.write(CALLING_PARAMS, (String) args.arg7);
+                            os.write(CALLING_STACKS, callingStack);
+                            os.write(ACCESSIBILITY_SERVICE, (byte[]) args.arg9);
+
+                            long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+                            synchronized (mService.mGlobalLock) {
+                                mService.dumpDebugLocked(os, WindowTraceLogLevel.ALL);
+                            }
+                            os.end(tokenInner);
+
+                            os.end(tokenOuter);
+                            synchronized (mLock) {
+                                mBuffer.add(os);
+                            }
+                        } catch (Exception e) {
+                            Slog.e(TAG, "Exception while tracing state", e);
+                        }
+                        break;
+                    }
+                    case MESSAGE_WRITE_FILE: {
+                        synchronized (mLock) {
+                            writeTraceToFileInternal();
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Writes the trace buffer to disk.
+         */
+        private void writeTraceToFileInternal() {
+            try {
+                ProtoOutputStream proto = new ProtoOutputStream();
+                proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+                mBuffer.writeTraceToFile(mTraceFile, proto);
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to write buffer to file", e);
+            }
+        }
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c25f1b4..771b712 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -54,6 +54,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -63,6 +64,7 @@
 import android.view.RemoteAnimationDefinition;
 
 import com.android.internal.app.AssistUtils;
+import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
@@ -99,6 +101,17 @@
     }
 
     @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(
+                    "ActivityClientController", e);
+        }
+    }
+
+    @Override
     public void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -962,7 +975,8 @@
             final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
             if (r != null && r.isState(Task.ActivityState.RESUMED, Task.ActivityState.PAUSING)) {
                 r.mDisplayContent.mAppTransition.overridePendingAppTransition(
-                        packageName, enterAnim, exitAnim, null, null);
+                        packageName, enterAnim, exitAnim, null, null,
+                        r.mOverrideTaskTransition);
             }
         }
         Binder.restoreCallingIdentity(origId);
@@ -1019,6 +1033,50 @@
     }
 
     @Override
+    public void restartActivityProcessIfVisible(IBinder token) {
+        ActivityTaskManagerService.enforceTaskPermission("restartActivityProcess");
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+                if (r != null) {
+                    r.restartProcessIfVisible();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    @Override
+    public void invalidateHomeTaskSnapshot(IBinder token) {
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+            if (r != null && r.isActivityTypeHome()) {
+                mService.mWindowManager.mTaskSnapshotController.removeSnapshotCache(
+                        r.getTask().mTaskId);
+            }
+        }
+    }
+
+    @Override
+    public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback,
+            CharSequence message) {
+        if (message != null) {
+            mService.mAmInternal.enforceCallingPermission(
+                    android.Manifest.permission.SHOW_KEYGUARD_MESSAGE, "dismissKeyguard");
+        }
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                mService.mKeyguardController.dismissKeyguard(token, callback, message);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    @Override
     public void registerRemoteAnimations(IBinder token, RemoteAnimationDefinition definition) {
         mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
                 "registerRemoteAnimations");
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 6bca484..3a0eb39 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -978,6 +978,7 @@
         final TransitionInfoSnapshot infoSnapshot =
                 new TransitionInfoSnapshot(info, r, (int) startupTimeMs);
         BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot));
+        mLastTransitionInfo.remove(r);
 
         if (!info.isInterestingToLoggerAndObserver()) {
             return infoSnapshot;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 509cbde..f29b57f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -204,6 +204,9 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
@@ -249,6 +252,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -303,13 +307,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
+import com.android.internal.policy.AttributeCache;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.AttributeCache;
 import com.android.server.LocalServices;
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.PendingIntentRecord;
@@ -321,6 +325,7 @@
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.Task.ActivityState;
 import com.android.server.wm.WindowManagerService.H;
+import com.android.server.wm.WindowManagerService.LetterboxBackgroundType;
 import com.android.server.wm.utils.InsetUtils;
 
 import com.google.android.collect.Sets;
@@ -673,6 +678,7 @@
     boolean mRequestForceTransition;
 
     boolean mEnteringAnimation;
+    boolean mOverrideTaskTransition;
 
     boolean mAppStopped;
     // A hint to override the window specified rotation animation, or -1 to use the window specified
@@ -859,6 +865,9 @@
                         pw.print(Integer.toHexString(taskDescription.getStatusBarColor()));
                         pw.print(" navigationBarColor=");
                         pw.println(Integer.toHexString(taskDescription.getNavigationBarColor()));
+                        pw.print(" backgroundColorFloating=");
+                        pw.println(Integer.toHexString(
+                                taskDescription.getBackgroundColorFloating()));
             }
         }
         if (results != null) {
@@ -1353,13 +1362,15 @@
             return;
         }
         final boolean surfaceReady = w.isDrawn()  // Regular case
-                || w.mWinAnimator.mSurfaceDestroyDeferred  // The preserved surface is still ready.
                 || w.isDragResizeChanged();  // Waiting for relayoutWindow to call preserveSurface.
-        final boolean needsLetterbox = surfaceReady && w.isLetterboxedAppWindow() && fillsParent();
+        final boolean needsLetterbox = surfaceReady && isLetterboxed(w);
+        updateRoundedCorners(w);
         if (needsLetterbox) {
             if (mLetterbox == null) {
                 mLetterbox = new Letterbox(() -> makeChildSurface(null),
-                        mWmService.mTransactionFactory);
+                        mWmService.mTransactionFactory,
+                        mWmService::isLetterboxActivityCornersRounded,
+                        this::getLetterboxBackgroundColor);
                 mLetterbox.attachInput(w);
             }
             getPosition(mTmpPoint);
@@ -1371,7 +1382,7 @@
             final Rect spaceToFill = transformedBounds != null
                     ? transformedBounds
                     : inMultiWindowMode()
-                            ? task.getBounds()
+                            ? getRootTask().getBounds()
                             : getRootTask().getParent().getBounds();
             mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
         } else if (mLetterbox != null) {
@@ -1379,6 +1390,52 @@
         }
     }
 
+    private Color getLetterboxBackgroundColor() {
+        final WindowState w = findMainWindow();
+        if (w == null || w.isLetterboxedForDisplayCutout()) {
+            return Color.valueOf(Color.BLACK);
+        }
+        @LetterboxBackgroundType int letterboxBackgroundType =
+                mWmService.getLetterboxBackgroundType();
+        switch (letterboxBackgroundType) {
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+                if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) {
+                    return Color.valueOf(taskDescription.getBackgroundColorFloating());
+                }
+                return mWmService.getLetterboxBackgroundColor();
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+                if (taskDescription != null && taskDescription.getBackgroundColor() != 0) {
+                    return Color.valueOf(taskDescription.getBackgroundColor());
+                }
+                // Falling through
+            case LETTERBOX_BACKGROUND_SOLID_COLOR:
+                return mWmService.getLetterboxBackgroundColor();
+        }
+        throw new AssertionError(
+                "Unexpected letterbox background type: " + letterboxBackgroundType);
+    }
+
+    /** @return {@code true} when main window is letterboxed and activity isn't transparent. */
+    private boolean isLetterboxed(WindowState mainWindow) {
+        return mainWindow.isLetterboxedAppWindow() && fillsParent();
+    }
+
+    private void updateRoundedCorners(WindowState mainWindow) {
+        int cornersRadius =
+                // Don't round corners if letterboxed only for display cutout.
+                isLetterboxed(mainWindow) && !mainWindow.isLetterboxedForDisplayCutout()
+                        ? Math.max(0, mWmService.getLetterboxActivityCornersRadius()) : 0;
+        setCornersRadius(mainWindow, cornersRadius);
+    }
+
+    private void setCornersRadius(WindowState mainWindow, int cornersRadius) {
+        final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
+        if (windowSurface != null && windowSurface.isValid()) {
+            Transaction transaction = getPendingTransaction();
+            transaction.setCornerRadius(windowSurface, cornersRadius);
+        }
+    }
+
     void updateLetterboxSurface(WindowState winHint) {
         final WindowState w = findMainWindow();
         if (w != winHint && winHint != null && w != null) {
@@ -1408,9 +1465,10 @@
     }
 
     /**
-     * @see Letterbox#notIntersectsOrFullyContains(Rect)
+     * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent
+     *     when the current activity is displayed.
      */
-    boolean letterboxNotIntersectsOrFullyContains(Rect rect) {
+    boolean isFullyTransparentBarAllowed(Rect rect) {
         return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
     }
 
@@ -1565,6 +1623,8 @@
             if (rotationAnimation >= 0) {
                 mRotationAnimationHint = rotationAnimation;
             }
+
+            mOverrideTaskTransition = options.getOverrideTaskTransition();
         }
 
         ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService(
@@ -2156,6 +2216,7 @@
     }
 
     /** @return Root task of this activity, null if there is no task. */
+    @Nullable
     Task getRootTask() {
         return task != null ? task.getRootTask() : null;
     }
@@ -2164,6 +2225,12 @@
         return task != null ? task.getRootTaskId() : INVALID_TASK_ID;
     }
 
+    /** @return the first organized parent task. */
+    @Nullable
+    Task getOrganizedTask() {
+        return task != null ? task.getOrganizedTask() : null;
+    }
+
     @Override
     @Nullable
     TaskDisplayArea getDisplayArea() {
@@ -3928,7 +3995,8 @@
                         pendingOptions.getCustomEnterResId(),
                         pendingOptions.getCustomExitResId(),
                         pendingOptions.getAnimationStartedListener(),
-                        pendingOptions.getAnimationFinishedListener());
+                        pendingOptions.getAnimationFinishedListener(),
+                        pendingOptions.getOverrideTaskTransition());
                 break;
             case ANIM_CLIP_REVEAL:
                 displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
@@ -6589,8 +6657,7 @@
                 // which point, the activity type is still undefined if it will be standard.
                 // For other non-standard types, the type is set in the constructor, so this should
                 // not be a problem.
-                && isActivityTypeStandardOrUndefined()
-                && !mAtmService.mForceResizableActivities;
+                && isActivityTypeStandardOrUndefined();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index ae20a72..7257478 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -37,15 +37,11 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.proto.ProtoOutputStream;
 import android.view.RemoteAnimationAdapter;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -55,10 +51,8 @@
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityStarter.DefaultFactory;
 import com.android.server.wm.ActivityStarter.Factory;
-import com.android.server.wm.ActivityTaskSupervisor.PendingActivityLaunch;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -87,35 +81,12 @@
     /** The result of the last home activity we attempted to start. */
     private int mLastHomeActivityStartResult;
 
-    /** A list of activities that are waiting to launch. */
-    private final ArrayList<ActivityTaskSupervisor.PendingActivityLaunch>
-            mPendingActivityLaunches = new ArrayList<>();
-
     private final Factory mFactory;
 
-    private final Handler mHandler;
-
     private final PendingRemoteAnimationRegistry mPendingRemoteAnimationRegistry;
 
     boolean mCheckedForSetup = false;
 
-    private final class StartHandler extends Handler {
-        public StartHandler(Looper looper) {
-            super(looper, null, true);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case DO_PENDING_ACTIVITY_LAUNCHES_MSG:
-                    synchronized (mService.mGlobalLock) {
-                        doPendingActivityLaunches(true);
-                    }
-                    break;
-            }
-        }
-    }
-
     /**
      * TODO(b/64750076): Capture information necessary for dump and
      * {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
@@ -134,7 +105,6 @@
             Factory factory) {
         mService = service;
         mSupervisor = supervisor;
-        mHandler = new StartHandler(mService.mH.getLooper());
         mFactory = factory;
         mFactory.setController(this);
         mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service.mGlobalLock,
@@ -514,45 +484,6 @@
         return START_SUCCESS;
     }
 
-    void schedulePendingActivityLaunches(long delayMs) {
-        mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
-        Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
-        mHandler.sendMessageDelayed(msg, delayMs);
-    }
-
-    void doPendingActivityLaunches(boolean doResume) {
-        while (!mPendingActivityLaunches.isEmpty()) {
-            final PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
-            final boolean resume = doResume && mPendingActivityLaunches.isEmpty();
-            final ActivityStarter starter = obtainStarter(null /* intent */,
-                    "pendingActivityLaunch");
-            try {
-                starter.startResolvedActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags,
-                        resume, pal.r.getOptions(), null, pal.intentGrants);
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e);
-                pal.sendErrorResult(e.getMessage());
-            }
-        }
-    }
-
-    void addPendingActivityLaunch(PendingActivityLaunch launch) {
-        mPendingActivityLaunches.add(launch);
-    }
-
-    boolean clearPendingActivityLaunches(String packageName) {
-        final int pendingLaunches = mPendingActivityLaunches.size();
-
-        for (int palNdx = pendingLaunches - 1; palNdx >= 0; --palNdx) {
-            final PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
-            final ActivityRecord r = pal.r;
-            if (r != null && r.packageName.equals(packageName)) {
-                mPendingActivityLaunches.remove(palNdx);
-            }
-        }
-        return mPendingActivityLaunches.size() < pendingLaunches;
-    }
-
     void registerRemoteAnimationForNextActivityStart(String packageName,
             RemoteAnimationAdapter adapter) {
         mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter);
@@ -609,10 +540,4 @@
             pw.println("(nothing)");
         }
     }
-
-    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        for (PendingActivityLaunch activity: mPendingActivityLaunches) {
-            activity.r.writeIdentifierToProto(proto, fieldId);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4fa4a67..79f8229 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -122,7 +122,6 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
-import com.android.server.wm.ActivityTaskSupervisor.PendingActivityLaunch;
 import com.android.server.wm.LaunchParamsController.LaunchParams;
 
 import java.io.PrintWriter;
@@ -1171,42 +1170,19 @@
             r.appTimeTracker = sourceRecord.appTimeTracker;
         }
 
-        final Task rootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
-
-        // If we are starting an activity that is not from the same uid as the currently resumed
-        // one, check whether app switches are allowed.
-        if (voiceSession == null && rootTask != null && (rootTask.getResumedActivity() == null
-                || rootTask.getResumedActivity().info.applicationInfo.uid != realCallingUid)) {
-            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
-                    realCallingPid, realCallingUid, "Activity start")) {
-                if (!(restrictedBgActivity && handleBackgroundActivityAbort(r))) {
-                    mController.addPendingActivityLaunch(new PendingActivityLaunch(r,
-                            sourceRecord, startFlags, rootTask, callerApp, intentGrants));
-                }
-                ActivityOptions.abort(checkedOptions);
-                return ActivityManager.START_SWITCHES_CANCELED;
-            }
+        // Only allow app switching to be resumed if activity is not a restricted background
+        // activity and target app is not home process, otherwise any background activity
+        // started in background task can stop home button protection mode.
+        // As the targeted app is not a home process and we don't need to wait for the 2nd
+        // activity to be started to resume app switching, we can just enable app switching
+        // directly.
+        WindowProcessController homeProcess = mService.mHomeProcess;
+        boolean isHomeProcess = homeProcess != null
+                && aInfo.applicationInfo.uid == homeProcess.mUid;
+        if (!restrictedBgActivity && !isHomeProcess) {
+            mService.resumeAppSwitches();
         }
 
-        if (mService.getBalAppSwitchesProtectionEnabled()) {
-            // Only allow app switching to be resumed if activity is not a restricted background
-            // activity and target app is not home process, otherwise any background activity
-            // started in background task can stop home button protection mode.
-            // As the targeted app is not a home process and we don't need to wait for the 2nd
-            // activity to be started to resume app switching, we can just enable app switching
-            // directly.
-            WindowProcessController homeProcess = mService.mHomeProcess;
-            boolean isHomeProcess = homeProcess != null
-                    && aInfo.applicationInfo.uid == homeProcess.mUid;
-            if (!restrictedBgActivity && !isHomeProcess) {
-                mService.resumeAppSwitches();
-            }
-        } else {
-            mService.onStartActivitySetDidAppSwitch();
-        }
-
-        mController.doPendingActivityLaunches(false);
-
         mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
                 request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask,
                 restrictedBgActivity, intentGrants);
@@ -1286,8 +1262,6 @@
             return false;
         }
 
-        // App switching will be allowed if BAL app switching flag is not enabled, or if
-        // its app switching rule allows it.
         // This is used to block background activity launch even if the app is still
         // visible to user after user clicking home button.
         final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed();
@@ -1438,7 +1412,6 @@
         Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                 + "; callingUid: " + callingUid
                 + "; appSwitchAllowed: " + appSwitchAllowed
-                + "; balAppSwitchEnabled: " + mService.getBalAppSwitchesProtectionEnabled()
                 + "; isCallingUidForeground: " + isCallingUidForeground
                 + "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow
                 + "; callingUidProcState: " + DebugUtils.valueToString(ActivityManager.class,
@@ -1841,8 +1814,7 @@
     }
 
     private Task computeTargetTask() {
-        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
-                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
+        if (mInTask == null && !mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
             // A new task should be created instead of using existing one.
             return null;
         } else if (mSourceRecord != null) {
@@ -2125,13 +2097,6 @@
             final ActivityRecord top = targetTask.performClearTaskForReuseLocked(mStartActivity,
                     mLaunchFlags);
 
-            // The above code can remove {@code reusedActivity} from the task, leading to the
-            // {@code ActivityRecord} removing its reference to the {@code Task}. The task
-            // reference is needed in the call below to {@link setTargetStackAndMoveToFrontIfNeeded}
-            if (targetTaskTop.getTask() == null) {
-                targetTask.addChild(targetTaskTop);
-            }
-
             if (top != null) {
                 if (top.isRootOfTask()) {
                     // Activity aliases may mean we use different intents for the top activity,
@@ -2539,7 +2504,9 @@
         // If bring to front is requested, and no result is requested and we have not been given
         // an explicit task to launch in to, and we can find a task that was started with this
         // same component, then instead of launching bring that one to the front.
-        putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
+        putIntoExistingTask &= !isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)
+                ? (mInTask == null && mStartActivity.resultTo == null)
+                : (mInTask == null);
         ActivityRecord intentActivity = null;
         if (putIntoExistingTask) {
             if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 081141c..7d2075c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -279,12 +279,6 @@
     public abstract void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition);
 
     /**
-     * This enforces {@code func} can only be called if either the caller is Recents activity or
-     * has {@code permission}.
-     */
-    public abstract void enforceCallerIsRecentsOrHasPermission(String permission, String func);
-
-    /**
      * Returns true if the app can close system dialogs. Otherwise it either throws a {@link
      * SecurityException} or returns false with a logcat message depending on whether the app
      * targets SDK level {@link android.os.Build.VERSION_CODES#S} or not.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 80add64..4bcef40 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -35,6 +35,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -129,6 +130,7 @@
 import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.ActivityThread;
 import android.app.AlertDialog;
+import android.app.AnrController;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.Dialog;
@@ -202,7 +204,6 @@
 import android.os.WorkSource;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.dreams.DreamActivity;
 import android.service.voice.IVoiceInteractionSession;
@@ -232,14 +233,13 @@
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.TransferPipe;
-import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.AttributeCache;
 import com.android.internal.policy.KeyguardDismissCallback;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.AttributeCache;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
@@ -324,12 +324,6 @@
     /** This activity is being relaunched due to a free-resize operation. */
     public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
 
-    /**
-     * Apps are blocked from starting activities in the foreground after the user presses home.
-     */
-    public static final String BLOCK_ACTIVITY_STARTS_AFTER_HOME_FLAG =
-            "am_block_activity_starts_after_home";
-
     Context mContext;
 
     /**
@@ -386,7 +380,6 @@
     volatile WindowProcessController mHeavyWeightProcess;
     boolean mHasHeavyWeightFeature;
     boolean mHasLeanbackFeature;
-    boolean mBlockActivityAfterHomeEnabled;
     /** The process of the top most activity. */
     volatile WindowProcessController mTopApp;
     /**
@@ -490,27 +483,19 @@
     /** Temporary to avoid allocations. */
     final StringBuilder mStringBuilder = new StringBuilder(256);
 
-    // Amount of time after a call to stopAppSwitches() during which we will
-    // prevent further untrusted switches from happening.
-    private static final long APP_SWITCH_DELAY_TIME = 5 * 1000;
-
     /**
-     * The time at which we will allow normal application switches again,
-     * after a call to {@link #stopAppSwitches()}.
+     * Whether normal application switches are allowed; a call to {@link #stopAppSwitches()
+     * disables this.
      */
-    private long mAppSwitchesAllowedTime;
-    /**
-     * This is set to true after the first switch after mAppSwitchesAllowedTime
-     * is set; any switches after that will clear the time.
-     */
-    private boolean mDidAppSwitch;
+    private volatile boolean mAppSwitchesAllowed = true;
 
     /**
      * Last stop app switches time, apps finished before this time cannot start background activity
      * even if they are in grace period.
      */
-    private long mLastStopAppSwitchesTime;
+    private volatile long mLastStopAppSwitchesTime;
 
+    private final List<AnrController> mAnrController = new ArrayList<>();
     IActivityController mController = null;
     boolean mControllerIsAMonkey = false;
 
@@ -715,6 +700,7 @@
         int OOM_ADJUSTMENT = 1;
         int LRU_UPDATE = 2;
         int PROCESS_CHANGE = 3;
+        int START_SERVICE = 4;
 
         int caller() default NONE;
     }
@@ -749,9 +735,6 @@
             mRecentTasks.onSystemReadyLocked();
             mTaskSupervisor.onSystemReady();
             mActivityClientController.onSystemReady();
-            mBlockActivityAfterHomeEnabled = DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                    BLOCK_ACTIVITY_STARTS_AFTER_HOME_FLAG, true);
         }
     }
 
@@ -1146,7 +1129,7 @@
             if (topFocusedRootTask != null && topFocusedRootTask.getResumedActivity() != null
                     && topFocusedRootTask.getResumedActivity().info.applicationInfo.uid
                     == Binder.getCallingUid()) {
-                mAppSwitchesAllowedTime = 0;
+                mAppSwitchesAllowed = true;
             }
         }
         return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null,
@@ -1598,7 +1581,7 @@
     @Override
     public void startRecentsActivity(Intent intent, long eventTime,
             @Nullable IRecentsAnimationRunner recentsAnimationRunner) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "startRecentsActivity()");
+        enforceTaskPermission("startRecentsActivity()");
         final int callingPid = Binder.getCallingPid();
         final int callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
@@ -1626,7 +1609,7 @@
 
     @Override
     public final int startActivityFromRecents(int taskId, Bundle bOptions) {
-        enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+        mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
                 "startActivityFromRecents()");
 
         final int callingPid = Binder.getCallingPid();
@@ -1756,7 +1739,7 @@
 
     @Override
     public RootTaskInfo getFocusedRootTaskInfo() throws RemoteException {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "getFocusedRootTaskInfo()");
+        enforceTaskPermission("getFocusedRootTaskInfo()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -1816,25 +1799,8 @@
     }
 
     @Override
-    public void restartActivityProcessIfVisible(IBinder activityToken) {
-        enforceTaskPermission("restartActivityProcess()");
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                final ActivityRecord r = ActivityRecord.isInRootTaskLocked(activityToken);
-                if (r == null) {
-                    return;
-                }
-                r.restartProcessIfVisible();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
-        }
-    }
-
-    @Override
     public boolean removeTask(int taskId) {
-        enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeTask()");
+        mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()");
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -1859,7 +1825,7 @@
 
     @Override
     public void removeAllVisibleRecentTasks() {
-        enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()");
+        mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()");
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -1898,8 +1864,7 @@
     @Override
     public ActivityManager.TaskDescription getTaskDescription(int id) {
         synchronized (mGlobalLock) {
-            enforceCallerIsRecentsOrHasPermission(
-                    MANAGE_ACTIVITY_TASKS, "getTaskDescription()");
+            enforceTaskPermission("getTaskDescription()");
             final Task tr = mRootWindowContainer.anyTaskForId(id,
                     MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
             if (tr != null) {
@@ -1911,10 +1876,16 @@
 
     @Override
     public boolean setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "setTaskWindowingMode()");
+        enforceTaskPermission("setTaskWindowingMode()");
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
             try {
+                if (isInLockTaskMode() && windowingMode != WINDOWING_MODE_FULLSCREEN) {
+                    Slog.w(TAG, "setTaskWindowingMode: Is in lock task mode="
+                            + getLockTaskModeState());
+                    return false;
+                }
+
                 if (WindowConfiguration.isSplitScreenWindowingMode(windowingMode)) {
                     return setTaskWindowingModeSplitScreen(taskId, windowingMode, toTop);
                 }
@@ -2002,10 +1973,7 @@
         final int callingPid = Binder.getCallingPid();
         final int callingUid = Binder.getCallingUid();
         assertPackageMatchesCallingUid(callingPackage);
-        if (!checkAppSwitchAllowedLocked(callingPid, callingUid, -1, -1, "Task to front")) {
-            SafeActivityOptions.abort(options);
-            return;
-        }
+
         final long origId = Binder.clearCallingIdentity();
         WindowProcessController callerApp = null;
         if (appThread != null) {
@@ -2086,76 +2054,44 @@
     }
 
     /**
-     * Return true if app switch protection will be handled by background activity launch logic.
-     */
-    boolean getBalAppSwitchesProtectionEnabled() {
-         return mBlockActivityAfterHomeEnabled;
-    }
-
-    /**
      * Return true if app switching is allowed.
      */
     boolean getBalAppSwitchesAllowed() {
-        if (getBalAppSwitchesProtectionEnabled()) {
-            // Apps no longer able to start BAL again until app switching is resumed.
-            return mAppSwitchesAllowedTime == 0;
-        } else {
-            // Legacy behavior, BAL logic won't block app switching.
-            return true;
+        return mAppSwitchesAllowed;
+    }
+
+    /** Register an {@link AnrController} to control the ANR dialog behavior */
+    public void registerAnrController(AnrController controller) {
+        synchronized (mGlobalLock) {
+            mAnrController.add(controller);
         }
     }
 
-    boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
-            int callingPid, int callingUid, String name) {
-
-        // Background activity launch logic replaces app switching protection, so allow
-        // apps to start activity here now.
-        if (getBalAppSwitchesProtectionEnabled()) {
-            return true;
+    /** Unregister an {@link AnrController} */
+    public void unregisterAnrController(AnrController controller) {
+        synchronized (mGlobalLock) {
+            mAnrController.remove(controller);
         }
-
-        if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
-            return true;
-        }
-
-        if (getRecentTasks().isCallerRecents(sourceUid)) {
-            return true;
-        }
-
-        int perm = checkComponentPermission(STOP_APP_SWITCHES, sourcePid, sourceUid, -1, true);
-        if (perm == PackageManager.PERMISSION_GRANTED) {
-            return true;
-        }
-        if (checkAllowAppSwitchUid(sourceUid)) {
-            return true;
-        }
-
-        // If the actual IPC caller is different from the logical source, then
-        // also see if they are allowed to control app switches.
-        if (callingUid != -1 && callingUid != sourceUid) {
-            perm = checkComponentPermission(STOP_APP_SWITCHES, callingPid, callingUid, -1, true);
-            if (perm == PackageManager.PERMISSION_GRANTED) {
-                return true;
-            }
-            if (checkAllowAppSwitchUid(callingUid)) {
-                return true;
-            }
-        }
-
-        Slog.w(TAG, name + " request from " + sourceUid + " stopped");
-        return false;
     }
 
-    private boolean checkAllowAppSwitchUid(int uid) {
-        ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(UserHandle.getUserId(uid));
-        if (types != null) {
-            for (int i = types.size() - 1; i >= 0; i--) {
-                if (types.valueAt(i).intValue() == uid) {
-                    return true;
-                }
-            }
+    /** @return the max ANR delay from all registered {@link AnrController} instances */
+    public long getMaxAnrDelayMillis(ApplicationInfo info) {
+        if (info == null || info.packageName == null) {
+            return 0;
         }
-        return false;
+
+        final ArrayList<AnrController> controllers;
+        synchronized (mGlobalLock) {
+            controllers = new ArrayList<>(mAnrController);
+        }
+
+        final String packageName = info.packageName;
+        long maxDelayMs = 0;
+        for (AnrController controller : controllers) {
+            maxDelayMs = Math.max(maxDelayMs, controller.getAnrDelayMillis(packageName, info.uid));
+        }
+        maxDelayMs = Math.max(maxDelayMs, 0);
+        return maxDelayMs;
     }
 
     @Override
@@ -2210,7 +2146,7 @@
 
     @Override
     public void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "moveTaskToRootTask()");
+        enforceTaskPermission("moveTaskToRootTask()");
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -2248,11 +2184,6 @@
             throw new IllegalArgumentException("Calling setTaskWindowingModeSplitScreen with non"
                     + "split-screen mode: " + windowingMode);
         }
-        if (isInLockTaskMode()) {
-            Slog.w(TAG, "setTaskWindowingModeSplitScreen: Is in lock task mode="
-                    + getLockTaskModeState());
-            return false;
-        }
 
         final Task task = mRootWindowContainer.anyTaskForId(taskId,
                 MATCH_ATTACHED_TASK_ONLY);
@@ -2304,8 +2235,7 @@
      */
     @Override
     public void removeRootTasksInWindowingModes(int[] windowingModes) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS,
-                "removeRootTasksInWindowingModes()");
+        enforceTaskPermission("removeRootTasksInWindowingModes()");
 
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
@@ -2319,8 +2249,7 @@
 
     @Override
     public void removeRootTasksWithActivityTypes(int[] activityTypes) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS,
-                "removeRootTasksWithActivityTypes()");
+        enforceTaskPermission("removeRootTasksWithActivityTypes()");
 
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
@@ -2346,7 +2275,7 @@
 
     @Override
     public List<RootTaskInfo> getAllRootTaskInfos() {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "getAllRootTaskInfos()");
+        enforceTaskPermission("getAllRootTaskInfos()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -2359,7 +2288,7 @@
 
     @Override
     public RootTaskInfo getRootTaskInfo(int windowingMode, int activityType) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "getRootTaskInfo()");
+        enforceTaskPermission("getRootTaskInfo()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -2372,8 +2301,7 @@
 
     @Override
     public List<RootTaskInfo> getAllRootTaskInfosOnDisplay(int displayId) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS,
-                "getAllRootTaskInfosOnDisplay()");
+        enforceTaskPermission("getAllRootTaskInfosOnDisplay()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -2387,7 +2315,7 @@
     @Override
     public RootTaskInfo getRootTaskInfoOnDisplay(int windowingMode, int activityType,
             int displayId) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "getRootTaskInfoOnDisplay()");
+        enforceTaskPermission("getRootTaskInfoOnDisplay()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -2400,7 +2328,7 @@
 
     @Override
     public void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "cancelRecentsAnimation()");
+        enforceTaskPermission("cancelRecentsAnimation()");
         final long callingUid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -2565,6 +2493,13 @@
             if (referrer != null) {
                 pae.extras.putParcelable(Intent.EXTRA_REFERRER, referrer);
             }
+            if (!pae.activity.isAttached()) {
+                // Skip directly because the caller activity may have been destroyed. If a caller
+                // is waiting for the assist data, it will be notified by timeout
+                // (see PendingAssistExtras#run()) and then pendingAssistExtrasTimedOut will clean
+                // up the request.
+                return;
+            }
             if (structure != null) {
                 // Pre-fill the task/activity component for all assist data receivers
                 structure.setTaskId(pae.activity.getTask().mTaskId);
@@ -2835,16 +2770,14 @@
     /** Sets the task stack listener that gets callbacks when a task stack changes. */
     @Override
     public void registerTaskStackListener(ITaskStackListener listener) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS,
-                "registerTaskStackListener()");
+        enforceTaskPermission("registerTaskStackListener()");
         mTaskChangeNotificationController.registerTaskStackListener(listener);
     }
 
     /** Unregister a task stack listener so that it stops receiving callbacks. */
     @Override
     public void unregisterTaskStackListener(ITaskStackListener listener) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS,
-                "unregisterTaskStackListener()");
+        enforceTaskPermission("unregisterTaskStackListener()");
         mTaskChangeNotificationController.unregisterTaskStackListener(listener);
     }
 
@@ -2897,19 +2830,6 @@
                 permission, Binder.getCallingPid(), Binder.getCallingUid());
     }
 
-    /** This can be called with or without the global lock held. */
-    void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
-        if (getRecentTasks().isCallerRecents(Binder.getCallingUid())) {
-            return;
-        }
-
-        if (permission.equals(MANAGE_ACTIVITY_TASKS) || permission.equals(MANAGE_ACTIVITY_STACKS)) {
-            enforceTaskPermission(func);
-        } else {
-            mAmInternal.enforceCallingPermission(permission, func);
-        }
-    }
-
     /**
      * Returns true if the app can close system dialogs. Otherwise it either throws a {@link
      * SecurityException} or returns false with a logcat message depending on whether the app
@@ -3352,7 +3272,7 @@
             // If the keyguard is showing or occluded, then try and dismiss it before
             // entering picture-in-picture (this will prompt the user to authenticate if the
             // device is currently locked).
-            dismissKeyguard(r.appToken, new KeyguardDismissCallback() {
+            mActivityClientController.dismissKeyguard(r.appToken, new KeyguardDismissCallback() {
                 @Override
                 public void onDismissSucceeded() {
                     mH.post(enterPipRunnable);
@@ -3370,7 +3290,7 @@
     public void resizePrimarySplitScreen(Rect dockedBounds, Rect tempDockedTaskBounds,
             Rect tempDockedTaskInsetBounds,
             Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "resizePrimarySplitScreen()");
+        enforceTaskPermission("resizePrimarySplitScreen()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -3408,7 +3328,7 @@
 
     @Override
     public void setSplitScreenResizing(boolean resizing) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "setSplitScreenResizing()");
+        enforceTaskPermission("setSplitScreenResizing()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -3477,26 +3397,8 @@
     }
 
     @Override
-    public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback,
-            CharSequence message) {
-        if (message != null) {
-            mAmInternal.enforceCallingPermission(
-                    Manifest.permission.SHOW_KEYGUARD_MESSAGE, "dismissKeyguard()");
-        }
-        final long callingId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                mKeyguardController.dismissKeyguard(token, callback, message);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(callingId);
-        }
-    }
-
-    @Override
     public void cancelTaskWindowTransition(int taskId) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS,
-                "cancelTaskWindowTransition()");
+        enforceTaskPermission("cancelTaskWindowTransition()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -3515,7 +3417,7 @@
 
     @Override
     public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
-        enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
+        mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
             return getTaskSnapshot(taskId, isLowResolution, true /* restoreFromDisk */);
@@ -3539,17 +3441,6 @@
         return task.getSnapshot(isLowResolution, restoreFromDisk);
     }
 
-    @Override
-    public void invalidateHomeTaskSnapshot(IBinder token) {
-        synchronized (mGlobalLock) {
-            final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
-            if (r == null || !r.isActivityTypeHome()) {
-                return;
-            }
-            mWindowManager.mTaskSnapshotController.removeSnapshotCache(r.getTask().mTaskId);
-        }
-    }
-
     /** Return the user id of the last resumed activity. */
     @Override
     public @UserIdInt
@@ -3661,26 +3552,18 @@
 
     @Override
     public void stopAppSwitches() {
-        enforceCallerIsRecentsOrHasPermission(STOP_APP_SWITCHES, "stopAppSwitches");
+        mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "stopAppSwitches");
         synchronized (mGlobalLock) {
-            mAppSwitchesAllowedTime = SystemClock.uptimeMillis() + APP_SWITCH_DELAY_TIME;
+            mAppSwitchesAllowed = false;
             mLastStopAppSwitchesTime = SystemClock.uptimeMillis();
-            mDidAppSwitch = false;
-            // If BAL app switching enabled, app switches are blocked not delayed.
-            if (!getBalAppSwitchesProtectionEnabled()) {
-                getActivityStartController().schedulePendingActivityLaunches(APP_SWITCH_DELAY_TIME);
-            }
         }
     }
 
     @Override
     public void resumeAppSwitches() {
-        enforceCallerIsRecentsOrHasPermission(STOP_APP_SWITCHES, "resumeAppSwitches");
+        mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "resumeAppSwitches");
         synchronized (mGlobalLock) {
-            // Note that we don't execute any pending app switches... we will
-            // let those wait until either the timeout, or the next start
-            // activity request.
-            mAppSwitchesAllowedTime = 0;
+            mAppSwitchesAllowed = true;
         }
     }
 
@@ -3688,19 +3571,6 @@
         return mLastStopAppSwitchesTime;
     }
 
-    void onStartActivitySetDidAppSwitch() {
-        if (mDidAppSwitch) {
-            // This is the second allowed switch since we stopped switches, so now just generally
-            // allow switches. Use case:
-            // - user presses home (switches disabled, switch to home, mDidAppSwitch now true);
-            // - user taps a home icon (coming from home so allowed, we hit here and now allow
-            // anyone to switch again).
-            mAppSwitchesAllowedTime = 0;
-        } else {
-            mDidAppSwitch = true;
-        }
-    }
-
     /** @return whether the system should disable UI modes incompatible with VR mode. */
     boolean shouldDisableNonVrUiLocked() {
         return mVrController.shouldDisableNonVrUiLocked();
@@ -4918,6 +4788,7 @@
     }
 
     /** A uid is considered to be foreground if it has a visible non-toast window. */
+    @HotPath(caller = HotPath.START_SERVICE)
     boolean hasActiveVisibleWindow(int uid) {
         if (mVisibleActivityProcessTracker.hasVisibleActivity(uid)) {
             return true;
@@ -5014,15 +4885,21 @@
         try {
             return super.onTransact(code, data, reply, flags);
         } catch (RuntimeException e) {
-            if (!(e instanceof SecurityException)) {
-                Slog.w(TAG, "Activity Task Manager onTransact aborts "
-                        + " UID:" + Binder.getCallingUid()
-                        + " PID:" + Binder.getCallingPid(), e);
-            }
-            throw e;
+            throw logAndRethrowRuntimeExceptionOnTransact(TAG, e);
         }
     }
 
+    /** Provides the full stack traces of non-security exception that occurs in onTransact. */
+    static RuntimeException logAndRethrowRuntimeExceptionOnTransact(String name,
+            RuntimeException e) {
+        if (!(e instanceof SecurityException)) {
+            Slog.w(TAG, name + " onTransact aborts"
+                    + " UID:" + Binder.getCallingUid()
+                    + " PID:" + Binder.getCallingPid(), e);
+        }
+        throw e;
+    }
+
     /**
      * Sets the corresponding {@link DisplayArea} information for the process global
      * configuration. To be called when we need to show IME on a different {@link DisplayArea}
@@ -5291,11 +5168,6 @@
         }
 
         @Override
-        public void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
-            ActivityTaskManagerService.this.enforceCallerIsRecentsOrHasPermission(permission, func);
-        }
-
-        @Override
         public boolean checkCanCloseSystemDialogs(int pid, int uid, @Nullable String packageName) {
             return ActivityTaskManagerService.this.checkCanCloseSystemDialogs(pid, uid,
                     packageName);
@@ -5822,15 +5694,12 @@
                 int userId) {
             synchronized (mGlobalLock) {
 
-                boolean didSomething =
-                        getActivityStartController().clearPendingActivityLaunches(packageName);
-                didSomething |= mRootWindowContainer.finishDisabledPackageActivities(packageName,
+                return mRootWindowContainer.finishDisabledPackageActivities(packageName,
                         null /* filterByClasses */, doit, evenPersistent, userId,
                         // Only remove the activities without process because the activities with
                         // attached process will be removed when handling process died with
                         // WindowProcessController#isRemoved == true.
                         true /* onlyRemoveNoProcess */);
-                return didSomething;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 0ad392b..de43643 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -142,7 +142,6 @@
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.UserState;
-import com.android.server.uri.NeededUriGrants;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 
 import java.io.FileDescriptor;
@@ -376,41 +375,6 @@
 
     private boolean mInitialized;
 
-    /**
-     * Description of a request to start a new activity, which has been held
-     * due to app switches being disabled.
-     */
-    static class PendingActivityLaunch {
-        final ActivityRecord r;
-        final ActivityRecord sourceRecord;
-        final int startFlags;
-        final Task rootTask;
-        final WindowProcessController callerApp;
-        final NeededUriGrants intentGrants;
-
-        PendingActivityLaunch(ActivityRecord r, ActivityRecord sourceRecord,
-                int startFlags, Task rootTask, WindowProcessController callerApp,
-                NeededUriGrants intentGrants) {
-            this.r = r;
-            this.sourceRecord = sourceRecord;
-            this.startFlags = startFlags;
-            this.rootTask = rootTask;
-            this.callerApp = callerApp;
-            this.intentGrants = intentGrants;
-        }
-
-        void sendErrorResult(String message) {
-            try {
-                if (callerApp != null && callerApp.hasThread()) {
-                    callerApp.getThread().scheduleCrash(message);
-                }
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Exception scheduling crash of failed "
-                        + "activity launcher sourceRecord=" + sourceRecord, e);
-            }
-        }
-    }
-
     public ActivityTaskSupervisor(ActivityTaskManagerService service, Looper looper) {
         mService = service;
         mLooper = looper;
diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java
index fde0369..c589fea 100644
--- a/services/core/java/com/android/server/wm/AlertWindowNotification.java
+++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java
@@ -39,7 +39,7 @@
 import android.os.Bundle;
 
 import com.android.internal.R;
-import com.android.server.policy.IconUtilities;
+import com.android.internal.util.ImageUtils;
 
 /** Displays an ongoing notification for a process displaying an alert window */
 class AlertWindowNotification {
@@ -54,7 +54,6 @@
     private final NotificationManager mNotificationManager;
     private final String mPackageName;
     private boolean mPosted;
-    private IconUtilities mIconUtilities;
 
     AlertWindowNotification(WindowManagerService service, String packageName) {
         mService = service;
@@ -63,7 +62,6 @@
                 (NotificationManager) mService.mContext.getSystemService(NOTIFICATION_SERVICE);
         mNotificationTag = CHANNEL_PREFIX + mPackageName;
         mRequestCode = sNextRequestCode++;
-        mIconUtilities = new IconUtilities(mService.mContext);
     }
 
     void post() {
@@ -126,8 +124,9 @@
 
         if (aInfo != null) {
             final Drawable drawable = pm.getApplicationIcon(aInfo);
-            if (drawable != null) {
-                final Bitmap bitmap = mIconUtilities.createIconBitmap(drawable);
+            int size = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
+            final Bitmap bitmap = ImageUtils.buildScaledBitmap(drawable, size, size);
+            if (bitmap != null) {
                 builder.setLargeIcon(bitmap);
             }
         }
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index e6b7585..7f0adca 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -101,10 +101,6 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mService.mGlobalLock) {
-                if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, -1, -1,
-                        "Move to front")) {
-                    return;
-                }
                 WindowProcessController callerApp = null;
                 if (appThread != null) {
                     callerApp = mService.getProcessController(appThread);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f8b4987..5b685b4 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -78,6 +78,10 @@
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
+import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
+import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
+import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
 import static com.android.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
@@ -86,6 +90,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+import static com.android.server.wm.WindowManagerInternal.KeyguardExitAnimationStartListener;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
 
@@ -96,9 +101,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.ResourceId;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -136,15 +138,12 @@
 import android.view.animation.ScaleAnimation;
 import android.view.animation.TranslateAnimation;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.DumpUtils.Dump;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
-import com.android.server.AttributeCache;
-import com.android.server.wm.animation.ClipRectLRAnimation;
-import com.android.server.wm.animation.ClipRectTBAnimation;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -184,6 +183,8 @@
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
 
+    private final TransitionAnimation mTransitionAnimation;
+
     private @TransitionFlags int mNextAppTransitionFlags = 0;
     private final ArrayList<Integer> mNextAppTransitionRequests = new ArrayList<>();
     private @TransitionOldType int mLastUsedAppTransition = TRANSIT_OLD_UNSET;
@@ -212,12 +213,6 @@
     private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
     private boolean mNextAppTransitionOverrideRequested;
 
-    // These are the possible states for the enter/exit activities during a thumbnail transition
-    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
-    private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_UP = 1;
-    private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN = 2;
-    private static final int THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN = 3;
-
     private String mNextAppTransitionPackage;
     // Used for thumbnail transitions. True if we're scaling up, false if scaling down
     private boolean mNextAppTransitionScaleUp;
@@ -263,6 +258,7 @@
     private long mLastClipRevealTransitionDuration = DEFAULT_APP_TRANSITION_DURATION;
 
     private final ArrayList<AppTransitionListener> mListeners = new ArrayList<>();
+    private KeyguardExitAnimationStartListener mKeyguardExitAnimationStartListener;
     private final ExecutorService mDefaultExecutor = Executors.newSingleThreadExecutor();
 
     private int mLastClipRevealMaxTranslation;
@@ -272,6 +268,7 @@
     private final boolean mLowRamRecentsEnabled;
 
     private final int mDefaultWindowAnimationStyleResId;
+    private boolean mOverrideTaskTransition;
 
     private RemoteAnimationController mRemoteAnimationController;
 
@@ -283,6 +280,7 @@
         mService = service;
         mHandler = new Handler(service.mH.getLooper());
         mDisplayContent = displayContent;
+        mTransitionAnimation = new TransitionAnimation(context, DEBUG_ANIM, TAG);
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.linear_out_slow_in);
         mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
@@ -450,7 +448,7 @@
                 AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
 
         if (mRemoteAnimationController != null) {
-            mRemoteAnimationController.goodToGo();
+            mRemoteAnimationController.goodToGo(transit);
         }
         return redoLayout;
     }
@@ -513,6 +511,11 @@
         mListeners.remove(listener);
     }
 
+    void registerKeygaurdExitAnimationStartListener(
+            KeyguardExitAnimationStartListener listener) {
+        mKeyguardExitAnimationStartListener = listener;
+    }
+
     public void notifyAppTransitionFinishedLocked(IBinder token) {
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onAppTransitionFinishedLocked(token);
@@ -555,242 +558,18 @@
     /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
     @VisibleForTesting
     int getAnimationStyleResId(@NonNull LayoutParams lp) {
-        int resId = lp.windowAnimations;
-        if (lp.type == LayoutParams.TYPE_APPLICATION_STARTING) {
-            // Note that we don't want application to customize starting window animation.
-            // Since this window is specific for displaying while app starting,
-            // application should not change its animation directly.
-            // In this case, it will use system resource to get default animation.
-            resId = mDefaultWindowAnimationStyleResId;
-        }
-        return resId;
-    }
-
-    private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
-        if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg="
-                + (lp != null ? lp.packageName : null)
-                + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
-        if (lp != null && lp.windowAnimations != 0) {
-            // If this is a system resource, don't try to load it from the
-            // application resources.  It is nice to avoid loading application
-            // resources if we can.
-            String packageName = lp.packageName != null ? lp.packageName : "android";
-            int resId = getAnimationStyleResId(lp);
-            if ((resId&0xFF000000) == 0x01000000) {
-                packageName = "android";
-            }
-            if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
-                    + packageName);
-            return AttributeCache.instance().get(packageName, resId,
-                    com.android.internal.R.styleable.WindowAnimation, mCurrentUserId);
-        }
-        return null;
-    }
-
-    private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
-        if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package="
-                + packageName + " resId=0x" + Integer.toHexString(resId));
-        if (packageName != null) {
-            if ((resId&0xFF000000) == 0x01000000) {
-                packageName = "android";
-            }
-            if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
-                    + packageName);
-            return AttributeCache.instance().get(packageName, resId,
-                    com.android.internal.R.styleable.WindowAnimation, mCurrentUserId);
-        }
-        return null;
-    }
-
-    Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
-        int resId = Resources.ID_NULL;
-        Context context = mContext;
-        if (animAttr >= 0) {
-            AttributeCache.Entry ent = getCachedAnimations(lp);
-            if (ent != null) {
-                context = ent.context;
-                resId = ent.array.getResourceId(animAttr, 0);
-            }
-        }
-        resId = updateToTranslucentAnimIfNeeded(resId, transit);
-        if (ResourceId.isValid(resId)) {
-            return loadAnimationSafely(context, resId);
-        }
-        return null;
-    }
-
-    private Animation loadAnimationRes(LayoutParams lp, int resId) {
-        Context context = mContext;
-        if (ResourceId.isValid(resId)) {
-            AttributeCache.Entry ent = getCachedAnimations(lp);
-            if (ent != null) {
-                context = ent.context;
-            }
-            return loadAnimationSafely(context, resId);
-        }
-        return null;
-    }
-
-    private Animation loadAnimationRes(String packageName, int resId) {
-        if (ResourceId.isValid(resId)) {
-            AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
-            if (ent != null) {
-                return loadAnimationSafely(ent.context, resId);
-            }
-        }
-        return null;
+        return mTransitionAnimation.getAnimationStyleResId(lp);
     }
 
     @VisibleForTesting
+    @Nullable
     Animation loadAnimationSafely(Context context, int resId) {
-        try {
-            return AnimationUtils.loadAnimation(context, resId);
-        } catch (NotFoundException e) {
-            Slog.w(TAG, "Unable to load animation resource", e);
-            return null;
-        }
+        return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
     }
 
-    private int updateToTranslucentAnimIfNeeded(int anim, @TransitionOldType int transit) {
-        if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN
-                && anim == R.anim.activity_open_enter) {
-            return R.anim.activity_translucent_open_enter;
-        }
-        if (transit == TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE
-                && anim == R.anim.activity_close_exit) {
-            return R.anim.activity_translucent_close_exit;
-        }
-        return anim;
-    }
-
-    /**
-     * Compute the pivot point for an animation that is scaling from a small
-     * rect on screen to a larger rect.  The pivot point varies depending on
-     * the distance between the inner and outer edges on both sides.  This
-     * function computes the pivot point for one dimension.
-     * @param startPos  Offset from left/top edge of outer rectangle to
-     * left/top edge of inner rectangle.
-     * @param finalScale The scaling factor between the size of the outer
-     * and inner rectangles.
-     */
-    private static float computePivot(int startPos, float finalScale) {
-
-        /*
-        Theorem of intercepting lines:
-
-          +      +   +-----------------------------------------------+
-          |      |   |                                               |
-          |      |   |                                               |
-          |      |   |                                               |
-          |      |   |                                               |
-        x |    y |   |                                               |
-          |      |   |                                               |
-          |      |   |                                               |
-          |      |   |                                               |
-          |      |   |                                               |
-          |      +   |             +--------------------+            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             |                    |            |
-          |          |             +--------------------+            |
-          |          |                                               |
-          |          |                                               |
-          |          |                                               |
-          |          |                                               |
-          |          |                                               |
-          |          |                                               |
-          |          |                                               |
-          |          +-----------------------------------------------+
-          |
-          |
-          |
-          |
-          |
-          |
-          |
-          |
-          |
-          +                                 ++
-                                         p  ++
-
-        scale = (x - y) / x
-        <=> x = -y / (scale - 1)
-        */
-        final float denom = finalScale-1;
-        if (Math.abs(denom) < .0001f) {
-            return startPos;
-        }
-        return -startPos / denom;
-    }
-
-    private Animation createScaleUpAnimationLocked(int transit, boolean enter,
-            Rect containingFrame) {
-        Animation a;
-        getDefaultNextAppTransitionStartRect(mTmpRect);
-        final int appWidth = containingFrame.width();
-        final int appHeight = containingFrame.height();
-        if (enter) {
-            // Entering app zooms out from the center of the initial rect.
-            float scaleW = mTmpRect.width() / (float) appWidth;
-            float scaleH = mTmpRect.height() / (float) appHeight;
-            Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    computePivot(mTmpRect.left, scaleW),
-                    computePivot(mTmpRect.top, scaleH));
-            scale.setInterpolator(mDecelerateInterpolator);
-
-            Animation alpha = new AlphaAnimation(0, 1);
-            alpha.setInterpolator(mThumbnailFadeOutInterpolator);
-
-            AnimationSet set = new AnimationSet(false);
-            set.addAnimation(scale);
-            set.addAnimation(alpha);
-            set.setDetachWallpaper(true);
-            a = set;
-        } else  if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN
-                || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) {
-            // If we are on top of the wallpaper, we need an animation that
-            // correctly handles the wallpaper staying static behind all of
-            // the animated elements.  To do this, will just have the existing
-            // element fade out.
-            a = new AlphaAnimation(1, 0);
-            a.setDetachWallpaper(true);
-        } else {
-            // For normal animations, the exiting element just holds in place.
-            a = new AlphaAnimation(1, 1);
-        }
-
-        // Pick the desired duration.  If this is an inter-activity transition,
-        // it  is the standard duration for that.  Otherwise we use the longer
-        // task transition duration.
-        final long duration;
-        switch (transit) {
-            case TRANSIT_OLD_ACTIVITY_OPEN:
-            case TRANSIT_OLD_ACTIVITY_CLOSE:
-                duration = mConfigShortAnimTime;
-                break;
-            default:
-                duration = DEFAULT_APP_TRANSITION_DURATION;
-                break;
-        }
-        a.setDuration(duration);
-        a.setFillAfter(true);
-        a.setInterpolator(mDecelerateInterpolator);
-        a.initialize(appWidth, appHeight, appWidth, appHeight);
-        return a;
+    @Nullable
+    Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
+        return mTransitionAnimation.loadAnimationAttr(lp, animAttr, transit);
     }
 
     private void getDefaultNextAppTransitionStartRect(Rect rect) {
@@ -825,27 +604,6 @@
     }
 
     /**
-     * @return the duration of the last clip reveal animation
-     */
-    long getLastClipRevealTransitionDuration() {
-        return mLastClipRevealTransitionDuration;
-    }
-
-    /**
-     * @return the maximum distance the app surface is traveling of the last clip reveal animation
-     */
-    int getLastClipRevealMaxTranslation() {
-        return mLastClipRevealMaxTranslation;
-    }
-
-    /**
-     * @return true if in the last app transition had a clip reveal animation, false otherwise
-     */
-    boolean hadClipRevealAnimation() {
-        return mLastHadClipReveal;
-    }
-
-    /**
      * Calculates the duration for the clip reveal animation. If the clip is "cut off", meaning that
      * the start rect is outside of the target rect, and there is a lot of movement going on.
      *
@@ -867,137 +625,21 @@
                 (MAX_CLIP_REVEAL_TRANSITION_DURATION - DEFAULT_APP_TRANSITION_DURATION));
     }
 
-    private Animation createClipRevealAnimationLocked(int transit, boolean enter, Rect appFrame,
-            Rect displayFrame) {
-        final Animation anim;
-        if (enter) {
-            final int appWidth = appFrame.width();
-            final int appHeight = appFrame.height();
-
-            // mTmpRect will contain an area around the launcher icon that was pressed. We will
-            // clip reveal from that area in the final area of the app.
-            getDefaultNextAppTransitionStartRect(mTmpRect);
-
-            float t = 0f;
-            if (appHeight > 0) {
-                t = (float) mTmpRect.top / displayFrame.height();
-            }
-            int translationY = mClipRevealTranslationY + (int)(displayFrame.height() / 7f * t);
-            int translationX = 0;
-            int translationYCorrection = translationY;
-            int centerX = mTmpRect.centerX();
-            int centerY = mTmpRect.centerY();
-            int halfWidth = mTmpRect.width() / 2;
-            int halfHeight = mTmpRect.height() / 2;
-            int clipStartX = centerX - halfWidth - appFrame.left;
-            int clipStartY = centerY - halfHeight - appFrame.top;
-            boolean cutOff = false;
-
-            // If the starting rectangle is fully or partially outside of the target rectangle, we
-            // need to start the clipping at the edge and then achieve the rest with translation
-            // and extending the clip rect from that edge.
-            if (appFrame.top > centerY - halfHeight) {
-                translationY = (centerY - halfHeight) - appFrame.top;
-                translationYCorrection = 0;
-                clipStartY = 0;
-                cutOff = true;
-            }
-            if (appFrame.left > centerX - halfWidth) {
-                translationX = (centerX - halfWidth) - appFrame.left;
-                clipStartX = 0;
-                cutOff = true;
-            }
-            if (appFrame.right < centerX + halfWidth) {
-                translationX = (centerX + halfWidth) - appFrame.right;
-                clipStartX = appWidth - mTmpRect.width();
-                cutOff = true;
-            }
-            final long duration = calculateClipRevealTransitionDuration(cutOff, translationX,
-                    translationY, displayFrame);
-
-            // Clip third of the from size of launch icon, expand to full width/height
-            Animation clipAnimLR = new ClipRectLRAnimation(
-                    clipStartX, clipStartX + mTmpRect.width(), 0, appWidth);
-            clipAnimLR.setInterpolator(mClipHorizontalInterpolator);
-            clipAnimLR.setDuration((long) (duration / 2.5f));
-
-            TranslateAnimation translate = new TranslateAnimation(translationX, 0, translationY, 0);
-            translate.setInterpolator(cutOff ? TOUCH_RESPONSE_INTERPOLATOR
-                    : mLinearOutSlowInInterpolator);
-            translate.setDuration(duration);
-
-            Animation clipAnimTB = new ClipRectTBAnimation(
-                    clipStartY, clipStartY + mTmpRect.height(),
-                    0, appHeight,
-                    translationYCorrection, 0,
-                    mLinearOutSlowInInterpolator);
-            clipAnimTB.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
-            clipAnimTB.setDuration(duration);
-
-            // Quick fade-in from icon to app window
-            final long alphaDuration = duration / 4;
-            AlphaAnimation alpha = new AlphaAnimation(0.5f, 1);
-            alpha.setDuration(alphaDuration);
-            alpha.setInterpolator(mLinearOutSlowInInterpolator);
-
-            AnimationSet set = new AnimationSet(false);
-            set.addAnimation(clipAnimLR);
-            set.addAnimation(clipAnimTB);
-            set.addAnimation(translate);
-            set.addAnimation(alpha);
-            set.setZAdjustment(Animation.ZORDER_TOP);
-            set.initialize(appWidth, appHeight, appWidth, appHeight);
-            anim = set;
-            mLastHadClipReveal = true;
-            mLastClipRevealTransitionDuration = duration;
-
-            // If the start rect was full inside the target rect (cutOff == false), we don't need
-            // to store the translation, because it's only used if cutOff == true.
-            mLastClipRevealMaxTranslation = cutOff
-                    ? Math.max(Math.abs(translationY), Math.abs(translationX)) : 0;
-        } else {
-            final long duration;
-            switch (transit) {
-                case TRANSIT_OLD_ACTIVITY_OPEN:
-                case TRANSIT_OLD_ACTIVITY_CLOSE:
-                    duration = mConfigShortAnimTime;
-                    break;
-                default:
-                    duration = DEFAULT_APP_TRANSITION_DURATION;
-                    break;
-            }
-            if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN
-                    || transit == TRANSIT_OLD_WALLPAPER_INTRA_CLOSE) {
-                // If we are on top of the wallpaper, we need an animation that
-                // correctly handles the wallpaper staying static behind all of
-                // the animated elements.  To do this, will just have the existing
-                // element fade out.
-                anim = new AlphaAnimation(1, 0);
-                anim.setDetachWallpaper(true);
-            } else {
-                // For normal animations, the exiting element just holds in place.
-                anim = new AlphaAnimation(1, 1);
-            }
-            anim.setInterpolator(mDecelerateInterpolator);
-            anim.setDuration(duration);
-            anim.setFillAfter(true);
-        }
-        return anim;
-    }
-
     /**
      * Prepares the specified animation with a standard duration, interpolator, etc.
      */
-    Animation prepareThumbnailAnimationWithDuration(Animation a, int appWidth, int appHeight,
-            long duration, Interpolator interpolator) {
-        if (duration > 0) {
-            a.setDuration(duration);
+    Animation prepareThumbnailAnimationWithDuration(@Nullable Animation a, int appWidth,
+            int appHeight, long duration, Interpolator interpolator) {
+        if (a != null) {
+            if (duration > 0) {
+                a.setDuration(duration);
+            }
+            a.setFillAfter(true);
+            if (interpolator != null) {
+                a.setInterpolator(interpolator);
+            }
+            a.initialize(appWidth, appHeight, appWidth, appHeight);
         }
-        a.setFillAfter(true);
-        if (interpolator != null) {
-            a.setInterpolator(interpolator);
-        }
-        a.initialize(appWidth, appHeight, appWidth, appHeight);
         return a;
     }
 
@@ -1069,8 +711,8 @@
     }
 
     Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
-        final Animation animation = loadAnimationRes(
-                "android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter);
+        final Animation animation =
+                mTransitionAnimation.loadCrossProfileAppThumbnailEnterAnimation();
         return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
                 appRect.height(), 0, null);
     }
@@ -1203,145 +845,6 @@
         return TOUCH_RESPONSE_INTERPOLATOR;
     }
 
-    /**
-     * This alternate animation is created when we are doing a thumbnail transition, for the
-     * activity that is leaving, and the activity that is entering.
-     */
-    Animation createAspectScaledThumbnailEnterExitAnimationLocked(int thumbTransitState,
-            int uiMode, int orientation, int transit, Rect containingFrame, Rect contentInsets,
-            @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean freeform,
-            WindowContainer container) {
-        Animation a;
-        final int appWidth = containingFrame.width();
-        final int appHeight = containingFrame.height();
-        getDefaultNextAppTransitionStartRect(mTmpRect);
-        final int thumbWidthI = mTmpRect.width();
-        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = mTmpRect.height();
-        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
-        final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left;
-        final int thumbStartY = mTmpRect.top - containingFrame.top;
-
-        switch (thumbTransitState) {
-            case THUMBNAIL_TRANSITION_ENTER_SCALE_UP:
-            case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
-                final boolean scaleUp = thumbTransitState == THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
-                if (freeform && scaleUp) {
-                    a = createAspectScaledThumbnailEnterFreeformAnimationLocked(
-                            containingFrame, surfaceInsets, container);
-                } else if (freeform) {
-                    a = createAspectScaledThumbnailExitFreeformAnimationLocked(
-                            containingFrame, surfaceInsets, container);
-                } else {
-                    AnimationSet set = new AnimationSet(true);
-
-                    // In portrait, we scale to fit the width
-                    mTmpFromClipRect.set(containingFrame);
-                    mTmpToClipRect.set(containingFrame);
-
-                    // Containing frame is in screen space, but we need the clip rect in the
-                    // app space.
-                    mTmpFromClipRect.offsetTo(0, 0);
-                    mTmpToClipRect.offsetTo(0, 0);
-
-                    // Exclude insets region from the source clip.
-                    mTmpFromClipRect.inset(contentInsets);
-                    mNextAppTransitionInsets.set(contentInsets);
-
-                    if (shouldScaleDownThumbnailTransition(uiMode, orientation)) {
-                        // We scale the width and clip to the top/left square
-                        float scale = thumbWidth /
-                                (appWidth - contentInsets.left - contentInsets.right);
-                        if (!mGridLayoutRecentsEnabled) {
-                            int unscaledThumbHeight = (int) (thumbHeight / scale);
-                            mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight;
-                        }
-
-                        mNextAppTransitionInsets.set(contentInsets);
-
-                        Animation scaleAnim = new ScaleAnimation(
-                                scaleUp ? scale : 1, scaleUp ? 1 : scale,
-                                scaleUp ? scale : 1, scaleUp ? 1 : scale,
-                                containingFrame.width() / 2f,
-                                containingFrame.height() / 2f + contentInsets.top);
-                        final float targetX = (mTmpRect.left - containingFrame.left);
-                        final float x = containingFrame.width() / 2f
-                                - containingFrame.width() / 2f * scale;
-                        final float targetY = (mTmpRect.top - containingFrame.top);
-                        float y = containingFrame.height() / 2f
-                                - containingFrame.height() / 2f * scale;
-
-                        // During transition may require clipping offset from any top stable insets
-                        // such as the statusbar height when statusbar is hidden
-                        if (mLowRamRecentsEnabled && contentInsets.top == 0 && scaleUp) {
-                            mTmpFromClipRect.top += stableInsets.top;
-                            y += stableInsets.top;
-                        }
-                        final float startX = targetX - x;
-                        final float startY = targetY - y;
-                        Animation clipAnim = scaleUp
-                                ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
-                                : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
-                        Animation translateAnim = scaleUp
-                                ? createCurvedMotion(startX, 0, startY - contentInsets.top, 0)
-                                : createCurvedMotion(0, startX, 0, startY - contentInsets.top);
-
-                        set.addAnimation(clipAnim);
-                        set.addAnimation(scaleAnim);
-                        set.addAnimation(translateAnim);
-
-                    } else {
-                        // In landscape, we don't scale at all and only crop
-                        mTmpFromClipRect.bottom = mTmpFromClipRect.top + thumbHeightI;
-                        mTmpFromClipRect.right = mTmpFromClipRect.left + thumbWidthI;
-
-                        Animation clipAnim = scaleUp
-                                ? new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect)
-                                : new ClipRectAnimation(mTmpToClipRect, mTmpFromClipRect);
-                        Animation translateAnim = scaleUp
-                                ? createCurvedMotion(thumbStartX, 0,
-                                thumbStartY - contentInsets.top, 0)
-                                : createCurvedMotion(0, thumbStartX, 0,
-                                        thumbStartY - contentInsets.top);
-
-                        set.addAnimation(clipAnim);
-                        set.addAnimation(translateAnim);
-                    }
-                    a = set;
-                    a.setZAdjustment(Animation.ZORDER_TOP);
-                }
-                break;
-            }
-            case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
-                // Previous app window during the scale up
-                if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
-                    // Fade out the source activity if we are animating to a wallpaper
-                    // activity.
-                    a = new AlphaAnimation(1, 0);
-                } else {
-                    a = new AlphaAnimation(1, 1);
-                }
-                break;
-            }
-            case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
-                // Target app window during the scale down
-                if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
-                    // Fade in the destination activity if we are animating from a wallpaper
-                    // activity.
-                    a = new AlphaAnimation(0, 1);
-                } else {
-                    a = new AlphaAnimation(1, 1);
-                }
-                break;
-            }
-            default:
-                throw new RuntimeException("Invalid thumbnail transition state");
-        }
-
-        return prepareThumbnailAnimationWithDuration(a, appWidth, appHeight,
-                getAspectScaleDuration(), getAspectScaleInterpolator());
-    }
-
     private Animation createAspectScaledThumbnailEnterFreeformAnimationLocked(Rect frame,
             @Nullable Rect surfaceInsets, WindowContainer container) {
         getNextAppTransitionStartRect(container, mTmpRect);
@@ -1408,8 +911,8 @@
             float scaleW = appWidth / thumbWidth;
             float scaleH = appHeight / thumbHeight;
             Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                    computePivot(mTmpRect.left, 1 / scaleW),
-                    computePivot(mTmpRect.top, 1 / scaleH));
+                    TransitionAnimation.computePivot(mTmpRect.left, 1 / scaleW),
+                    TransitionAnimation.computePivot(mTmpRect.top, 1 / scaleH));
             scale.setInterpolator(mDecelerateInterpolator);
 
             Animation alpha = new AlphaAnimation(1, 0);
@@ -1425,125 +928,14 @@
             float scaleW = appWidth / thumbWidth;
             float scaleH = appHeight / thumbHeight;
             a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                    computePivot(mTmpRect.left, 1 / scaleW),
-                    computePivot(mTmpRect.top, 1 / scaleH));
+                    TransitionAnimation.computePivot(mTmpRect.left, 1 / scaleW),
+                    TransitionAnimation.computePivot(mTmpRect.top, 1 / scaleH));
         }
 
         return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
     }
 
     /**
-     * This animation is created when we are doing a thumbnail transition, for the activity that is
-     * leaving, and the activity that is entering.
-     */
-    Animation createThumbnailEnterExitAnimationLocked(int thumbTransitState, Rect containingFrame,
-            int transit, WindowContainer container) {
-        final int appWidth = containingFrame.width();
-        final int appHeight = containingFrame.height();
-        final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container);
-        Animation a;
-        getDefaultNextAppTransitionStartRect(mTmpRect);
-        final int thumbWidthI = thumbnailHeader != null ? thumbnailHeader.getWidth() : appWidth;
-        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
-        final int thumbHeightI = thumbnailHeader != null ? thumbnailHeader.getHeight() : appHeight;
-        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
-
-        switch (thumbTransitState) {
-            case THUMBNAIL_TRANSITION_ENTER_SCALE_UP: {
-                // Entering app scales up with the thumbnail
-                float scaleW = thumbWidth / appWidth;
-                float scaleH = thumbHeight / appHeight;
-                a = new ScaleAnimation(scaleW, 1, scaleH, 1,
-                        computePivot(mTmpRect.left, scaleW),
-                        computePivot(mTmpRect.top, scaleH));
-                break;
-            }
-            case THUMBNAIL_TRANSITION_EXIT_SCALE_UP: {
-                // Exiting app while the thumbnail is scaling up should fade or stay in place
-                if (transit == TRANSIT_OLD_WALLPAPER_INTRA_OPEN) {
-                    // Fade out while bringing up selected activity. This keeps the
-                    // current activity from showing through a launching wallpaper
-                    // activity.
-                    a = new AlphaAnimation(1, 0);
-                } else {
-                    // noop animation
-                    a = new AlphaAnimation(1, 1);
-                }
-                break;
-            }
-            case THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN: {
-                // Entering the other app, it should just be visible while we scale the thumbnail
-                // down above it
-                a = new AlphaAnimation(1, 1);
-                break;
-            }
-            case THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN: {
-                // Exiting the current app, the app should scale down with the thumbnail
-                float scaleW = thumbWidth / appWidth;
-                float scaleH = thumbHeight / appHeight;
-                Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
-                        computePivot(mTmpRect.left, scaleW),
-                        computePivot(mTmpRect.top, scaleH));
-
-                Animation alpha = new AlphaAnimation(1, 0);
-
-                AnimationSet set = new AnimationSet(true);
-                set.addAnimation(scale);
-                set.addAnimation(alpha);
-                set.setZAdjustment(Animation.ZORDER_TOP);
-                a = set;
-                break;
-            }
-            default:
-                throw new RuntimeException("Invalid thumbnail transition state");
-        }
-
-        return prepareThumbnailAnimation(a, appWidth, appHeight, transit);
-    }
-
-    private Animation createRelaunchAnimation(Rect containingFrame, Rect contentInsets) {
-        getDefaultNextAppTransitionStartRect(mTmpFromClipRect);
-        final int left = mTmpFromClipRect.left;
-        final int top = mTmpFromClipRect.top;
-        mTmpFromClipRect.offset(-left, -top);
-        // TODO: Isn't that strange that we ignore exact position of the containingFrame?
-        mTmpToClipRect.set(0, 0, containingFrame.width(), containingFrame.height());
-        AnimationSet set = new AnimationSet(true);
-        float fromWidth = mTmpFromClipRect.width();
-        float toWidth = mTmpToClipRect.width();
-        float fromHeight = mTmpFromClipRect.height();
-        // While the window might span the whole display, the actual content will be cropped to the
-        // system decoration frame, for example when the window is docked. We need to take into
-        // account the visible height when constructing the animation.
-        float toHeight = mTmpToClipRect.height() - contentInsets.top - contentInsets.bottom;
-        int translateAdjustment = 0;
-        if (fromWidth <= toWidth && fromHeight <= toHeight) {
-            // The final window is larger in both dimensions than current window (e.g. we are
-            // maximizing), so we can simply unclip the new window and there will be no disappearing
-            // frame.
-            set.addAnimation(new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect));
-        } else {
-            // The disappearing window has one larger dimension. We need to apply scaling, so the
-            // first frame of the entry animation matches the old window.
-            set.addAnimation(new ScaleAnimation(fromWidth / toWidth, 1, fromHeight / toHeight, 1));
-            // We might not be going exactly full screen, but instead be aligned under the status
-            // bar using cropping. We still need to account for the cropped part, which will also
-            // be scaled.
-            translateAdjustment = (int) (contentInsets.top * fromHeight / toHeight);
-        }
-
-        // We animate the translation from the old position of the removed window, to the new
-        // position of the added window. The latter might not be full screen, for example docked for
-        // docked windows.
-        TranslateAnimation translate = new TranslateAnimation(left - containingFrame.left,
-                0, top - containingFrame.top - translateAdjustment, 0);
-        set.addAnimation(translate);
-        set.setDuration(DEFAULT_APP_TRANSITION_DURATION);
-        set.setZAdjustment(Animation.ZORDER_TOP);
-        return set;
-    }
-
-    /**
      * @return true if and only if the first frame of the transition can be skipped, i.e. the first
      *         frame of the transition doesn't change the visuals on screen, so we can start
      *         directly with the second one
@@ -1581,68 +973,74 @@
      *                      to the recents thumbnail and hence need to account for the surface being
      *                      bigger.
      */
+    @Nullable
     Animation loadAnimation(LayoutParams lp, int transit, boolean enter, int uiMode,
             int orientation, Rect frame, Rect displayFrame, Rect insets,
             @Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction,
             boolean freeform, WindowContainer container) {
 
-        if (mNextAppTransitionOverrideRequested && container.canCustomizeAppTransition()) {
+        if (mNextAppTransitionOverrideRequested
+                && (container.canCustomizeAppTransition() || mOverrideTaskTransition)) {
             mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
         }
 
         Animation a;
         if (isKeyguardGoingAwayTransitOld(transit) && enter) {
-            a = loadKeyguardExitAnimation(transit);
+            a = mTransitionAnimation.loadKeyguardExitAnimation(transit, mNextAppTransitionFlags);
         } else if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) {
             a = null;
         } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE && !enter) {
-            a = loadAnimationRes(lp, com.android.internal.R.anim.wallpaper_open_exit);
+            a = mTransitionAnimation.loadKeyguardUnoccludeAnimation(lp);
         } else if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) {
             a = null;
         } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_OPEN
                 || transit == TRANSIT_OLD_TASK_OPEN
                 || transit == TRANSIT_OLD_TASK_TO_FRONT)) {
-            a = loadAnimationRes(lp, enter
-                    ? com.android.internal.R.anim.voice_activity_open_enter
-                    : com.android.internal.R.anim.voice_activity_open_exit);
+            a = mTransitionAnimation.loadVoiceActivityOpenAnimation(lp, enter);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a,
                     appTransitionOldToString(transit), enter, Debug.getCallers(3));
         } else if (isVoiceInteraction && (transit == TRANSIT_OLD_ACTIVITY_CLOSE
                 || transit == TRANSIT_OLD_TASK_CLOSE
                 || transit == TRANSIT_OLD_TASK_TO_BACK)) {
-            a = loadAnimationRes(lp, enter
-                    ? com.android.internal.R.anim.voice_activity_close_enter
-                    : com.android.internal.R.anim.voice_activity_close_exit);
+            a = mTransitionAnimation.loadVoiceActivityExitAnimation(lp, enter);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation voice: anim=%s transit=%s isEntrance=%b Callers=%s", a,
                     appTransitionOldToString(transit), enter, Debug.getCallers(3));
         } else if (transit == TRANSIT_OLD_ACTIVITY_RELAUNCH) {
-            a = createRelaunchAnimation(frame, insets);
+            a = mTransitionAnimation.createRelaunchAnimation(frame, insets,
+                    mDefaultNextAppTransitionAnimationSpec != null
+                            ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s transit=%s Callers=%s", a,
                     appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
-            a = loadAnimationRes(mNextAppTransitionPackage, enter ?
-                    mNextAppTransitionEnter : mNextAppTransitionExit);
+            a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage,
+                    enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
                             + "isEntrance=%b Callers=%s",
                     a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
-            a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace);
+            a = mTransitionAnimation.loadAppTransitionAnimation(
+                    mNextAppTransitionPackage, mNextAppTransitionInPlace);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM_IN_PLACE "
                             + "transit=%s Callers=%s",
                     a, appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
-            a = createClipRevealAnimationLocked(transit, enter, frame, displayFrame);
+            a = mTransitionAnimation.createClipRevealAnimationLocked(
+                    transit, enter, frame, displayFrame,
+                    mDefaultNextAppTransitionAnimationSpec != null
+                            ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL "
                             + "transit=%s Callers=%s",
                     a, appTransitionOldToString(transit), Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
-            a = createScaleUpAnimationLocked(transit, enter, frame);
+            a = mTransitionAnimation.createScaleUpAnimationLocked(transit, enter, frame,
+                    mDefaultNextAppTransitionAnimationSpec != null
+                            ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=ANIM_SCALE_UP transit=%s "
                             + "isEntrance=%s Callers=%s",
@@ -1651,8 +1049,11 @@
                 mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
-            a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
-                    frame, transit, container);
+            final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container);
+            a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(
+                    getThumbnailTransitionState(enter), frame, transit, thumbnailHeader,
+                    mDefaultNextAppTransitionAnimationSpec != null
+                            ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b "
                             + "Callers=%s",
@@ -1663,9 +1064,13 @@
                 mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) {
             mNextAppTransitionScaleUp =
                     (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
-            a = createAspectScaledThumbnailEnterExitAnimationLocked(
-                    getThumbnailTransitionState(enter), uiMode, orientation, transit, frame,
-                    insets, surfaceInsets, stableInsets, freeform, container);
+            AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
+                    container.hashCode());
+            a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(
+                    getThumbnailTransitionState(enter), orientation, transit, frame,
+                    insets, surfaceInsets, stableInsets, freeform, spec != null ? spec.rect : null,
+                    mDefaultNextAppTransitionAnimationSpec != null
+                            ? mDefaultNextAppTransitionAnimationSpec.rect : null);
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b "
                             + "Callers=%s",
@@ -1674,8 +1079,7 @@
                         : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN",
                     appTransitionOldToString(transit), enter, Debug.getCallers(3));
         } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS && enter) {
-            a = loadAnimationRes("android",
-                    com.android.internal.R.anim.task_open_enter_cross_profile_apps);
+            a = mTransitionAnimation.loadCrossProfileAppEnterAnimation();
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: "
                             + "anim=%s transit=%s isEntrance=true Callers=%s",
@@ -1758,18 +1162,6 @@
         return a;
     }
 
-    private Animation loadKeyguardExitAnimation(int transit) {
-        if ((mNextAppTransitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) != 0) {
-            return null;
-        }
-        final boolean toShade =
-                (mNextAppTransitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0;
-        final boolean subtle =
-                (mNextAppTransitionFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0;
-        return mService.mPolicy.createHiddenByKeyguardExit(
-                transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER, toShade, subtle);
-    }
-
     int getAppRootTaskClipMode() {
         return mNextAppTransitionRequests.contains(TRANSIT_RELAUNCH)
                 || mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_GOING_AWAY)
@@ -1792,7 +1184,8 @@
     }
 
     void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
-            IRemoteCallback startedCallback, IRemoteCallback endedCallback) {
+            IRemoteCallback startedCallback, IRemoteCallback endedCallback,
+            boolean overrideTaskTransaction) {
         if (canOverridePendingAppTransition()) {
             clear();
             mNextAppTransitionOverrideRequested = true;
@@ -1802,6 +1195,7 @@
             postAnimationCallback();
             mNextAppTransitionCallback = startedCallback;
             mAnimationFinishedCallback = endedCallback;
+            mOverrideTaskTransition = overrideTaskTransaction;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 582aeb3..9ca7eca 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -102,6 +102,7 @@
     private final DisplayContent mDisplayContent;
     private final WallpaperController mWallpaperControllerLocked;
     private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
+    private static final int KEYGUARD_GOING_AWAY_ANIMATION_DURATION = 400;
 
     private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
 
@@ -437,10 +438,14 @@
                 return adapter;
             }
         }
-        if (mRemoteAnimationDefinition == null) {
-            return null;
+        if (mRemoteAnimationDefinition != null) {
+            final RemoteAnimationAdapter adapter = mRemoteAnimationDefinition.getAdapter(
+                    transit, activityTypes);
+            if (adapter != null) {
+                return adapter;
+            }
         }
-        return mRemoteAnimationDefinition.getAdapter(transit, activityTypes);
+        return null;
     }
 
     /**
@@ -709,11 +714,17 @@
         applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
                 voiceInteraction);
 
+        for (int i = 0; i < openingApps.size(); ++i) {
+            openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
+        }
+        for (int i = 0; i < closingApps.size(); ++i) {
+            closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
+        }
+
         final AccessibilityController accessibilityController =
                 mDisplayContent.mWmService.mAccessibilityController;
         if (accessibilityController != null) {
-            accessibilityController.onAppWindowTransitionLocked(
-                    mDisplayContent.getDisplayId(), transit);
+            accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java b/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
index 200f207..eec648d 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartCallback.java
@@ -31,7 +31,8 @@
      * Note that if the start was allowed due to a mechanism other than tokens (eg. permission),
      * this won't be called.
      *
-     * This will be called holding the WM lock, don't do anything costly here.
+     * This will be called holding the WM and local lock, don't do anything costly or invoke AM/WM
+     * methods here directly.
      */
     boolean isActivityStartAllowed(Collection<IBinder> tokens, int uid, String packageName);
 
@@ -40,7 +41,8 @@
      * #ACTION_CLOSE_SYSTEM_DIALOGS}, presumably to start activities, based on the originating
      * tokens {@code tokens} currently associated with potential activity starts.
      *
-     * This will be called holding the AM and WM lock, don't do anything costly here.
+     * This will be called holding the AM and local lock, don't do anything costly or invoke AM/WM
+     * methods here directly.
      */
     boolean canCloseSystemDialogs(Collection<IBinder> tokens, int uid);
 }
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
new file mode 100644
index 0000000..ab1ed67
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2021 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.wm;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.function.IntPredicate;
+
+/**
+ * A per-process controller to decide whether the process can start activity or foreground service
+ * (especially from background). All methods of this class must be thread safe. The caller does not
+ * need to hold WM lock, e.g. lock contention of WM lock shouldn't happen when starting service.
+ */
+class BackgroundLaunchProcessController {
+    private static final String TAG =
+            TAG_WITH_CLASS_NAME ? "BackgroundLaunchProcessController" : TAG_ATM;
+
+    /** It is {@link ActivityTaskManagerService#hasActiveVisibleWindow(int)}. */
+    private final IntPredicate mUidHasActiveVisibleWindowPredicate;
+
+    private final @Nullable BackgroundActivityStartCallback mBackgroundActivityStartCallback;
+
+    /**
+     * A set of tokens that currently contribute to this process being temporarily allowed
+     * to start activities even if it's not in the foreground. The values of this map are optional
+     * (can be null) and are used to trace back the grant to the notification token mechanism.
+     */
+    @GuardedBy("this")
+    private @Nullable ArrayMap<Binder, IBinder> mBackgroundActivityStartTokens;
+
+    /** Set of UIDs of clients currently bound to this process. */
+    @GuardedBy("this")
+    private @Nullable IntArray mBoundClientUids;
+
+    BackgroundLaunchProcessController(@NonNull IntPredicate uidHasActiveVisibleWindowPredicate,
+            @Nullable BackgroundActivityStartCallback callback) {
+        mUidHasActiveVisibleWindowPredicate = uidHasActiveVisibleWindowPredicate;
+        mBackgroundActivityStartCallback = callback;
+    }
+
+    boolean areBackgroundActivityStartsAllowed(int pid, int uid, String packageName,
+            boolean appSwitchAllowed, boolean isCheckingForFgsStart, boolean hasVisibleActivities,
+            boolean hasBackgroundActivityStartPrivileges, long lastStopAppSwitchesTime,
+            long lastActivityLaunchTime, long lastActivityFinishTime) {
+        // If app switching is not allowed, we ignore all the start activity grace period
+        // exception so apps cannot start itself in onPause() after pressing home button.
+        if (appSwitchAllowed) {
+            // Allow if any activity in the caller has either started or finished very recently, and
+            // it must be started or finished after last stop app switches time.
+            final long now = SystemClock.uptimeMillis();
+            if (now - lastActivityLaunchTime < ACTIVITY_BG_START_GRACE_PERIOD_MS
+                    || now - lastActivityFinishTime < ACTIVITY_BG_START_GRACE_PERIOD_MS) {
+                // If activity is started and finished before stop app switch time, we should not
+                // let app to be able to start background activity even it's in grace period.
+                if (lastActivityLaunchTime > lastStopAppSwitchesTime
+                        || lastActivityFinishTime > lastStopAppSwitchesTime) {
+                    if (DEBUG_ACTIVITY_STARTS) {
+                        Slog.d(TAG, "[Process(" + pid
+                                + ")] Activity start allowed: within "
+                                + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period");
+                    }
+                    return true;
+                }
+                if (DEBUG_ACTIVITY_STARTS) {
+                    Slog.d(TAG, "[Process(" + pid + ")] Activity start within "
+                            + ACTIVITY_BG_START_GRACE_PERIOD_MS
+                            + "ms grace period but also within stop app switch window");
+                }
+
+            }
+        }
+        // Allow if the proc is instrumenting with background activity starts privs.
+        if (hasBackgroundActivityStartPrivileges) {
+            if (DEBUG_ACTIVITY_STARTS) {
+                Slog.d(TAG, "[Process(" + pid
+                        + ")] Activity start allowed: process instrumenting with background "
+                        + "activity starts privileges");
+            }
+            return true;
+        }
+        // Allow if the caller has an activity in any foreground task.
+        if (appSwitchAllowed && hasVisibleActivities) {
+            if (DEBUG_ACTIVITY_STARTS) {
+                Slog.d(TAG, "[Process(" + pid
+                        + ")] Activity start allowed: process has activity in foreground task");
+            }
+            return true;
+        }
+        // Allow if the caller is bound by a UID that's currently foreground.
+        if (isBoundByForegroundUid()) {
+            if (DEBUG_ACTIVITY_STARTS) {
+                Slog.d(TAG, "[Process(" + pid
+                        + ")] Activity start allowed: process bound by foreground uid");
+            }
+            return true;
+        }
+        // Allow if the flag was explicitly set.
+        if (isBackgroundStartAllowedByToken(uid, packageName, isCheckingForFgsStart)) {
+            if (DEBUG_ACTIVITY_STARTS) {
+                Slog.d(TAG, "[Process(" + pid
+                        + ")] Activity start allowed: process allowed by token");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * If there are no tokens, we don't allow *by token*. If there are tokens and
+     * isCheckingForFgsStart is false, we ask the callback if the start is allowed for these tokens,
+     * otherwise if there is no callback we allow.
+     */
+    private boolean isBackgroundStartAllowedByToken(int uid, String packageName,
+            boolean isCheckingForFgsStart) {
+        synchronized (this) {
+            if (mBackgroundActivityStartTokens == null
+                    || mBackgroundActivityStartTokens.isEmpty()) {
+                return false;
+            }
+            if (isCheckingForFgsStart) {
+                // BG-FGS-start only checks if there is a token.
+                return true;
+            }
+
+            if (mBackgroundActivityStartCallback == null) {
+                // We have tokens but no callback to decide => allow.
+                return true;
+            }
+            // The callback will decide.
+            return mBackgroundActivityStartCallback.isActivityStartAllowed(
+                    mBackgroundActivityStartTokens.values(), uid, packageName);
+        }
+    }
+
+    private boolean isBoundByForegroundUid() {
+        synchronized (this) {
+            if (mBoundClientUids != null) {
+                for (int i = mBoundClientUids.size() - 1; i >= 0; i--) {
+                    if (mUidHasActiveVisibleWindowPredicate.test(mBoundClientUids.get(i))) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    void setBoundClientUids(ArraySet<Integer> boundClientUids) {
+        synchronized (this) {
+            if (boundClientUids == null || boundClientUids.isEmpty()) {
+                mBoundClientUids = null;
+                return;
+            }
+            if (mBoundClientUids == null) {
+                mBoundClientUids = new IntArray();
+            } else {
+                mBoundClientUids.clear();
+            }
+            for (int i = boundClientUids.size() - 1; i >= 0; i--) {
+                mBoundClientUids.add(boundClientUids.valueAt(i));
+            }
+        }
+    }
+
+    /**
+     * Allows background activity starts using token {@code entity}. Optionally, you can provide
+     * {@code originatingToken} if you have one such originating token, this is useful for tracing
+     * back the grant in the case of the notification token.
+     *
+     * If {@code entity} is already added, this method will update its {@code originatingToken}.
+     */
+    void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity,
+            @Nullable IBinder originatingToken) {
+        synchronized (this) {
+            if (mBackgroundActivityStartTokens == null) {
+                mBackgroundActivityStartTokens = new ArrayMap<>();
+            }
+            mBackgroundActivityStartTokens.put(entity, originatingToken);
+        }
+    }
+
+    /**
+     * Removes token {@code entity} that allowed background activity starts added via {@link
+     * #addOrUpdateAllowBackgroundActivityStartsToken(Binder, IBinder)}.
+     */
+    void removeAllowBackgroundActivityStartsToken(Binder entity) {
+        synchronized (this) {
+            if (mBackgroundActivityStartTokens != null) {
+                mBackgroundActivityStartTokens.remove(entity);
+            }
+        }
+    }
+
+    /**
+     * Returns whether this process is allowed to close system dialogs via a background activity
+     * start token that allows the close system dialogs operation (eg. notification).
+     */
+    boolean canCloseSystemDialogsByToken(int uid) {
+        if (mBackgroundActivityStartCallback == null) {
+            return false;
+        }
+        synchronized (this) {
+            if (mBackgroundActivityStartTokens == null
+                    || mBackgroundActivityStartTokens.isEmpty()) {
+                return false;
+            }
+            return mBackgroundActivityStartCallback.canCloseSystemDialogs(
+                    mBackgroundActivityStartTokens.values(), uid);
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        synchronized (this) {
+            if (mBackgroundActivityStartTokens != null
+                    && !mBackgroundActivityStartTokens.isEmpty()) {
+                pw.print(prefix);
+                pw.println("Background activity start tokens (token: originating token):");
+                for (int i = mBackgroundActivityStartTokens.size() - 1; i >= 0; i--) {
+                    pw.print(prefix);
+                    pw.print("  - ");
+                    pw.print(mBackgroundActivityStartTokens.keyAt(i));
+                    pw.print(": ");
+                    pw.println(mBackgroundActivityStartTokens.valueAt(i));
+                }
+            }
+            if (mBoundClientUids != null && mBoundClientUids.size() > 0) {
+                pw.print(prefix);
+                pw.print("BoundClientUids:");
+                pw.println(Arrays.toString(mBoundClientUids.toArray()));
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
deleted file mode 100644
index 4a90bbc..0000000
--- a/services/core/java/com/android/server/wm/BarController.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2018 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.wm;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-
-/**
- * Controls state/behavior specific to a system bar window.
- */
-public class BarController {
-    private final int mWindowType;
-
-    private final Rect mContentFrame = new Rect();
-
-    BarController(int windowType) {
-        mWindowType = windowType;
-    }
-
-    /**
-     * Sets the frame within which the bar will display its content.
-     *
-     * This is used to determine if letterboxes interfere with the display of such content.
-     */
-    void setContentFrame(Rect frame) {
-        mContentFrame.set(frame);
-    }
-
-    private Rect getContentFrame(@NonNull WindowState win) {
-        final Rect rotatedContentFrame = win.mToken.getFixedRotationBarContentFrame(mWindowType);
-        return rotatedContentFrame != null ? rotatedContentFrame : mContentFrame;
-    }
-
-    boolean isLightAppearanceAllowed(WindowState win) {
-        if (win == null) {
-            return true;
-        }
-        return !win.isLetterboxedOverlappingWith(getContentFrame(win));
-    }
-
-    boolean isTransparentAllowed(WindowState win) {
-        if (win == null) {
-            return true;
-        }
-        return win.letterboxNotIntersectsOrFullyContains(getContentFrame(win));
-    }
-}
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index dbad8b3..a725dd3 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -24,6 +24,9 @@
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.res.CompatibilityInfo;
@@ -32,6 +35,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -63,6 +67,58 @@
     // Compatibility state: compatibility mode is enabled.
     private static final int COMPAT_FLAG_ENABLED = 1<<1;
 
+    /**
+     * CompatModePackages#DOWNSCALED is the gatekeeper of all per-app buffer downscaling
+     * changes.  Disabling this change will prevent the following scaling factors from working:
+     * CompatModePackages#DOWNSCALE_87_5
+     * CompatModePackages#DOWNSCALE_75
+     * CompatModePackages#DOWNSCALE_62_5
+     * CompatModePackages#DOWNSCALE_50
+     *
+     * If CompatModePackages#DOWNSCALED is enabled for an app package, then the app will be forcibly
+     * resized to the highest enabled scaling factor e.g. 87.5% if both 87.5% and 75% were
+     * enabled.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALED = 168419799L;
+
+    /**
+     * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+     * CompatModePackages#DOWNSCALE_87_5 for a package will force the app to assume it's
+     * running on a display with 87.5% the vertical and horizontal resolution of the real display.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALE_87_5 = 176926753L;
+
+    /**
+     * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+     * CompatModePackages#DOWNSCALE_75 for a package will force the app to assume it's
+     * running on a display with 75% the vertical and horizontal resolution of the real display.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALE_75 = 176926829L;
+
+    /**
+     * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+     * CompatModePackages#DOWNSCALE_62_5 for a package will force the app to assume it's
+     * running on a display with 62.5% the vertical and horizontal resolution of the real display.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALE_62_5 = 176926771L;
+
+    /**
+     * With CompatModePackages#DOWNSCALED enabled, subsequently enabling change-id
+     * CompatModePackages#DOWNSCALE_50 for a package will force the app to assume it's
+     * running on a display with 50% vertical and horizontal resolution of the real display.
+     */
+    @ChangeId
+    @Disabled
+    private static final long DOWNSCALE_50 = 176926741L;
+
     private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
 
     private static final int MSG_WRITE = 300;
@@ -191,11 +247,39 @@
         mHandler.sendMessageDelayed(msg, 10000);
     }
 
+    float getCompatScale(String packageName, int uid) {
+        if (!CompatChanges.isChangeEnabled(
+                DOWNSCALED, packageName, UserHandle.getUserHandleForUid(uid))) {
+            return 1f;
+        }
+        if (CompatChanges.isChangeEnabled(
+                DOWNSCALE_87_5, packageName, UserHandle.getUserHandleForUid(uid))) {
+            // 8/7 == (1 / 0.875) ~= 1.14285714286
+            return 8f / 7f;
+        }
+        if (CompatChanges.isChangeEnabled(
+                DOWNSCALE_75, packageName, UserHandle.getUserHandleForUid(uid))) {
+            // 4/3 == (1 / 0.75) ~= 1.333333333
+            return 4f / 3f;
+        }
+        if (CompatChanges.isChangeEnabled(
+                DOWNSCALE_62_5, packageName, UserHandle.getUserHandleForUid(uid))) {
+            // (1 / 0.625) == 1.6
+            return 1.6f;
+        }
+        if (CompatChanges.isChangeEnabled(
+                DOWNSCALE_50, packageName, UserHandle.getUserHandleForUid(uid))) {
+            return 2f;
+        }
+        return 1f;
+    }
+
     public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
         final Configuration globalConfig = mService.getGlobalConfiguration();
+        final float requestedScale = getCompatScale(ai.packageName, ai.uid);
         CompatibilityInfo ci = new CompatibilityInfo(ai, globalConfig.screenLayout,
                 globalConfig.smallestScreenWidthDp,
-                (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
+                (getPackageFlags(ai.packageName) & COMPAT_FLAG_ENABLED) != 0, requestedScale);
         //Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
         return ci;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 15483cb..f075d85 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -462,6 +462,23 @@
     }
 
     @Override
+    void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+        super.resolveOverrideConfiguration(newParentConfiguration);
+        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+        final Rect overrideBounds = resolvedConfig.windowConfiguration.getBounds();
+        final Rect overrideAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
+        final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+
+        // If there is no override of appBounds, restrict appBounds to the override bounds.
+        if (!overrideBounds.isEmpty() && (overrideAppBounds == null || overrideAppBounds.isEmpty())
+                && parentAppBounds != null && !parentAppBounds.isEmpty()) {
+            final Rect appBounds = new Rect(overrideBounds);
+            appBounds.intersect(parentAppBounds);
+            resolvedConfig.windowConfiguration.setAppBounds(appBounds);
+        }
+    }
+
+    @Override
     boolean isOrganized() {
         return mOrganizer != null;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8841a9b..ee1e64f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -190,6 +190,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.DisplayCutout;
+import android.view.DisplayCutout.CutoutPathParserInfo;
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.IDisplayWindowInsetsController;
@@ -203,6 +204,7 @@
 import android.view.InsetsState.InternalInsetsType;
 import android.view.MagnificationSpec;
 import android.view.RemoteAnimationDefinition;
+import android.view.RoundedCorners;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -336,6 +338,10 @@
             = new RotationCache<>(this::calculateDisplayCutoutForRotationUncached);
     boolean mIgnoreDisplayCutout;
 
+    RoundedCorners mInitialRoundedCorners;
+    private final RotationCache<RoundedCorners, RoundedCorners> mRoundedCornerCache =
+            new RotationCache<>(this::calculateRoundedCornersForRotationUncached);
+
     /**
      * Overridden display size. Initialized with {@link #mInitialDisplayWidth}
      * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
@@ -679,6 +685,10 @@
      */
     private boolean mInEnsureActivitiesVisible = false;
 
+    // Used to indicate that the movement of child tasks to top will not move the display to top as
+    // well and thus won't change the top resumed / focused record
+    boolean mDontMoveToTop;
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         final ActivityRecord activity = w.mActivityRecord;
@@ -982,7 +992,8 @@
         isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
         mInsetsStateController = new InsetsStateController(this);
         mDisplayFrames = new DisplayFrames(mDisplayId, mInsetsStateController.getRawInsetsState(),
-                mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
+                mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
+                calculateRoundedCornersForRotation(mDisplayInfo.rotation));
         initializeDisplayBaseInfo();
 
         mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
@@ -1219,7 +1230,7 @@
 
         if (mWmService.mAccessibilityController != null) {
             final int prevDisplayId = prevDc != null ? prevDc.getDisplayId() : INVALID_DISPLAY;
-            mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(prevDisplayId,
+            mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(prevDisplayId,
                     getDisplayId());
         }
     }
@@ -1695,8 +1706,9 @@
         mTmpConfiguration.unset();
         final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
         final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
+        final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
         final DisplayFrames displayFrames = new DisplayFrames(mDisplayId, new InsetsState(), info,
-                cutout);
+                cutout, roundedCorners);
         token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
     }
 
@@ -1842,7 +1854,7 @@
         }
 
         if (mWmService.mAccessibilityController != null) {
-            mWmService.mAccessibilityController.onRotationChangedLocked(this);
+            mWmService.mAccessibilityController.onRotationChanged(this);
         }
     }
 
@@ -1937,22 +1949,43 @@
         if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
             return WmDisplayCutout.NO_CUTOUT;
         }
-        final Insets waterfallInsets =
-                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
         if (rotation == ROTATION_0) {
             return WmDisplayCutout.computeSafeInsets(
                     cutout, mInitialDisplayWidth, mInitialDisplayHeight);
         }
+        final Insets waterfallInsets =
+                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
         final Rect[] newBounds = mRotationUtil.getRotatedBounds(
                 cutout.getBoundingRectsAll(),
                 rotation, mInitialDisplayWidth, mInitialDisplayHeight);
+        final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
+        final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
+                info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
+                info.getCutoutSpec(), rotation, info.getScale());
         return WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets),
+                DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
                 rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
                 rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
     }
 
+    RoundedCorners calculateRoundedCornersForRotation(int rotation) {
+        return mRoundedCornerCache.getOrCompute(mInitialRoundedCorners, rotation);
+    }
+
+    private RoundedCorners calculateRoundedCornersForRotationUncached(
+            RoundedCorners roundedCorners, int rotation) {
+        if (roundedCorners == null || roundedCorners == RoundedCorners.NO_ROUNDED_CORNERS) {
+            return RoundedCorners.NO_ROUNDED_CORNERS;
+        }
+
+        if (rotation == ROTATION_0) {
+            return roundedCorners;
+        }
+
+        return roundedCorners.rotate(rotation, mInitialDisplayWidth, mInitialDisplayHeight);
+    }
+
     /**
      * Compute display info and configuration according to the given rotation without changing
      * current display.
@@ -2480,7 +2513,8 @@
 
     void onDisplayInfoChanged() {
         final DisplayInfo info = mDisplayInfo;
-        mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation));
+        mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation),
+                calculateRoundedCornersForRotation(info.rotation));
         mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(info);
     }
@@ -2513,6 +2547,7 @@
         mInitialDisplayHeight = mDisplayInfo.logicalHeight;
         mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
         mInitialDisplayCutout = mDisplayInfo.displayCutout;
+        mInitialRoundedCorners = mDisplayInfo.roundedCorners;
     }
 
     /**
@@ -2530,11 +2565,13 @@
         final DisplayCutout newCutout = mIgnoreDisplayCutout
                 ? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout;
         final String newUniqueId = mDisplayInfo.uniqueId;
+        final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners;
 
         final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth
                 || mInitialDisplayHeight != newHeight
                 || mInitialDisplayDensity != mDisplayInfo.logicalDensityDpi
-                || !Objects.equals(mInitialDisplayCutout, newCutout);
+                || !Objects.equals(mInitialDisplayCutout, newCutout)
+                || !Objects.equals(mInitialRoundedCorners, newRoundedCorners);
         final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId);
 
         if (displayMetricsChanged || physicalDisplayChanged) {
@@ -2553,6 +2590,7 @@
             mInitialDisplayHeight = newHeight;
             mInitialDisplayDensity = newDensity;
             mInitialDisplayCutout = newCutout;
+            mInitialRoundedCorners = newRoundedCorners;
             mCurrentUniqueDisplayId = newUniqueId;
             reconfigureDisplayLocked();
         }
@@ -3366,7 +3404,7 @@
     }
 
     void updateAccessibilityOnWindowFocusChanged(AccessibilityController accessibilityController) {
-        accessibilityController.onWindowFocusChangedNotLocked(getDisplayId());
+        accessibilityController.onWindowFocusChangedNot(getDisplayId());
     }
 
     private static void onWindowFocusChanged(WindowState oldFocus, WindowState newFocus) {
@@ -3607,7 +3645,7 @@
                 && mImeLayeringTarget.mActivityRecord.matchParentBounds()
                 // IME is attached to non-Letterboxed app windows, other than windows with
                 // LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER flag. (Refer to WS.isLetterboxedAppWindow())
-                && mImeLayeringTarget.matchesRootDisplayAreaBounds();
+                && mImeLayeringTarget.matchesDisplayAreaBounds();
     }
 
     /**
@@ -4105,8 +4143,15 @@
      * Callbacks when the given type of {@link WindowContainer} animation finished running in the
      * hierarchy.
      */
-    void onWindowAnimationFinished(int type) {
+    void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) {
         if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) {
+            // Unfreeze the insets state of the frozen target when the animation finished if exists.
+            final Task task = wc.asTask();
+            if (task != null) {
+                task.forAllWindows(w -> {
+                    w.clearFrozenInsetsState();
+                }, true /* traverseTopToBottom */);
+            }
             removeImeSurfaceImmediately();
         }
     }
@@ -4183,12 +4228,14 @@
         mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
 
         mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
-        mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
-                mLastHasContent,
-                mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
-                mTmpApplySurfaceChangesTransactionState.preferredModeId,
-                mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
-                true /* inTraversal, must call performTraversalInTrans... below */);
+        if (!mWmService.mDisplayFrozen) {
+            mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
+                    mLastHasContent,
+                    mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
+                    mTmpApplySurfaceChangesTransactionState.preferredModeId,
+                    mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
+                    true /* inTraversal, must call performTraversalInTrans... below */);
+        }
 
         final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
         if (wallpaperVisible != mLastWallpaperVisible) {
@@ -4920,7 +4967,7 @@
         if (!mLocationInParentWindow.equals(x, y)) {
             mLocationInParentWindow.set(x, y);
             if (mWmService.mAccessibilityController != null) {
-                mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(mDisplayId);
+                mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(mDisplayId);
             }
             notifyLocationInParentDisplayChanged();
         }
@@ -5480,14 +5527,16 @@
 
     /** Checks whether the given activity is in size compatibility mode and notifies the change. */
     void handleActivitySizeCompatModeIfNeeded(ActivityRecord r) {
-        if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+        final Task organizedTask = r.getOrganizedTask();
+        if (!r.isState(RESUMED) || r.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+                || organizedTask == null) {
             // The callback is only interested in the foreground changes of fullscreen activity.
             return;
         }
+        // TODO(b/178327644) Update for per Task size compat
         if (!r.inSizeCompatMode()) {
             if (mLastCompatModeActivity != null) {
-                mAtmService.getTaskChangeNotificationController()
-                        .notifySizeCompatModeActivityChanged(mDisplayId, null /* activityToken */);
+                organizedTask.onSizeCompatActivityChanged();
             }
             mLastCompatModeActivity = null;
             return;
@@ -5496,8 +5545,7 @@
             return;
         }
         mLastCompatModeActivity = r;
-        mAtmService.getTaskChangeNotificationController()
-                .notifySizeCompatModeActivityChanged(mDisplayId, r.appToken);
+        organizedTask.onSizeCompatActivityChanged();
     }
 
     boolean isUidPresent(int uid) {
@@ -5916,36 +5964,6 @@
         }
     }
 
-    /**
-     * Returns the number of window tokens without surface on this display. A {@link WindowToken}
-     * won't have its {@link SurfaceControl} until a window is added to a {@link WindowToken}.
-     * The purpose of this method is to accumulate non-window containing {@link WindowToken}s and
-     * limit the usage if the count exceeds a number.
-     *
-     * @param callingUid app calling uid
-     * @return the number of window tokens without surface on this display
-     * @see WindowToken#addWindow(WindowState)
-     */
-    int getWindowTokensWithoutSurfaceCount(int callingUid) {
-        List<WindowToken> tokens = new ArrayList<>(mTokenMap.values());
-        int count = 0;
-        for (int i = tokens.size() - 1; i >= 0; i--) {
-            final WindowToken token = tokens.get(i);
-            if (callingUid != token.getOwnerUid()) {
-                continue;
-            }
-            // Skip if token is an Activity
-            if (token.asActivityRecord() != null) {
-                continue;
-            }
-            if (token.mSurfaceControl != null) {
-                continue;
-            }
-            count++;
-        }
-        return count;
-    }
-
     MagnificationSpec getMagnificationSpec() {
         return mMagnificationSpec;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 43c1435..e4230a2 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -21,12 +21,12 @@
 import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
 
-import android.annotation.NonNull;
 import android.graphics.Rect;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
+import android.view.RoundedCorners;
 
 import com.android.server.wm.utils.WmDisplayCutout;
 
@@ -47,9 +47,6 @@
      */
     public final Rect mUnrestricted = new Rect();
 
-    /** The display cutout used for layout (after rotation) */
-    @NonNull public WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT;
-
     /**
      * During layout, the frame that is display-cutout safe, i.e. that does not intersect with it.
      */
@@ -61,26 +58,37 @@
     public int mRotation;
 
     public DisplayFrames(int displayId, InsetsState insetsState, DisplayInfo info,
-            WmDisplayCutout displayCutout) {
+            WmDisplayCutout displayCutout, RoundedCorners roundedCorners) {
         mDisplayId = displayId;
         mInsetsState = insetsState;
-        onDisplayInfoUpdated(info, displayCutout);
+        onDisplayInfoUpdated(info, displayCutout, roundedCorners);
     }
 
-    public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout) {
+    /**
+     * Update {@link DisplayFrames} when {@link DisplayInfo} is updated.
+     *
+     * @param info the updated {@link DisplayInfo}.
+     * @param displayCutout the updated {@link DisplayCutout}.
+     * @param roundedCorners the updated {@link RoundedCorners}.
+     */
+    public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout,
+            RoundedCorners roundedCorners) {
         mDisplayWidth = info.logicalWidth;
         mDisplayHeight = info.logicalHeight;
         mRotation = info.rotation;
-        mDisplayCutout = displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT;
+        final WmDisplayCutout wmDisplayCutout =
+                displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT;
 
         final InsetsState state = mInsetsState;
         final Rect unrestricted = mUnrestricted;
         final Rect safe = mDisplayCutoutSafe;
-        final DisplayCutout cutout = mDisplayCutout.getDisplayCutout();
+        final DisplayCutout cutout = wmDisplayCutout.getDisplayCutout();
         unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight);
         safe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
         state.setDisplayFrame(unrestricted);
         state.setDisplayCutout(cutout);
+        state.setRoundedCorners(roundedCorners != null ? roundedCorners
+                : RoundedCorners.NO_ROUNDED_CORNERS);
         if (!cutout.isEmpty()) {
             if (cutout.getSafeInsetLeft() > 0) {
                 safe.left = unrestricted.left + cutout.getSafeInsetLeft();
@@ -118,12 +126,5 @@
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
                 + " r=" + mRotation);
-        final String myPrefix = prefix + "  ";
-        dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw);
-        pw.println(myPrefix + "mDisplayCutout=" + mDisplayCutout);
-    }
-
-    private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) {
-        pw.print(prefix + name + "="); frame.printShortString(pw); pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a7db9d6..61fe023 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -25,7 +25,7 @@
 import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
 import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
 import static android.view.Display.TYPE_INTERNAL;
-import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
+import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
@@ -35,13 +35,16 @@
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_GESTURES;
+import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
 import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+import static android.view.ViewRootImpl.computeWindowBounds;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
@@ -84,6 +87,7 @@
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_UNKNOWN;
 import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
@@ -125,7 +129,6 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.util.ArraySet;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -139,8 +142,6 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.WindowInsets.Side;
-import android.view.WindowInsets.Side.InsetsSide;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
@@ -165,7 +166,6 @@
 import com.android.server.policy.WindowManagerPolicy.NavigationBarPosition;
 import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
-import com.android.server.policy.WindowOrientationListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wallpaper.WallpaperManagerInternal;
 import com.android.server.wm.InputMonitor.EventReceiverInputConsumer;
@@ -303,8 +303,7 @@
 
     private boolean mLastImmersiveMode;
 
-    private final BarController mStatusBarController;
-    private final BarController mNavigationBarController;
+    private final SparseArray<Rect> mBarContentFrames = new SparseArray<>();
 
     // The windows we were told about in focusChanged.
     private WindowState mFocusedWindow;
@@ -430,8 +429,8 @@
 
         final int displayId = displayContent.getDisplayId();
 
-        mStatusBarController = new BarController(TYPE_STATUS_BAR);
-        mNavigationBarController = new BarController(TYPE_NAVIGATION_BAR);
+        mBarContentFrames.put(TYPE_STATUS_BAR, new Rect());
+        mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect());
 
         final Resources r = mContext.getResources();
         mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
@@ -1075,7 +1074,7 @@
                             rect.bottom = rect.top + getStatusBarHeight(displayFrames);
                         };
                 mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, frameProvider);
-                mDisplayContent.setInsetProvider(ITYPE_TOP_GESTURES, win, frameProvider);
+                mDisplayContent.setInsetProvider(ITYPE_TOP_MANDATORY_GESTURES, win, frameProvider);
                 mDisplayContent.setInsetProvider(ITYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider);
                 break;
             case TYPE_NAVIGATION_BAR:
@@ -1102,7 +1101,7 @@
                         (displayFrames, windowState, inOutFrame) ->
                                 inOutFrame.set(windowState.getFrame()));
 
-                mDisplayContent.setInsetProvider(ITYPE_BOTTOM_GESTURES, win,
+                mDisplayContent.setInsetProvider(ITYPE_BOTTOM_MANDATORY_GESTURES, win,
                         (displayFrames, windowState, inOutFrame) -> {
                             inOutFrame.top -= mBottomGestureAdditionalInset;
                         });
@@ -1251,11 +1250,6 @@
                 displayFrames.mDisplayCutoutSafe.top);
     }
 
-    @VisibleForTesting
-    BarController getStatusBarController() {
-        return mStatusBarController;
-    }
-
     WindowState getStatusBar() {
         return mStatusBar != null ? mStatusBar : mStatusBarAlt;
     }
@@ -1399,29 +1393,15 @@
      *
      * @param attrs The LayoutParams of the window.
      * @param windowToken The token of the window.
-     * @param outFrame The frame of the window.
      * @param outInsetsState The insets state of this display from the client's perspective.
      * @param localClient Whether the client is from the our process.
      * @return Whether to always consume the system bars.
      *         See {@link #areSystemBarsForcedShownLw(WindowState)}.
      */
-    boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, Rect outFrame,
-            InsetsState outInsetsState, boolean localClient) {
-        final boolean isFixedRotationTransforming =
-                windowToken != null && windowToken.isFixedRotationTransforming();
-        final ActivityRecord activity = windowToken != null ? windowToken.asActivityRecord() : null;
-        final Task task = activity != null ? activity.getTask() : null;
-        final Rect taskBounds = isFixedRotationTransforming
-                // Use token (activity) bounds if it is rotated because its task is not rotated.
-                ? windowToken.getBounds()
-                : (task != null ? task.getBounds() : null);
+    boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, InsetsState outInsetsState,
+            boolean localClient) {
         final InsetsState state =
                 mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
-        computeWindowBounds(attrs, state, windowToken, outFrame);
-        if (taskBounds != null) {
-            outFrame.intersect(taskBounds);
-        }
-
         final boolean inSizeCompatMode = WindowState.inSizeCompatMode(attrs, windowToken);
         outInsetsState.set(state, inSizeCompatMode || localClient);
         if (inSizeCompatMode) {
@@ -1471,7 +1451,7 @@
         mSystemGestures.screenHeight = info.logicalHeight;
     }
 
-    private void layoutStatusBar(DisplayFrames displayFrames, Rect simulatedContentFrame) {
+    private void layoutStatusBar(DisplayFrames displayFrames, Rect contentFrame) {
         // decide where the status bar goes ahead of time
         if (mStatusBar == null) {
             return;
@@ -1498,21 +1478,16 @@
                     statusBarBottom);
         }
 
-        // Tell the bar controller where the collapsed status bar content is.
         sTmpRect.set(windowFrames.mFrame);
         sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
         sTmpRect.top = windowFrames.mFrame.top; // Ignore top display cutout inset
         sTmpRect.bottom = statusBarBottom; // Use collapsed status bar size
-        if (simulatedContentFrame != null) {
-            simulatedContentFrame.set(sTmpRect);
-        } else {
-            mStatusBarController.setContentFrame(sTmpRect);
-        }
+        contentFrame.set(sTmpRect);
     }
 
-    private void layoutNavigationBar(DisplayFrames displayFrames, Rect simulatedContentFrame) {
+    private int layoutNavigationBar(DisplayFrames displayFrames, Rect contentFrame) {
         if (mNavigationBar == null) {
-            return;
+            return NAV_BAR_INVALID;
         }
 
         final int uiMode = mDisplayContent.getConfiguration().uiMode;
@@ -1550,17 +1525,12 @@
         windowFrames.setFrames(navigationFrame /* parentFrame */,
                 navigationFrame /* displayFrame */);
         mNavigationBar.computeFrameAndUpdateSourceFrame();
-        final Rect contentFrame =  sTmpRect;
-        contentFrame.set(windowFrames.mFrame);
-        contentFrame.intersect(displayFrames.mDisplayCutoutSafe);
-        if (simulatedContentFrame != null) {
-            simulatedContentFrame.set(contentFrame);
-        } else {
-            mNavigationBarPosition = navBarPosition;
-            mNavigationBarController.setContentFrame(contentFrame);
-        }
+        sTmpRect.set(windowFrames.mFrame);
+        sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
+        contentFrame.set(sTmpRect);
 
         if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + navigationFrame);
+        return navBarPosition;
     }
 
     private boolean canReceiveInput(WindowState win) {
@@ -1572,28 +1542,6 @@
         return !notFocusableForIm;
     }
 
-    private void computeWindowBounds(WindowManager.LayoutParams attrs, InsetsState state,
-            @Nullable WindowToken windowToken, Rect outBounds) {
-        final @InsetsType int typesToFit = attrs.getFitInsetsTypes();
-        final @InsetsSide int sidesToFit = attrs.getFitInsetsSides();
-        final ArraySet<Integer> types = InsetsState.toInternalType(typesToFit);
-        final Rect df = windowToken != null ? windowToken.getBounds() : state.getDisplayFrame();
-        Insets insets = Insets.of(0, 0, 0, 0);
-        for (int i = types.size() - 1; i >= 0; i--) {
-            final InsetsSource source = state.peekSource(types.valueAt(i));
-            if (source == null) {
-                continue;
-            }
-            insets = Insets.max(insets, source.calculateInsets(
-                    df, attrs.isFitInsetsIgnoringVisibility()));
-        }
-        final int left = (sidesToFit & Side.LEFT) != 0 ? insets.left : 0;
-        final int top = (sidesToFit & Side.TOP) != 0 ? insets.top : 0;
-        final int right = (sidesToFit & Side.RIGHT) != 0 ? insets.right : 0;
-        final int bottom = (sidesToFit & Side.BOTTOM) != 0 ? insets.bottom : 0;
-        outBounds.set(df.left + left, df.top + top, df.right - right, df.bottom - bottom);
-    }
-
     /**
      * Called for each window attached to the window manager as layout is proceeding. The
      * implementation of this function must take care of setting the window's frame, either here or
@@ -1607,11 +1555,12 @@
      */
     public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
         if (win == mNavigationBar) {
-            layoutNavigationBar(displayFrames, null /* simulatedContentFrame */);
+            mNavigationBarPosition = layoutNavigationBar(displayFrames,
+                    mBarContentFrames.get(TYPE_NAVIGATION_BAR));
             return;
         }
         if ((win == mStatusBar && !canReceiveInput(win))) {
-            layoutStatusBar(displayFrames, null /* simulatedContentFrame */);
+            layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
             return;
         }
         final WindowManager.LayoutParams attrs = win.getAttrs();
@@ -1633,7 +1582,7 @@
         final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
 
         final InsetsState state = win.getInsetsState();
-        computeWindowBounds(attrs, state, win.mToken, df);
+        computeWindowBounds(attrs, state, win.mToken.getBounds(), df);
         if (attached == null) {
             pf.set(df);
             if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
@@ -2620,7 +2569,7 @@
                 // Otherwise if it's dimming, clear the light flag.
                 appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
             }
-            if (!mStatusBarController.isLightAppearanceAllowed(statusColorWin)) {
+            if (!isLightBarAllowed(statusColorWin, TYPE_STATUS_BAR)) {
                 appearance &= ~APPEARANCE_LIGHT_STATUS_BARS;
             }
         }
@@ -2683,7 +2632,7 @@
                 // Clear the light flag for dimming window.
                 appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
             }
-            if (!mNavigationBarController.isLightAppearanceAllowed(navColorWin)) {
+            if (!isLightBarAllowed(navColorWin, TYPE_NAVIGATION_BAR)) {
                 appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
             }
         }
@@ -2693,34 +2642,17 @@
     private int updateSystemBarsLw(WindowState win, int disableFlags) {
         final boolean dockedRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
                 .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
-                .isRootTaskVisible(WINDOWING_MODE_FREEFORM);
         final boolean resizing = mDisplayContent.getDockedDividerController().isResizing();
 
         // We need to force system bars when the docked root task is visible, when the freeform
         // root task is focused but also when we are resizing for the transitions when docked
         // root task visibility changes.
         mForceShowSystemBars = dockedRootTaskVisible || win.inFreeformWindowingMode() || resizing;
-        final boolean forceOpaqueStatusBar = mForceShowSystemBars && !isKeyguardShowing();
-
-        final boolean fullscreenDrawsStatusBarBackground =
-                drawsStatusBarBackground(mTopFullscreenOpaqueWindowState);
-        final boolean dockedDrawsStatusBarBackground =
-                drawsStatusBarBackground(mTopDockedOpaqueWindowState);
-        final boolean fullscreenDrawsNavBarBackground =
-                drawsNavigationBarBackground(mTopFullscreenOpaqueWindowState);
-        final boolean dockedDrawsNavigationBarBackground =
-                drawsNavigationBarBackground(mTopDockedOpaqueWindowState);
 
         int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
 
-        if (fullscreenDrawsStatusBarBackground && dockedDrawsStatusBarBackground) {
-            appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
-        }
-
-        appearance = configureNavBarOpacity(appearance, dockedRootTaskVisible,
-                freeformRootTaskVisible, resizing, fullscreenDrawsNavBarBackground,
-                dockedDrawsNavigationBarBackground);
+        appearance = configureStatusBarOpacity(appearance);
+        appearance = configureNavBarOpacity(appearance, dockedRootTaskVisible, resizing);
 
         final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
         final long now = SystemClock.uptimeMillis();
@@ -2754,10 +2686,36 @@
         return appearance;
     }
 
-    private boolean drawsBarBackground(WindowState win, BarController controller) {
-        if (!controller.isTransparentAllowed(win)) {
-            return false;
+    private boolean isLightBarAllowed(WindowState win, int windowType) {
+        if (win == null) {
+            return true;
         }
+        return !win.isLetterboxedOverlappingWith(getBarContentFrameForWindow(win, windowType));
+    }
+
+    private Rect getBarContentFrameForWindow(WindowState win, int windowType) {
+        final Rect rotatedBarFrame = win.mToken.getFixedRotationBarContentFrame(windowType);
+        return rotatedBarFrame != null ? rotatedBarFrame : mBarContentFrames.get(windowType);
+    }
+
+    /**
+     * @return {@code true} if bar is allowed to be fully transparent when given window is show.
+     *
+     * <p>Prevents showing a transparent bar over a letterboxed activity which can make
+     * notification icons or navigation buttons unreadable due to contrast between letterbox
+     * background and an activity. For instance, this happens when letterbox background is solid
+     * black while activity is white. To resolve this, only semi-transparent bars are allowed to
+     * be drawn over letterboxed activity.
+     */
+    @VisibleForTesting
+    boolean isFullyTransparentAllowed(WindowState win, int windowType) {
+        if (win == null) {
+            return true;
+        }
+        return win.isFullyTransparentBarAllowed(getBarContentFrameForWindow(win, windowType));
+    }
+
+    private boolean drawsBarBackground(WindowState win) {
         if (win == null) {
             return true;
         }
@@ -2770,12 +2728,23 @@
         return forceDrawsSystemBars || drawsSystemBars;
     }
 
-    private boolean drawsStatusBarBackground(WindowState win) {
-        return drawsBarBackground(win, mStatusBarController);
-    }
+    /** @return the current visibility flags with the status bar opacity related flags toggled. */
+    private int configureStatusBarOpacity(int appearance) {
+        final boolean fullscreenDrawsBackground =
+                drawsBarBackground(mTopFullscreenOpaqueWindowState);
+        final boolean dockedDrawsBackground =
+                drawsBarBackground(mTopDockedOpaqueWindowState);
 
-    private boolean drawsNavigationBarBackground(WindowState win) {
-        return drawsBarBackground(win, mNavigationBarController);
+        if (fullscreenDrawsBackground && dockedDrawsBackground) {
+            appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
+        }
+
+        if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_STATUS_BAR)
+                || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_STATUS_BAR)) {
+            appearance |= APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
+        }
+
+        return appearance;
     }
 
     /**
@@ -2783,10 +2752,16 @@
      *         on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
      */
     private int configureNavBarOpacity(int appearance, boolean dockedRootTaskVisible,
-            boolean freeformRootTaskVisible, boolean isDockedDividerResizing,
-            boolean fullscreenDrawsBackground, boolean dockedDrawsNavigationBarBackground) {
+            boolean isDockedDividerResizing) {
+        final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
+                .isRootTaskVisible(WINDOWING_MODE_FREEFORM);
+        final boolean fullscreenDrawsBackground =
+                drawsBarBackground(mTopFullscreenOpaqueWindowState);
+        final boolean dockedDrawsBackground =
+                drawsBarBackground(mTopDockedOpaqueWindowState);
+
         if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
-            if (fullscreenDrawsBackground && dockedDrawsNavigationBarBackground) {
+            if (fullscreenDrawsBackground && dockedDrawsBackground) {
                 appearance = clearNavBarOpaqueFlag(appearance);
             } else if (dockedRootTaskVisible) {
                 appearance = setNavBarOpaqueFlag(appearance);
@@ -2811,6 +2786,11 @@
             }
         }
 
+        if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_NAVIGATION_BAR)
+                || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_NAVIGATION_BAR)) {
+            appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
+        }
+
         return appearance;
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 6a86aee..48e4df7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -69,7 +69,6 @@
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
 import com.android.server.policy.WindowManagerPolicy;
-import com.android.server.policy.WindowOrientationListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
@@ -253,7 +252,7 @@
 
         if (isDefaultDisplay) {
             final Handler uiHandler = UiThread.getHandler();
-            mOrientationListener = new OrientationListener(mContext, uiHandler);
+            mOrientationListener = new OrientationListener(mContext, uiHandler, mService);
             mOrientationListener.setCurrentRotation(mRotation);
             mSettingsObserver = new SettingsObserver(uiHandler);
             mSettingsObserver.observe();
@@ -1474,8 +1473,8 @@
         final SparseArray<Runnable> mRunnableCache = new SparseArray<>(5);
         boolean mEnabled;
 
-        OrientationListener(Context context, Handler handler) {
-            super(context, handler);
+        OrientationListener(Context context, Handler handler, WindowManagerService service) {
+            super(context, handler, service);
         }
 
         private class UpdateRunnable implements Runnable {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index b82fdd2..6d5abe1 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -16,11 +16,11 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 
 import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_AUTO;
 import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED;
@@ -196,6 +196,14 @@
         mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
     }
 
+    void setDontMoveToTop(DisplayContent dc, boolean dontMoveToTop) {
+        DisplayInfo displayInfo = dc.getDisplayInfo();
+        SettingsProvider.SettingsEntry overrideSettings =
+                mSettingsProvider.getSettings(displayInfo);
+        overrideSettings.mDontMoveToTop = dontMoveToTop;
+        mSettingsProvider.updateOverrideSettings(displayInfo, overrideSettings);
+    }
+
     boolean shouldShowSystemDecorsLocked(DisplayContent dc) {
         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
             // Default display should show system decors.
@@ -274,6 +282,10 @@
         final int forcedScalingMode = settings.mForcedScalingMode != null
                 ? settings.mForcedScalingMode : FORCE_SCALING_MODE_AUTO;
         dc.mDisplayScalingDisabled = forcedScalingMode == FORCE_SCALING_MODE_DISABLED;
+
+        boolean dontMoveToTop = settings.mDontMoveToTop != null
+                ? settings.mDontMoveToTop : false;
+        dc.mDontMoveToTop = dontMoveToTop;
     }
 
     /**
@@ -358,6 +370,8 @@
             Boolean mIgnoreOrientationRequest;
             @Nullable
             Boolean mIgnoreDisplayCutout;
+            @Nullable
+            Boolean mDontMoveToTop;
 
             SettingsEntry() {}
 
@@ -432,6 +446,10 @@
                     mIgnoreDisplayCutout = other.mIgnoreDisplayCutout;
                     changed = true;
                 }
+                if (other.mDontMoveToTop != mDontMoveToTop) {
+                    mDontMoveToTop = other.mDontMoveToTop;
+                    changed = true;
+                }
                 return changed;
             }
 
@@ -515,6 +533,11 @@
                     mIgnoreDisplayCutout = delta.mIgnoreDisplayCutout;
                     changed = true;
                 }
+                if (delta.mDontMoveToTop != null
+                        && delta.mDontMoveToTop != mDontMoveToTop) {
+                    mDontMoveToTop = delta.mDontMoveToTop;
+                    changed = true;
+                }
                 return changed;
             }
 
@@ -531,7 +554,8 @@
                         && mImePolicy == null
                         && mFixedToUserRotation == null
                         && mIgnoreOrientationRequest == null
-                        && mIgnoreDisplayCutout == null;
+                        && mIgnoreDisplayCutout == null
+                        && mDontMoveToTop == null;
             }
 
             @Override
@@ -553,7 +577,8 @@
                         && Objects.equals(mImePolicy, that.mImePolicy)
                         && Objects.equals(mFixedToUserRotation, that.mFixedToUserRotation)
                         && Objects.equals(mIgnoreOrientationRequest, that.mIgnoreOrientationRequest)
-                        && Objects.equals(mIgnoreDisplayCutout, that.mIgnoreDisplayCutout);
+                        && Objects.equals(mIgnoreDisplayCutout, that.mIgnoreDisplayCutout)
+                        && Objects.equals(mDontMoveToTop, that.mDontMoveToTop);
             }
 
             @Override
@@ -561,7 +586,8 @@
                 return Objects.hash(mWindowingMode, mUserRotationMode, mUserRotation, mForcedWidth,
                         mForcedHeight, mForcedDensity, mForcedScalingMode, mRemoveContentMode,
                         mShouldShowWithInsecureKeyguard, mShouldShowSystemDecors, mImePolicy,
-                        mFixedToUserRotation, mIgnoreOrientationRequest, mIgnoreDisplayCutout);
+                        mFixedToUserRotation, mIgnoreOrientationRequest, mIgnoreDisplayCutout,
+                        mDontMoveToTop);
             }
 
             @Override
@@ -581,6 +607,7 @@
                         + ", mFixedToUserRotation=" + mFixedToUserRotation
                         + ", mIgnoreOrientationRequest=" + mIgnoreOrientationRequest
                         + ", mIgnoreDisplayCutout=" + mIgnoreDisplayCutout
+                        + ", mDontMoveToTop=" + mDontMoveToTop
                         + '}';
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 5f3ab43..737f810 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -405,6 +405,9 @@
                     "ignoreOrientationRequest", null /* defaultValue */);
             settingsEntry.mIgnoreDisplayCutout = getBooleanAttribute(parser,
                     "ignoreDisplayCutout", null /* defaultValue */);
+            settingsEntry.mDontMoveToTop = getBooleanAttribute(parser,
+                    "dontMoveToTop", null /* defaultValue */);
+
             fileData.mSettings.put(name, settingsEntry);
         }
         XmlUtils.skipCurrentTag(parser);
@@ -496,6 +499,10 @@
                     out.attributeBoolean(null, "ignoreDisplayCutout",
                             settingsEntry.mIgnoreDisplayCutout);
                 }
+                if (settingsEntry.mDontMoveToTop != null) {
+                    out.attributeBoolean(null, "dontMoveToTop",
+                            settingsEntry.mDontMoveToTop);
+                }
                 out.endTag(null, "display");
             }
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 803bec8..de4bdaa 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -47,7 +47,7 @@
         mTouchRegion.set(touchRegion);
         // We need to report touchable region changes to accessibility.
         if (mDisplayContent.mWmService.mAccessibilityController != null) {
-            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(
+            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
                     mDisplayContent.getDisplayId());
         }
     }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 7d0854d..c6c7fe0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.os.Build.IS_DEBUGGABLE;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
@@ -557,11 +556,6 @@
                 // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
                 t.setAlpha(animationLeash, 1 /* alpha */);
                 t.hide(animationLeash);
-
-                // TODO(b/175954493): Remove this after finding root cause.
-                if (IS_DEBUGGABLE) {
-                    animationLeash.setDebugRelease(true);
-                }
             }
             ProtoLog.i(WM_DEBUG_IME,
                     "ControlAdapter startAnimation mSource: %s controlTarget: %s", mSource,
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 7b709ea..580d328 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
@@ -74,7 +75,7 @@
     private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
 
     private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
-        if (w.isVisible()) {
+        if (w.isReadyToDispatchInsetsState()) {
             w.notifyInsetsChanged();
         }
     };
@@ -119,6 +120,7 @@
         final @InternalInsetsType int type = provider != null
                 ? provider.getSource().getType() : ITYPE_INVALID;
         return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(),
+                target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() :
                 target.mAboveInsetsState);
     }
 
@@ -217,6 +219,9 @@
             state.removeSource(ITYPE_STATUS_BAR);
             state.removeSource(ITYPE_NAVIGATION_BAR);
             state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
+            if (windowingMode == WINDOWING_MODE_PINNED) {
+                state.removeSource(ITYPE_IME);
+            }
         }
 
         return state;
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 44ce4de..99c9e79 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -19,6 +19,7 @@
 import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.view.SurfaceControl.HIDDEN;
 
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -44,12 +45,19 @@
 
     private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
+    private final Supplier<Boolean> mAreCornersRounded;
+    private final Supplier<Color> mColorSupplier;
+
     private final Rect mOuter = new Rect();
     private final Rect mInner = new Rect();
     private final LetterboxSurface mTop = new LetterboxSurface("top");
     private final LetterboxSurface mLeft = new LetterboxSurface("left");
     private final LetterboxSurface mBottom = new LetterboxSurface("bottom");
     private final LetterboxSurface mRight = new LetterboxSurface("right");
+    // Prevents wallpaper from peeking through near rounded corners. It's not included in
+    // mSurfaces array since it isn't needed in methods like notIntersectsOrFullyContains
+    // or attachInput.
+    private final LetterboxSurface mBehind = new LetterboxSurface("behind");
     private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
 
     /**
@@ -58,9 +66,13 @@
      * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s
      */
     public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
-            Supplier<SurfaceControl.Transaction> transactionFactory) {
+            Supplier<SurfaceControl.Transaction> transactionFactory,
+            Supplier<Boolean> areCornersRounded,
+            Supplier<Color> colorSupplier) {
         mSurfaceControlFactory = surfaceControlFactory;
         mTransactionFactory = transactionFactory;
+        mAreCornersRounded = areCornersRounded;
+        mColorSupplier = colorSupplier;
     }
 
     /**
@@ -82,6 +94,7 @@
         mLeft.layout(outer.left, outer.top, inner.left, outer.bottom, surfaceOrigin);
         mBottom.layout(outer.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin);
         mRight.layout(inner.right, outer.top, outer.right, outer.bottom, surfaceOrigin);
+        mBehind.layout(inner.left, inner.top, inner.right, inner.bottom, surfaceOrigin);
     }
 
 
@@ -157,6 +170,7 @@
         for (LetterboxSurface surface : mSurfaces) {
             surface.remove();
         }
+        mBehind.remove();
     }
 
     /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */
@@ -166,6 +180,9 @@
                 return true;
             }
         }
+        if (mBehind.needsApplySurfaceChanges()) {
+            return true;
+        }
         return false;
     }
 
@@ -173,6 +190,11 @@
         for (LetterboxSurface surface : mSurfaces) {
             surface.applySurfaceChanges(t);
         }
+        if (mAreCornersRounded.get()) {
+            mBehind.applySurfaceChanges(t);
+        } else {
+            mBehind.remove();
+        }
     }
 
     /** Enables touches to slide into other neighboring surfaces. */
@@ -251,6 +273,7 @@
 
         private final String mType;
         private SurfaceControl mSurface;
+        private Color mColor;
 
         private final Rect mSurfaceFrameRelative = new Rect();
         private final Rect mLayoutFrameGlobal = new Rect();
@@ -275,9 +298,8 @@
                     .setColorLayer()
                     .setCallsite("LetterboxSurface.createSurface")
                     .build();
-            t.setLayer(mSurface, -1)
-                    .setColor(mSurface, new float[]{0, 0, 0})
-                    .setColorSpaceAgnostic(mSurface, true);
+
+            t.setLayer(mSurface, -1).setColorSpaceAgnostic(mSurface, true);
         }
 
         void attachInput(WindowState win) {
@@ -318,7 +340,7 @@
         }
 
         public void applySurfaceChanges(SurfaceControl.Transaction t) {
-            if (mSurfaceFrameRelative.equals(mLayoutFrameRelative)) {
+            if (!needsApplySurfaceChanges()) {
                 // Nothing changed.
                 return;
             }
@@ -327,6 +349,14 @@
                 if (mSurface == null) {
                     createSurface(t);
                 }
+
+                mColor = mColorSupplier.get();
+                final float[] rgbTmpFloat = new float[3];
+                rgbTmpFloat[0] = mColor.red();
+                rgbTmpFloat[1] = mColor.green();
+                rgbTmpFloat[2] = mColor.blue();
+                t.setColor(mSurface, rgbTmpFloat);
+
                 t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
                 t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
                         mSurfaceFrameRelative.height());
@@ -341,7 +371,8 @@
         }
 
         public boolean needsApplySurfaceChanges() {
-            return !mSurfaceFrameRelative.equals(mLayoutFrameRelative);
+            return !mSurfaceFrameRelative.equals(mLayoutFrameRelative)
+                    || mColorSupplier.get() != mColor;
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index e1fdefd..e02cce4 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -421,7 +421,10 @@
             if (skipAnimation(task)) {
                 continue;
             }
-            addAnimation(task, !recentTaskIds.get(task.mTaskId));
+            addAnimation(task, !recentTaskIds.get(task.mTaskId), false /* hidden */,
+                    (type, anim) -> task.forAllWindows(win -> {
+                        win.onAnimationFinished(type, anim);
+                    }, true /* traverseTopToBottom */));
         }
 
         // Skip the animation if there is nothing to animate
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 91014aa..26871d1 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -28,7 +28,7 @@
  */
 class RefreshRatePolicy {
 
-    private final int mLowRefreshRateId;
+    private final Mode mLowRefreshRateMode;
     private final ArraySet<String> mNonHighRefreshRatePackages = new ArraySet<>();
     private final HighRefreshRateDenylist mHighRefreshRateDenylist;
     private final WindowManagerService mWmService;
@@ -56,7 +56,7 @@
 
     RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
             HighRefreshRateDenylist denylist) {
-        mLowRefreshRateId = findLowRefreshRateModeId(displayInfo);
+        mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
         mHighRefreshRateDenylist = denylist;
         mWmService = wmService;
     }
@@ -65,7 +65,7 @@
      * Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
      * default mode.
      */
-    private int findLowRefreshRateModeId(DisplayInfo displayInfo) {
+    private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
         Mode mode = displayInfo.getDefaultMode();
         float[] refreshRates = displayInfo.getDefaultRefreshRates();
         float bestRefreshRate = mode.getRefreshRate();
@@ -104,13 +104,9 @@
 
         // If app is using Camera, force it to default (lower) refresh rate.
         if (mNonHighRefreshRatePackages.contains(packageName)) {
-            return mLowRefreshRateId;
+            return mLowRefreshRateMode.getModeId();
         }
 
-        // If app is denylisted using higher refresh rate, return default (lower) refresh rate
-        if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
-            return mLowRefreshRateId;
-        }
         return 0;
     }
 
@@ -137,4 +133,18 @@
         }
         return LAYER_PRIORITY_UNSET;
     }
+
+    float getPreferredRefreshRate(WindowState w) {
+        // If app is animating, it's not able to control refresh rate because we want the animation
+        // to run in default refresh rate.
+        if (w.isAnimating(TRANSITION | PARENTS)) {
+            return 0;
+        }
+
+        final String packageName = w.getOwningPackage();
+        if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
+            return mLowRefreshRateMode.getRefreshRate();
+        }
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index a1d2072..392f27e 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -36,6 +36,7 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.view.WindowManager;
 
 import com.android.internal.protolog.ProtoLogImpl;
 import com.android.internal.protolog.common.ProtoLog;
@@ -98,7 +99,7 @@
     /**
      * Called when the transition is ready to be started, and all leashes have been set up.
      */
-    void goodToGo() {
+    void goodToGo(@WindowManager.TransitionOldType int transit) {
         ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()");
         if (mPendingAnimations.isEmpty() || mCanceled) {
             ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
@@ -123,11 +124,15 @@
 
         // Create the remote wallpaper animation targets (if any)
         final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
+
+        // TODO(bc-unlock): Create the remote non app animation targets (if any)
+        final RemoteAnimationTarget[] nonAppTargets = new RemoteAnimationTarget[0];
+
         mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             try {
                 linkToDeathOfRunner();
-                mRemoteAnimationAdapter.getRunner().onAnimationStart(appTargets, wallpaperTargets,
-                        mFinishedCallback);
+                mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
+                        wallpaperTargets, nonAppTargets, mFinishedCallback);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to start remote animation", e);
                 onAnimationFinished();
@@ -274,6 +279,7 @@
     private void setRunningRemoteAnimation(boolean running) {
         final int pid = mRemoteAnimationAdapter.getCallingPid();
         final int uid = mRemoteAnimationAdapter.getCallingUid();
+
         if (pid == 0) {
             throw new RuntimeException("Calling pid of remote animation was null");
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ce4e5ec..2c97f78 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -37,6 +37,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
 import static android.view.WindowManager.TRANSIT_NONE;
@@ -68,7 +69,6 @@
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
-import static com.android.server.wm.RootWindowContainerProto.PENDING_ACTIVITIES;
 import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
 import static com.android.server.wm.Task.ActivityState.FINISHING;
 import static com.android.server.wm.Task.ActivityState.PAUSED;
@@ -933,7 +933,6 @@
                     displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                 }
                 win.destroySurfaceUnchecked();
-                win.mWinAnimator.destroyPreservedSurfaceLocked(win.getSyncTransaction());
             } while (i > 0);
             mWmService.mDestroySurface.clear();
         }
@@ -1289,7 +1288,6 @@
         mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
         proto.write(IS_HOME_RECENTS_COMPONENT,
                 mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
-        mService.getActivityStartController().dumpDebug(proto, PENDING_ACTIVITIES);
 
         proto.end(token);
     }
@@ -2182,6 +2180,8 @@
                 // display area, so reparent.
                 rootTask.reparent(taskDisplayArea, true /* onTop */);
             }
+            mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CHANGE, rootTask);
+
             // Defer the windowing mode change until after the transition to prevent the activity
             // from doing work and changing the activity visuals while animating
             // TODO(task-org): Figure-out more structured way to do this long term.
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 6df4536..6567195 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -253,6 +253,20 @@
             throw new SecurityException(msg);
         }
 
+        // Check if the caller is allowed to override any app transition animation.
+        final boolean overrideTaskTransition = options.getOverrideTaskTransition();
+        if (aInfo != null && overrideTaskTransition) {
+            final int startTasksFromRecentsPerm = ActivityTaskManagerService.checkPermission(
+                    START_TASKS_FROM_RECENTS, callingPid, callingUid);
+            if (startTasksFromRecentsPerm != PERMISSION_GRANTED) {
+                final String msg = "Permission Denial: starting " + getIntentString(intent)
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ") with overrideTaskTransition=true";
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+        }
+
         // Check permission for remote animations
         final RemoteAnimationAdapter adapter = options.getRemoteAnimationAdapter();
         if (adapter != null && supervisor.mService.checkPermission(
diff --git a/services/core/java/com/android/server/wm/ImpressionAttestationController.java b/services/core/java/com/android/server/wm/ScreenshotHashController.java
similarity index 79%
rename from services/core/java/com/android/server/wm/ImpressionAttestationController.java
rename to services/core/java/com/android/server/wm/ScreenshotHashController.java
index bad6c80..03f4e28 100644
--- a/services/core/java/com/android/server/wm/ImpressionAttestationController.java
+++ b/services/core/java/com/android/server/wm/ScreenshotHashController.java
@@ -16,7 +16,9 @@
 
 package com.android.server.wm;
 
-import static android.service.attestation.ImpressionAttestationService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
+import static android.service.screenshot.ScreenshotHasherService.EXTRA_SCREENSHOT_HASH;
+import static android.service.screenshot.ScreenshotHasherService.EXTRA_VERIFICATION_STATUS;
+import static android.service.screenshot.ScreenshotHasherService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -44,9 +46,9 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.service.attestation.IImpressionAttestationService;
-import android.service.attestation.ImpressionAttestationService;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.IScreenshotHasherService;
+import android.service.screenshot.ScreenshotHash;
+import android.service.screenshot.ScreenshotHasherService;
 import android.util.Slog;
 import android.view.MagnificationSpec;
 
@@ -59,30 +61,29 @@
 import java.util.function.BiConsumer;
 
 /**
- * Handles requests into {@link ImpressionAttestationService}
+ * Handles requests into {@link android.service.screenshot.ScreenshotHasherService}
  *
  * Do not hold the {@link WindowManagerService#mGlobalLock} when calling methods since they are
  * blocking calls into another service.
  */
-public class ImpressionAttestationController {
-    private static final String TAG =
-            TAG_WITH_CLASS_NAME ? "ImpressionAttestationController" : TAG_WM;
+public class ScreenshotHashController {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenshotHashController" : TAG_WM;
     private static final boolean DEBUG = false;
 
     private final Object mServiceConnectionLock = new Object();
 
     @GuardedBy("mServiceConnectionLock")
-    private ImpressionAttestationServiceConnection mServiceConnection;
+    private ScreenshotHasherServiceConnection mServiceConnection;
 
     private final Context mContext;
 
     /**
-     * Lock used for the cached {@link #mImpressionAlgorithms} array
+     * Lock used for the cached {@link #mHashingAlgorithms} array
      */
-    private final Object mImpressionAlgorithmsLock = new Object();
+    private final Object mHashingAlgorithmsLock = new Object();
 
-    @GuardedBy("mImpressionAlgorithmsLock")
-    private String[] mImpressionAlgorithms;
+    @GuardedBy("mHashingAlgorithmsLock")
+    private String[] mHashingAlgorithms;
 
     private final Handler mHandler;
 
@@ -93,24 +94,24 @@
     private final RectF mTmpRectF = new RectF();
 
     private interface Command {
-        void run(IImpressionAttestationService service) throws RemoteException;
+        void run(IScreenshotHasherService service) throws RemoteException;
     }
 
-    ImpressionAttestationController(Context context) {
+    ScreenshotHashController(Context context) {
         mContext = context;
         mHandler = new Handler(Looper.getMainLooper());
         mSalt = UUID.randomUUID().toString().getBytes();
     }
 
-    String[] getSupportedImpressionAlgorithms() {
-        // We have a separate lock for the impression algorithm array since it doesn't need to make
+    String[] getSupportedHashingAlgorithms() {
+        // We have a separate lock for the hashing algorithm array since it doesn't need to make
         // the request through the service connection. Instead, we have a lock to ensure we can
-        // properly cache the impression algorithms array so we don't need to call into the
+        // properly cache the hashing algorithms array so we don't need to call into the
         // ExtServices process for each request.
-        synchronized (mImpressionAlgorithmsLock) {
+        synchronized (mHashingAlgorithmsLock) {
             // Already have cached values
-            if (mImpressionAlgorithms != null) {
-                return mImpressionAlgorithms;
+            if (mHashingAlgorithms != null) {
+                return mHashingAlgorithms;
             }
 
             final ServiceInfo serviceInfo = getServiceInfo();
@@ -127,54 +128,55 @@
 
             final int resourceId = serviceInfo.metaData.getInt(
                     SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS);
-            mImpressionAlgorithms = res.getStringArray(resourceId);
+            mHashingAlgorithms = res.getStringArray(resourceId);
 
-            return mImpressionAlgorithms;
+            return mHashingAlgorithms;
         }
     }
 
-    boolean verifyImpressionToken(ImpressionToken impressionToken) {
+    boolean verifyScreenshotHash(ScreenshotHash screenshotHash) {
         final SyncCommand syncCommand = new SyncCommand();
         Bundle results = syncCommand.run((service, remoteCallback) -> {
             try {
-                service.verifyImpressionToken(mSalt, impressionToken, remoteCallback);
+                service.verifyScreenshotHash(mSalt, screenshotHash, remoteCallback);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to invoke verifyImpressionToken command");
+                Slog.e(TAG, "Failed to invoke verifyScreenshotHash command");
             }
         });
 
-        return results.getBoolean(ImpressionAttestationService.EXTRA_VERIFICATION_STATUS);
+        return results.getBoolean(EXTRA_VERIFICATION_STATUS);
     }
 
-    ImpressionToken generateImpressionToken(HardwareBuffer screenshot, Rect bounds,
+    ScreenshotHash generateScreenshotHash(HardwareBuffer screenshot, Rect bounds,
             String hashAlgorithm) {
         final SyncCommand syncCommand = new SyncCommand();
         Bundle results = syncCommand.run((service, remoteCallback) -> {
             try {
-                service.generateImpressionToken(mSalt, screenshot, bounds, hashAlgorithm,
+                service.generateScreenshotHash(mSalt, screenshot, bounds, hashAlgorithm,
                         remoteCallback);
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to invoke generateImpressionToken command", e);
+                Slog.e(TAG, "Failed to invoke generateScreenshotHash command", e);
             }
         });
 
-        return results.getParcelable(ImpressionAttestationService.EXTRA_IMPRESSION_TOKEN);
+        return results.getParcelable(EXTRA_SCREENSHOT_HASH);
     }
 
     /**
-     * Calculate the bounds to take the screenshot when generating the impression token. This takes
-     * into account window transform, magnification, and display bounds.
+     * Calculate the bounds to take the screenshot when generating the ScreenshotHash. This
+     * takes into account window transform, magnification, and display bounds.
      *
      * Call while holding {@link WindowManagerService#mGlobalLock}
      *
-     * @param win Window that the impression token is generated for.
+     * @param win Window that the ScreenshotHash is generated for.
      * @param boundsInWindow The bounds passed in about where in the window to take the screenshot.
      * @param outBounds The result of the calculated bounds
      */
-    void calculateImpressionTokenBoundsLocked(WindowState win, Rect boundsInWindow,
+    void calculateScreenshotHashBoundsLocked(WindowState win, Rect boundsInWindow,
             Rect outBounds) {
         if (DEBUG) {
-            Slog.d(TAG, "calculateImpressionTokenBounds: boundsInWindow=" + boundsInWindow);
+            Slog.d(TAG,
+                    "calculateScreenshotHashBoundsLocked: boundsInWindow=" + boundsInWindow);
         }
         outBounds.set(boundsInWindow);
 
@@ -192,7 +194,8 @@
         outBounds.intersectUnchecked(windowBounds);
 
         if (DEBUG) {
-            Slog.d(TAG, "calculateImpressionTokenBounds: boundsIntersectWindow=" + outBounds);
+            Slog.d(TAG,
+                    "calculateScreenshotHashBoundsLocked: boundsIntersectWindow=" + outBounds);
         }
 
         if (outBounds.isEmpty()) {
@@ -207,7 +210,7 @@
         outBounds.set((int) mTmpRectF.left, (int) mTmpRectF.top, (int) mTmpRectF.right,
                 (int) mTmpRectF.bottom);
         if (DEBUG) {
-            Slog.d(TAG, "calculateImpressionTokenBounds: boundsInDisplay=" + outBounds);
+            Slog.d(TAG, "calculateScreenshotHashBoundsLocked: boundsInDisplay=" + outBounds);
         }
 
         // Apply the magnification spec values to the bounds since the content could be magnified
@@ -218,7 +221,8 @@
         }
 
         if (DEBUG) {
-            Slog.d(TAG, "calculateImpressionTokenBounds: boundsWithMagnification=" + outBounds);
+            Slog.d(TAG, "calculateScreenshotHashBoundsLocked: boundsWithMagnification="
+                    + outBounds);
         }
 
         if (outBounds.isEmpty()) {
@@ -230,7 +234,7 @@
         final Rect displayBounds = displayContent.getBounds();
         outBounds.intersectUnchecked(displayBounds);
         if (DEBUG) {
-            Slog.d(TAG, "calculateImpressionTokenBounds: finalBounds=" + outBounds);
+            Slog.d(TAG, "calculateScreenshotHashBoundsLocked: finalBounds=" + outBounds);
         }
     }
 
@@ -244,7 +248,7 @@
                 if (DEBUG) Slog.v(TAG, "creating connection");
 
                 // Create the connection
-                mServiceConnection = new ImpressionAttestationServiceConnection();
+                mServiceConnection = new ScreenshotHasherServiceConnection();
 
                 final ComponentName component = getServiceComponentName();
                 if (DEBUG) Slog.v(TAG, "binding to: " + component);
@@ -275,7 +279,7 @@
             return null;
         }
 
-        final Intent intent = new Intent(ImpressionAttestationService.SERVICE_INTERFACE);
+        final Intent intent = new Intent(ScreenshotHasherService.SERVICE_INTERFACE);
         intent.setPackage(packageName);
         final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
@@ -292,10 +296,10 @@
         if (serviceInfo == null) return null;
 
         final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
-        if (!Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE
+        if (!Manifest.permission.BIND_SCREENSHOT_HASHER_SERVICE
                 .equals(serviceInfo.permission)) {
             Slog.w(TAG, name.flattenToShortString() + " requires permission "
-                    + Manifest.permission.BIND_IMPRESSION_ATTESTATION_SERVICE);
+                    + Manifest.permission.BIND_SCREENSHOT_HASHER_SERVICE);
             return null;
         }
 
@@ -308,7 +312,7 @@
         private Bundle mResult;
         private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
 
-        public Bundle run(BiConsumer<IImpressionAttestationService, RemoteCallback> func) {
+        public Bundle run(BiConsumer<IScreenshotHasherService, RemoteCallback> func) {
             connectAndRun(service -> {
                 RemoteCallback callback = new RemoteCallback(result -> {
                     mResult = result;
@@ -327,9 +331,9 @@
         }
     }
 
-    private class ImpressionAttestationServiceConnection implements ServiceConnection {
+    private class ScreenshotHasherServiceConnection implements ServiceConnection {
         @GuardedBy("mServiceConnectionLock")
-        private IImpressionAttestationService mRemoteService;
+        private IScreenshotHasherService mRemoteService;
 
         @GuardedBy("mServiceConnectionLock")
         private ArrayList<Command> mQueuedCommands;
@@ -338,7 +342,7 @@
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DEBUG) Slog.v(TAG, "onServiceConnected(): " + name);
             synchronized (mServiceConnectionLock) {
-                mRemoteService = IImpressionAttestationService.Stub.asInterface(service);
+                mRemoteService = IScreenshotHasherService.Stub.asInterface(service);
                 if (mQueuedCommands != null) {
                     final int size = mQueuedCommands.size();
                     if (DEBUG) Slog.d(TAG, "running " + size + " queued commands");
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b1606c5..1f8daf6 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -22,7 +22,6 @@
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
-import static android.Manifest.permission.USE_BACKGROUND_BLUR;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
@@ -58,7 +57,7 @@
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.MergedConfiguration;
@@ -103,13 +102,14 @@
     private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
     private final DragDropController mDragDropController;
     final boolean mCanAddInternalSystemWindow;
+    private final boolean mCanStartTasksFromRecents;
+
     // If non-system overlays from this process can be hidden by the user or app using
     // HIDE_NON_SYSTEM_OVERLAY_WINDOWS.
     final boolean mOverlaysCanBeHidden;
     final boolean mCanCreateSystemApplicationOverlay;
     final boolean mCanHideNonSystemOverlayWindows;
     final boolean mCanAcquireSleepToken;
-    final boolean mCanUseBackgroundBlur;
     private AlertWindowNotification mAlertWindowNotification;
     private boolean mShowingAlertWindowNotificationAllowed;
     private boolean mClientDead = false;
@@ -134,12 +134,12 @@
         mCanCreateSystemApplicationOverlay =
                 service.mContext.checkCallingOrSelfPermission(SYSTEM_APPLICATION_OVERLAY)
                         == PERMISSION_GRANTED;
+        mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission(
+                START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED;
         mOverlaysCanBeHidden = !mCanAddInternalSystemWindow
                 && !mService.mAtmInternal.isCallerRecents(mUid);
         mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
                 == PERMISSION_GRANTED;
-        mCanUseBackgroundBlur = service.mContext.checkCallingOrSelfPermission(USE_BACKGROUND_BLUR)
-                == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
         mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
@@ -192,32 +192,30 @@
 
     @Override
     public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
-            int viewVisibility, int displayId, InsetsState requestedVisibility, Rect outFrame,
+            int viewVisibility, int displayId, InsetsState requestedVisibility,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         return mService.addWindow(this, window, attrs, viewVisibility, displayId,
-                UserHandle.getUserId(mUid), requestedVisibility, outFrame, outInputChannel,
-                outInsetsState, outActiveControls);
+                UserHandle.getUserId(mUid), requestedVisibility, outInputChannel, outInsetsState,
+                outActiveControls);
     }
 
 
     @Override
     public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
             int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
-            Rect outFrame, InputChannel outInputChannel, InsetsState outInsetsState,
+            InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
-                requestedVisibility, outFrame, outInputChannel, outInsetsState,
-                outActiveControls);
+                requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
     }
 
     @Override
     public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
             int viewVisibility, int displayId, InsetsState outInsetsState) {
         return mService.addWindow(this, window, attrs, viewVisibility, displayId,
-                UserHandle.getUserId(mUid), mDummyRequestedVisibility,
-                new Rect() /* outFrame */, null /* outInputChannel */, outInsetsState,
-                mDummyControls);
+                UserHandle.getUserId(mUid), mDummyRequestedVisibility, null /* outInputChannel */,
+                outInsetsState, mDummyControls);
     }
 
     @Override
@@ -374,8 +372,9 @@
         } else if (hasShortcut) {
             // Restrict who can start a shortcut drag since it will start the shortcut as the
             // target shortcut package
-            mService.mAtmService.enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
-                    "performDrag");
+            if (!mCanStartTasksFromRecents) {
+                throw new SecurityException("Requires START_TASKS_FROM_RECENTS permission");
+            }
             for (int i = 0; i < data.getItemCount(); i++) {
                 final ClipData.Item item = data.getItemAt(i);
                 final Intent intent = item.getIntent();
@@ -403,8 +402,9 @@
             }
         } else if (hasTask) {
             // TODO(b/169894807): Consider opening this up for tasks from the same app as the caller
-            mService.mAtmService.enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
-                    "performDrag");
+            if (!mCanStartTasksFromRecents) {
+                throw new SecurityException("Requires START_TASKS_FROM_RECENTS permission");
+            }
             for (int i = 0; i < data.getItemCount(); i++) {
                 final ClipData.Item item = data.getItemAt(i);
                 final Intent intent = item.getIntent();
@@ -860,11 +860,11 @@
     }
 
     @Override
-    public ImpressionToken generateImpressionToken(IWindow window, Rect boundsInWindow,
+    public ScreenshotHash generateScreenshotHash(IWindow window, Rect boundsInWindow,
             String hashAlgorithm) {
         final long origId = Binder.clearCallingIdentity();
         try {
-            return mService.generateImpressionToken(this, window, boundsInWindow, hashAlgorithm);
+            return mService.generateScreenshotHash(this, window, boundsInWindow, hashAlgorithm);
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 62c0527..0902948 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -191,7 +191,7 @@
             }
         }
         if (mDisplayContent.mWmService.mAccessibilityController != null) {
-            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(
+            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
                     mDisplayContent.getDisplayId());
         }
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 79d1123..e44a028 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -35,6 +35,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
 import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -124,6 +125,7 @@
 import static com.android.server.wm.Task.ActivityState.STARTED;
 import static com.android.server.wm.Task.ActivityState.STOPPING;
 import static com.android.server.wm.TaskProto.ACTIVITY_TYPE;
+import static com.android.server.wm.TaskProto.AFFINITY;
 import static com.android.server.wm.TaskProto.BOUNDS;
 import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
 import static com.android.server.wm.TaskProto.DISPLAY_ID;
@@ -917,10 +919,8 @@
             return;
         }
 
-        if (isLeafTask()) {
-            // This task is going away, so save the last state if necessary.
-            saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent());
-        }
+        // This task is going away, so save the last state if necessary.
+        saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent());
 
         // TODO: VI what about activity?
         final boolean isVoiceSession = voiceSession != null;
@@ -1244,27 +1244,20 @@
         mCallingFeatureId = r.launchedFromFeatureId;
         setIntent(intent != null ? intent : r.intent, info != null ? info : r.info);
         setLockTaskAuth(r);
-
-        final WindowContainer parent = getParent();
-        if (parent != null) {
-            final Task t = parent.asTask();
-            if (t != null) {
-                t.setIntent(r);
-            }
-        }
     }
 
     /** Sets the original intent, _without_ updating the calling uid or package. */
     private void setIntent(Intent _intent, ActivityInfo info) {
-        final boolean isLeaf = isLeafTask();
+        if (!isLeafTask()) return;
+
         if (intent == null) {
             mNeverRelinquishIdentity =
                     (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
-        } else if (mNeverRelinquishIdentity && isLeaf) {
+        } else if (mNeverRelinquishIdentity) {
             return;
         }
 
-        affinity = isLeaf ? info.taskAffinity : null;
+        affinity = info.taskAffinity;
         if (intent == null) {
             // If this task already has an intent associated with it, don't set the root
             // affinity -- we don't want it changing after initially set, but the initially
@@ -2095,7 +2088,9 @@
                 td.setEnsureNavigationBarContrastWhenTransparent(
                         atd.getEnsureNavigationBarContrastWhenTransparent());
             }
-
+            if (td.getBackgroundColorFloating() == 0) {
+                td.setBackgroundColorFloating(atd.getBackgroundColorFloating());
+            }
         }
 
         // End search once we get to root.
@@ -2320,12 +2315,9 @@
             return;
         }
 
-        final boolean windowingModeChanged = prevWindowingMode != getWindowingMode();
-        final int overrideWindowingMode = getRequestedOverrideWindowingMode();
-        // Update bounds if applicable
-        boolean hasNewOverrideBounds = false;
         // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
-        if ((overrideWindowingMode != WINDOWING_MODE_PINNED)
+        final int overrideWindowingMode = getRequestedOverrideWindowingMode();
+        if (overrideWindowingMode != WINDOWING_MODE_PINNED
                 && !getRequestedOverrideBounds().isEmpty()) {
             // If the parent (display) has rotated, rotate our bounds to best-fit where their
             // bounds were on the pre-rotated display.
@@ -2335,22 +2327,10 @@
                 mDisplayContent.rotateBounds(
                         newParentConfig.windowConfiguration.getBounds(), prevRotation, newRotation,
                         newBounds);
-                hasNewOverrideBounds = true;
+                setBounds(newBounds);
             }
         }
 
-        if (windowingModeChanged) {
-            taskDisplayArea.onRootTaskWindowingModeChanged(this);
-        }
-        if (hasNewOverrideBounds) {
-            if (inSplitScreenWindowingMode()) {
-                setBounds(newBounds);
-            } else if (overrideWindowingMode != WINDOWING_MODE_PINNED) {
-                // For root pinned task, resize is now part of the {@link
-                // WindowContainerTransaction}
-                resize(new Rect(newBounds), PRESERVE_WINDOWS, true /* deferResume */);
-            }
-        }
         if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
             // Since always on top is only on when the root task is freeform or pinned, the state
             // can be toggled when the windowing mode changes. We must make sure the root task is
@@ -2475,14 +2455,16 @@
 
     /**
      * Saves launching state if necessary so that we can launch the activity to its latest state.
-     * It only saves state if this task has been shown to user and it's in fullscreen or freeform
-     * mode on freeform displays.
      */
     private void saveLaunchingStateIfNeeded() {
         saveLaunchingStateIfNeeded(getDisplayContent());
     }
 
     private void saveLaunchingStateIfNeeded(DisplayContent display) {
+        if (!isLeafTask()) {
+            return;
+        }
+
         if (!getHasBeenVisible()) {
             // Not ever visible to user.
             return;
@@ -2865,41 +2847,61 @@
 
         adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig);
         if (windowingMode == WINDOWING_MODE_FREEFORM) {
-            // by policy, make sure the window remains within parent somewhere
-            final float density =
-                    ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
-            final Rect parentBounds =
-                    new Rect(newParentConfig.windowConfiguration.getBounds());
-            final DisplayContent display = getDisplayContent();
-            if (display != null) {
-                // If a freeform window moves below system bar, there is no way to move it again
-                // by touch. Because its caption is covered by system bar. So we exclude them
-                // from root task bounds. and then caption will be shown inside stable area.
-                final Rect stableBounds = new Rect();
-                display.getStableRect(stableBounds);
-                parentBounds.intersect(stableBounds);
-            }
+            computeFreeformBounds(outOverrideBounds, newParentConfig);
+            return;
+        }
 
-            fitWithinBounds(outOverrideBounds, parentBounds,
-                    (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
-                    (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
+        if (isSplitScreenWindowingMode(windowingMode)
+                || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+            // This is to compute whether the task should be letterboxed to handle non-resizable app
+            // in multi window. There is no split screen only logic.
+            computeLetterboxBounds(outOverrideBounds, newParentConfig);
+        }
+    }
 
-            // Prevent to overlap caption with stable insets.
-            final int offsetTop = parentBounds.top - outOverrideBounds.top;
-            if (offsetTop > 0) {
-                outOverrideBounds.offset(0, offsetTop);
-            }
+    /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. */
+    @VisibleForTesting
+    void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) {
+        // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent".
+        outBounds.setEmpty();
+        computeLetterboxBounds(outBounds, newParentConfig);
+    }
+
+    /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
+    private void computeFreeformBounds(@NonNull Rect outBounds,
+            @NonNull Configuration newParentConfig) {
+        // by policy, make sure the window remains within parent somewhere
+        final float density =
+                ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
+        final Rect parentBounds =
+                new Rect(newParentConfig.windowConfiguration.getBounds());
+        final DisplayContent display = getDisplayContent();
+        if (display != null) {
+            // If a freeform window moves below system bar, there is no way to move it again
+            // by touch. Because its caption is covered by system bar. So we exclude them
+            // from root task bounds. and then caption will be shown inside stable area.
+            final Rect stableBounds = new Rect();
+            display.getStableRect(stableBounds);
+            parentBounds.intersect(stableBounds);
+        }
+
+        fitWithinBounds(outBounds, parentBounds,
+                (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
+                (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
+
+        // Prevent to overlap caption with stable insets.
+        final int offsetTop = parentBounds.top - outBounds.top;
+        if (offsetTop > 0) {
+            outBounds.offset(0, offsetTop);
         }
     }
 
     /**
-     * Compute bounds (letterbox or pillarbox) for
-     * {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN} when the parent doesn't handle the
-     * orientation change and the requested orientation is different from the parent.
+     * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation
+     * change and the requested orientation is different from the parent.
      */
-    void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) {
-        // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent".
-        outBounds.setEmpty();
+    private void computeLetterboxBounds(@NonNull Rect outBounds,
+            @NonNull Configuration newParentConfig) {
         if (handlesOrientationChangeFromDescendant()) {
             // No need to letterbox at task level. Display will handle fixed-orientation requests.
             return;
@@ -2957,6 +2959,8 @@
         aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO
                 ? letterboxAspectRatioOverride : aspect;
 
+        // Store the current bounds to be able to revert to size compat mode values below if needed.
+        mTmpFullBounds.set(outBounds);
         if (forcedOrientation == ORIENTATION_LANDSCAPE) {
             final int height = (int) Math.rint(parentWidth / aspect);
             final int top = parentBounds.centerY() - height / 2;
@@ -2975,7 +2979,7 @@
                 // The app shouldn't be resized, we only do task letterboxing if the compat bounds
                 // is also from the same task letterbox. Otherwise, clear the task bounds to show
                 // app in size compat mode.
-                outBounds.setEmpty();
+                outBounds.set(mTmpFullBounds);
             }
         }
     }
@@ -3065,6 +3069,20 @@
         return parentTask == null ? this : parentTask.getRootTask();
     }
 
+    /** @return the first organized task. */
+    @Nullable
+    Task getOrganizedTask() {
+        if (isOrganized()) {
+            return this;
+        }
+        final WindowContainer parent = getParent();
+        if (parent == null) {
+            return null;
+        }
+        final Task parentTask = parent.asTask();
+        return parentTask == null ? null : parentTask.getOrganizedTask();
+    }
+
     // TODO(task-merge): Figure out what's the right thing to do for places that used it.
     boolean isRootTask() {
         return getRootTask() == this;
@@ -3361,8 +3379,9 @@
     @Override
     boolean handlesOrientationChangeFromDescendant() {
         return super.handlesOrientationChangeFromDescendant()
-                // Display won't rotate for the orientation request if the TaskDisplayArea can't
-                // specify orientation.
+                // Display won't rotate for the orientation request if the Task/TaskDisplayArea
+                // can't specify orientation.
+                && canSpecifyOrientation()
                 && getDisplayArea().canSpecifyOrientation();
     }
 
@@ -3875,7 +3894,9 @@
     }
 
     boolean isTaskLetterboxed() {
-        return getWindowingMode() == WINDOWING_MODE_FULLSCREEN && !matchParentBounds();
+        // No letterbox for multi window root task
+        return !matchParentBounds()
+                && (getWindowingMode() == WINDOWING_MODE_FULLSCREEN || !isRootTask());
     }
 
     @Override
@@ -4152,6 +4173,14 @@
         info.topActivityInfo = mReuseActivitiesReport.top != null
                 ? mReuseActivitiesReport.top.info
                 : null;
+        info.topActivityToken = mReuseActivitiesReport.top != null
+                ? mReuseActivitiesReport.top.appToken
+                : null;
+        // Whether the direct top activity is in size compat mode on foreground.
+        info.topActivityInSizeCompat = mReuseActivitiesReport.top != null
+                && mReuseActivitiesReport.top.getOrganizedTask() == this
+                && mReuseActivitiesReport.top.inSizeCompatMode()
+                && mReuseActivitiesReport.top.isState(RESUMED);
         info.launchCookies.clear();
         info.addLaunchCookie(mLaunchCookie);
         forAllActivities(r -> {
@@ -5201,6 +5230,12 @@
         }
     }
 
+    /** Called when the top activity in the Root Task enters or exits size compat mode. */
+    void onSizeCompatActivityChanged() {
+        // Trigger TaskInfoChanged to update the size compat restart button.
+        dispatchTaskInfoChangedIfNeeded(true /* force */);
+    }
+
     /**
      * See {@link WindowContainerTransaction#setBoundsChangeTransaction}. In short this
      * transaction will be consumed by the next BASE_APPLICATION window within our hierarchy
@@ -6047,7 +6082,9 @@
             mInResumeTopActivity = true;
 
             if (isLeafTask()) {
-                someActivityResumed = resumeTopActivityInnerLocked(prev, options);
+                if (isFocusableAndVisible()) {
+                    someActivityResumed = resumeTopActivityInnerLocked(prev, options);
+                }
             } else {
                 int idx = mChildren.size() - 1;
                 while (idx >= 0) {
@@ -7809,6 +7846,7 @@
         }
 
         proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer);
+        proto.write(AFFINITY, affinity);
 
         proto.end(token);
     }
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 3bd11ba..e18219e 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -23,7 +23,6 @@
 import android.content.ComponentName;
 import android.os.Binder;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallbackList;
@@ -52,15 +51,14 @@
     private static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
     private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG = 18;
     private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG = 19;
-    private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
-    private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 21;
-    private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 22;
-    private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 23;
-    private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 24;
-    private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 25;
-    private static final int NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG = 26;
-    private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 27;
-    private static final int NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG = 28;
+    private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 20;
+    private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 21;
+    private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 22;
+    private static final int NOTIFY_TASK_LIST_FROZEN_UNFROZEN_MSG = 23;
+    private static final int NOTIFY_TASK_FOCUS_CHANGED_MSG = 24;
+    private static final int NOTIFY_TASK_REQUESTED_ORIENTATION_CHANGED_MSG = 25;
+    private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 26;
+    private static final int NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG = 27;
 
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -151,10 +149,6 @@
         l.onTaskSnapshotChanged(m.arg1, (TaskSnapshot) m.obj);
     };
 
-    private final TaskStackConsumer mOnSizeCompatModeActivityChanged = (l, m) -> {
-        l.onSizeCompatModeActivityChanged(m.arg1, (IBinder) m.obj);
-    };
-
     private final TaskStackConsumer mNotifyTaskDisplayChanged = (l, m) -> {
         l.onTaskDisplayChanged(m.arg1, m.arg2);
     };
@@ -250,9 +244,6 @@
                 case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg);
                     break;
-                case NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG:
-                    forAllRemoteListeners(mOnSizeCompatModeActivityChanged, msg);
-                    break;
                 case NOTIFY_BACK_PRESSED_ON_TASK_ROOT:
                     forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg);
                     break;
@@ -490,17 +481,6 @@
     }
 
     /**
-     * Notify listeners that whether a size compatibility mode activity is using the override
-     * bounds which is not fit its parent.
-     */
-    void notifySizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
-        final Message msg = mHandler.obtainMessage(NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG,
-                displayId, 0 /* unused */, activityToken);
-        forAllLocalListeners(mOnSizeCompatModeActivityChanged, msg);
-        msg.sendToTarget();
-    }
-
-    /**
      * Notify listeners that an activity received a back press when there are no other activities
      * in the back stack.
      */
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 4185407..40248c4 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -47,6 +47,7 @@
 import android.os.UserHandle;
 import android.util.IntArray;
 import android.util.Slog;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
@@ -403,7 +404,11 @@
         }
         // We don't allow untrusted display to top when root task moves to top,
         // until user tapping this display to change display position as top intentionally.
-        if (!mDisplayContent.isTrusted() && !getParent().isOnTop()) {
+        //
+        // Displays with {@code mDontMoveToTop} property set to {@code true} won't be
+        // allowed to top neither.
+        if ((!mDisplayContent.isTrusted() || mDisplayContent.mDontMoveToTop)
+                && !getParent().isOnTop()) {
             includingParents = false;
         }
         final int targetPosition = findPositionForRootTask(position, child, false /* adding */);
@@ -905,6 +910,13 @@
         }
     }
 
+    @Override
+    RemoteAnimationTarget createRemoteAnimationTarget(
+            RemoteAnimationController.RemoteAnimationRecord record) {
+        final ActivityRecord activity = getTopMostActivity();
+        return activity != null ? activity.createRemoteAnimationTarget(record) : null;
+    }
+
     SurfaceControl getSplitScreenDividerAnchor() {
         return mSplitScreenDividerAnchor;
     }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index b3e0108..9fac3f0 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS;
 import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
@@ -33,6 +34,7 @@
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.SurfaceControl;
@@ -357,8 +359,14 @@
         mGlobalLock = atm.mGlobalLock;
     }
 
-    private void enforceTaskPermission(String func) {
-        mService.enforceTaskPermission(func);
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(TAG, e);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 09df71c..07610ab 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -226,9 +226,8 @@
         }
         int displayId = activity.getDisplayContent().getDisplayId();
         try {
-            final int res = session.addToDisplay(window, layoutParams,
-                    View.GONE, displayId, mTmpInsetsState, tmpFrames.frame,
-                    null /* outInputChannel */, mTmpInsetsState, mTempControls);
+            final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
+                    mTmpInsetsState, null /* outInputChannel */, mTmpInsetsState, mTempControls);
             if (res < 0) {
                 Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
                 return null;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 46aea23..98eb11f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -372,8 +372,6 @@
                     dc.mWallpaperController.startWallpaperAnimation(anim);
                 }
             }
-        }
-        if (transit == TRANSIT_KEYGUARD_GOING_AWAY) {
             dc.startKeyguardExitOnNonAppWindows(
                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
                     (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index f572e8e..43303d4 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.os.Process.INVALID_UID;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -51,7 +50,7 @@
 
     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
             DisplayContent dc, boolean ownerCanManageAppTokens, @Nullable Bundle options) {
-        super(service, token, TYPE_WALLPAPER, explicit, dc, ownerCanManageAppTokens, INVALID_UID,
+        super(service, token, TYPE_WALLPAPER, explicit, dc, ownerCanManageAppTokens,
                 false /* roundedCornerOverlay */, false /* fromClientToken */, options);
         dc.mWallpaperController.addWallpaperToken(this);
         setWindowingMode(WINDOWING_MODE_FULLSCREEN);
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 52ed278..91a6664 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -164,7 +164,7 @@
 
                 dc.checkAppWindowsReadyToShow();
                 if (accessibilityController != null) {
-                    accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId,
+                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId,
                             mTransaction);
                 }
             }
@@ -220,8 +220,6 @@
             mRemoveReplacedWindows = false;
         }
 
-        mService.destroyPreservedSurfaceLocked();
-
         mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
         executeAfterPrepareSurfacesRunnables();
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 03fca11..dd4ee877 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2684,6 +2684,14 @@
             @Nullable ArrayList<WindowContainer> sources) {
         final Task task = asTask();
         if (task != null && !enter && !task.isHomeOrRecentsRootTask()) {
+            if (AppTransition.isClosingTransitOld(transit)) {
+                // Freezes the insets state when the window is in app exiting transition, to
+                // ensure the exiting window won't receive unexpected insets changes from the
+                // next window.
+                task.forAllWindows(w -> {
+                    w.freezeInsetsState();
+                }, true /* traverseTopToBottom */);
+            }
             mDisplayContent.showImeScreenshot();
         }
         final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
@@ -2831,7 +2839,7 @@
         }
         mSurfaceAnimationSources.clear();
         if (mDisplayContent != null) {
-            mDisplayContent.onWindowAnimationFinished(type);
+            mDisplayContent.onWindowAnimationFinished(this, type);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index a5ebf9a..015a0fb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -49,6 +49,10 @@
     static final String KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS =
             "system_gesture_exclusion_log_debounce_millis";
 
+    // Enable logging from the sensor which publishes accel and gyro data generating a rotation
+    // event
+    private static final String KEY_RAW_SENSOR_LOGGING_ENABLED = "raw_sensor_logging_enabled";
+
     private static final int MIN_GESTURE_EXCLUSION_LIMIT_DP = 200;
 
     /** @see #KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS */
@@ -58,6 +62,8 @@
     /** @see AndroidDeviceConfig#KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE */
     boolean mSystemGestureExcludedByPreQStickyImmersive;
 
+    boolean mRawSensorLoggingEnabled;
+
     private final WindowManagerGlobalLock mGlobalLock;
     private final Runnable mUpdateSystemGestureExclusionCallback;
     private final DeviceConfigInterface mDeviceConfig;
@@ -133,6 +139,9 @@
                     case KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS:
                         updateSystemGestureExclusionLogDebounceMillis();
                         break;
+                    case KEY_RAW_SENSOR_LOGGING_ENABLED:
+                        updateRawSensorDataLoggingEnabled();
+                        break;
                     default:
                         break;
                 }
@@ -158,6 +167,12 @@
                 KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
     }
 
+    private void updateRawSensorDataLoggingEnabled() {
+        mRawSensorLoggingEnabled = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                KEY_RAW_SENSOR_LOGGING_ENABLED, false);
+    }
+
     void dump(PrintWriter pw) {
         pw.println("WINDOW MANAGER CONSTANTS (dumpsys window constants):");
 
@@ -167,6 +182,8 @@
         pw.print("="); pw.println(mSystemGestureExclusionLimitDp);
         pw.print("  "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
         pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
+        pw.print("  "); pw.print(KEY_RAW_SENSOR_LOGGING_ENABLED);
+        pw.print("="); pw.println(mRawSensorLoggingEnabled);
         pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index a3a9eb7..bb04ace 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -23,12 +23,15 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.display.DisplayManagerInternal;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.view.Display;
 import android.view.IInputFilter;
+import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IWindow;
 import android.view.InputChannel;
 import android.view.MagnificationSpec;
+import android.view.RemoteAnimationTarget;
 import android.view.WindowInfo;
 import android.view.WindowManager.DisplayImePolicy;
 
@@ -46,6 +49,41 @@
 public abstract class WindowManagerInternal {
 
     /**
+     * Interface for accessibility features implemented by AccessibilityController inside
+     * WindowManager.
+     */
+    public interface AccessibilityControllerInternal {
+        /**
+         * Enable the accessibility trace logging.
+         */
+        void startTrace();
+
+        /**
+         * Disable the accessibility trace logging.
+         */
+        void stopTrace();
+
+        /**
+         * Is trace enabled or not.
+         */
+        boolean isEnabled();
+
+        /**
+         * Add an accessibility trace entry.
+         *
+         * @param where A string to identify this log entry, which can be used to filter/search
+         *        through the tracing file.
+         * @param callingParams The parameters for the method to be logged.
+         * @param a11yDump The proto byte array for a11y state when the entry is generated.
+         * @param callingUid The calling uid.
+         * @param stackTrace The stack trace, null if not needed.
+         */
+        void logTrace(
+                String where, String callingParams, byte[] a11yDump, int callingUid,
+                StackTraceElement[] stackTrace);
+    }
+
+    /**
      * Interface to receive a callback when the windows reported for
      * accessibility changed.
      */
@@ -154,6 +192,21 @@
     }
 
     /**
+     * An interface to be notified when keyguard exit animation should start.
+     */
+    public interface KeyguardExitAnimationStartListener {
+        /**
+         * Called when keyguard exit animation should start.
+         * @param apps The list of apps to animate.
+         * @param wallpapers The list of wallpapers to animate.
+         * @param finishedCallback The callback to invoke when the animation is finished.
+         */
+        void onAnimationStart(RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
+                IRemoteAnimationFinishedCallback finishedCallback);
+    }
+
+    /**
       * An interface to be notified about hardware keyboard status.
       */
     public interface OnHardKeyboardStatusChangeListener {
@@ -207,6 +260,11 @@
     }
 
     /**
+     * Request the interface to access features implemented by AccessibilityController.
+     */
+    public abstract AccessibilityControllerInternal getAccessibilityController();
+
+    /**
      * Request that the window manager call
      * {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
      * within a surface transaction at a later time.
@@ -351,8 +409,10 @@
      * @param token The token to add.
      * @param type The window type.
      * @param displayId The display to add the token to.
+     * @param options A bundle used to pass window-related options.
      */
-    public abstract void addWindowToken(android.os.IBinder token, int type, int displayId);
+    public abstract void addWindowToken(@NonNull android.os.IBinder token, int type, int displayId,
+            @Nullable Bundle options);
 
     /**
      * Removes a window token.
@@ -372,6 +432,14 @@
     public abstract void registerAppTransitionListener(AppTransitionListener listener);
 
     /**
+     * Registers a listener to be notified to start the keyguard exit animation.
+     *
+     * @param listener The listener to register.
+     */
+    public abstract void registerKeyguardExitAnimationStartListener(
+            KeyguardExitAnimationStartListener listener);
+
+    /**
      * Reports that the password for the given user has changed.
      */
     public abstract void reportPasswordChanged(int userId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b6fabee3..a41761f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -23,7 +23,6 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
 import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
-import static android.Manifest.permission.STATUS_BAR_SERVICE;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
@@ -35,7 +34,6 @@
 import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.IInputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
-import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.myPid;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -82,8 +80,6 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
-import static android.view.WindowManagerGlobal.ADD_TOO_MANY_TOKENS;
-import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
@@ -160,6 +156,7 @@
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Point;
@@ -201,7 +198,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
-import android.service.attestation.ImpressionToken;
+import android.service.screenshot.ScreenshotHash;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
 import android.sysprop.SurfaceFlingerProperties;
@@ -404,6 +401,8 @@
     // trying to apply a new one.
     private static final boolean ALWAYS_KEEP_CURRENT = true;
 
+    static final int LOGTAG_INPUT_FOCUS = 62001;
+
     /**
      * Restrict ability of activities overriding transition animation in a way such that
      * an activity can do it only when the transition happens within a same task.
@@ -412,7 +411,6 @@
      */
     private static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
             "persist.wm.disable_custom_task_animation";
-    static final int LOGTAG_INPUT_FOCUS = 62001;
 
     /**
      * @see #DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
@@ -420,6 +418,19 @@
     static boolean sDisableCustomTaskAnimationProperty =
             SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
 
+    /**
+     * Run Keyguard animation as remote animation in System UI instead of local animation in
+     * the server process.
+     */
+    private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
+            "persist.wm.enable_remote_keyguard_animation";
+
+    /**
+     * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
+     */
+    public static boolean sEnableRemoteKeyguardAnimation =
+            SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
+
     private static final String DISABLE_TRIPLE_BUFFERING_PROPERTY =
             "ro.sf.disable_triple_buffer";
 
@@ -443,12 +454,6 @@
 
     private static final int ANIMATION_COMPLETED_TIMEOUT_MS = 5000;
 
-    /** The maximum count of window tokens without surface that an app can register. */
-    private static final int MAXIMUM_WINDOW_TOKEN_COUNT_WITHOUT_SURFACE = 5;
-
-    /** System UI can create more window context... */
-    private static final int SYSTEM_UI_MULTIPLIER = 2;
-
     /**
      * Override of task letterbox aspect ratio that is set via ADB with
      * set-task-letterbox-aspect-ratio or via {@link
@@ -457,7 +462,8 @@
      */
     static final float MIN_TASK_LETTERBOX_ASPECT_RATIO = 1.0f;
 
-    final WindowManagerConstants mConstants;
+    @VisibleForTesting
+    WindowManagerConstants mConstants;
 
     final WindowTracing mWindowTracing;
 
@@ -624,13 +630,6 @@
     final ArrayList<WindowState> mDestroySurface = new ArrayList<>();
 
     /**
-     * Windows with a preserved surface waiting to be destroyed. These windows
-     * are going through a surface change. We keep the old surface around until
-     * the first frame on the new surface finishes drawing.
-     */
-    final ArrayList<WindowState> mDestroyPreservedSurface = new ArrayList<>();
-
-    /**
      * This is set when we have run out of memory, and will either be an empty
      * list or contain windows that need to be force removed.
      */
@@ -767,8 +766,9 @@
     final EmbeddedWindowController mEmbeddedWindowController;
     final AnrController mAnrController;
 
-    private final ImpressionAttestationController mImpressionAttestationController;
-    private final WindowContextListenerController mWindowContextListenerController =
+    private final ScreenshotHashController mScreenshotHashController;
+    @VisibleForTesting
+    final WindowContextListenerController mWindowContextListenerController =
             new WindowContextListenerController();
 
     @VisibleForTesting
@@ -1010,7 +1010,30 @@
 
     // Aspect ratio of task level letterboxing, values <= MIN_TASK_LETTERBOX_ASPECT_RATIO will be
     // ignored.
-    private float mTaskLetterboxAspectRatio;
+    private volatile float mTaskLetterboxAspectRatio;
+
+    /** Enum for Letterbox background type. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
+            LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING})
+    @interface LetterboxBackgroundType {};
+    /** Solid background using color specified in R.color.config_letterboxBackgroundColor. */
+    static final int LETTERBOX_BACKGROUND_SOLID_COLOR = 0;
+
+    /** Color specified in R.attr.colorBackground for the letterboxed application. */
+    static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND = 1;
+
+    /** Color specified in R.attr.colorBackgroundFloating for the letterboxed application. */
+    static final int LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING = 2;
+
+    // Corners radius for activities presented in the letterbox mode, values < 0 will be ignored.
+    private volatile int mLetterboxActivityCornersRadius;
+
+    // Color for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type.
+    private volatile Color mLetterboxBackgroundColor;
+
+    @LetterboxBackgroundType
+    private volatile int mLetterboxBackgroundType;
 
     final InputManagerService mInputManager;
     final DisplayManagerInternal mDisplayManagerInternal;
@@ -1237,8 +1260,15 @@
                 com.android.internal.R.bool.config_perDisplayFocusEnabled);
         mAssistantOnTopOfDream = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_assistantOnTopOfDream);
+
         mTaskLetterboxAspectRatio = context.getResources().getFloat(
                 com.android.internal.R.dimen.config_taskLetterboxAspectRatio);
+        mLetterboxActivityCornersRadius = context.getResources().getInteger(
+                com.android.internal.R.integer.config_letterboxActivityCornersRadius);
+        mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor(
+                com.android.internal.R.color.config_letterboxBackgroundColor));
+        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(context);
+
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
 
@@ -1387,7 +1417,7 @@
         mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources(
                 mContext.getResources());
 
-        mImpressionAttestationController = new ImpressionAttestationController(mContext);
+        mScreenshotHashController = new ScreenshotHashController(mContext);
         setGlobalShadowSettings();
         mAnrController = new AnrController(this);
         mStartingSurfaceController = new StartingSurfaceController(this);
@@ -1456,7 +1486,7 @@
     }
 
     public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
-            int displayId, int requestUserId, InsetsState requestedVisibility, Rect outFrame,
+            int displayId, int requestUserId, InsetsState requestedVisibility,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         Arrays.fill(outActiveControls, null);
@@ -1571,7 +1601,7 @@
                     final Bundle options = mWindowContextListenerController
                             .getOptions(windowContextToken);
                     token = new WindowToken(this, binder, type, false /* persistOnEmpty */,
-                            displayContent, session.mCanAddInternalSystemWindow, callingUid,
+                            displayContent, session.mCanAddInternalSystemWindow,
                             isRoundedCornerOverlay, true /* fromClientToken */, options);
                 } else {
                     final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
@@ -1647,7 +1677,7 @@
 
             final WindowState win = new WindowState(this, session, client, token, parentWindow,
                     appOp[0], attrs, viewVisibility, session.mUid, userId,
-                    session.mCanAddInternalSystemWindow, session.mCanUseBackgroundBlur);
+                    session.mCanAddInternalSystemWindow);
             if (win.mDeathRecipient == null) {
                 // Client has apparently died, so there is no reason to
                 // continue.
@@ -1798,7 +1828,7 @@
                 prepareNoneTransitionForRelaunching(activity);
             }
 
-            if (displayPolicy.getLayoutHint(win.mAttrs, token, outFrame, outInsetsState,
+            if (displayPolicy.getLayoutHint(win.mAttrs, token, outInsetsState,
                     win.isClientLocal())) {
                 res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
             }
@@ -2137,6 +2167,7 @@
 
     void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets,
             Rect visibleInsets, Region touchableRegion) {
+        int uid = Binder.getCallingUid();
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
@@ -2162,8 +2193,8 @@
 
                     // We need to report touchable region changes to accessibility.
                     if (mAccessibilityController != null) {
-                        mAccessibilityController.onSomeWindowResizedOrMovedLocked(
-                                w.getDisplayContent().getDisplayId());
+                        mAccessibilityController.onSomeWindowResizedOrMovedWithCallingUid(
+                                uid, w.getDisplayContent().getDisplayId());
                     }
                 }
             }
@@ -2177,7 +2208,7 @@
             if (mAccessibilityController != null) {
                 WindowState window = mWindowMap.get(token);
                 if (window != null) {
-                    mAccessibilityController.onRectangleOnScreenRequestedLocked(
+                    mAccessibilityController.onRectangleOnScreenRequested(
                             window.getDisplayId(), rectangle);
                 }
             }
@@ -2280,8 +2311,8 @@
                 if (((attrChanges & LayoutParams.ACCESSIBILITY_TITLE_CHANGED) != 0)
                         && (mAccessibilityController != null)) {
                     // No move or resize, but the controller checks for title changes as well
-                    mAccessibilityController.onSomeWindowResizedOrMovedLocked(
-                            win.getDisplayContent().getDisplayId());
+                    mAccessibilityController.onSomeWindowResizedOrMovedWithCallingUid(
+                            uid, win.getDisplayContent().getDisplayId());
                 }
 
                 if ((privateFlagChanges & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
@@ -2296,7 +2327,6 @@
 
             if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility
                     + " req=" + requestedWidth + "x" + requestedHeight + " " + win.mAttrs);
-            winAnimator.mSurfaceDestroyDeferred = (flags & RELAYOUT_DEFER_SURFACE_DESTROY) != 0;
             if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) {
                 winAnimator.mAlpha = attrs.alpha;
             }
@@ -2563,11 +2593,12 @@
         } else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
             focusMayChange = true;
             win.mAnimatingExit = true;
-        } else if (win.isAnimating(TRANSITION | PARENTS)) {
+        } else if (win.mDisplayContent.okToAnimate() && win.isAnimating(TRANSITION | PARENTS)) {
             // Currently in a hide animation... turn this into
             // an exit.
             win.mAnimatingExit = true;
-        } else if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) {
+        } else if (win.mDisplayContent.okToAnimate()
+                && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) {
             // If the wallpaper is currently behind this
             // window, we need to change both of them inside
             // of a transaction to avoid artifacts.
@@ -2581,7 +2612,7 @@
             win.destroySurface(false, stopped);
         }
         if (mAccessibilityController != null) {
-            mAccessibilityController.onWindowTransitionLocked(win, transit);
+            mAccessibilityController.onWindowTransition(win, transit);
         }
 
         return focusMayChange;
@@ -2674,93 +2705,49 @@
     }
 
     @Override
-    public void addWindowToken(IBinder binder, int type, int displayId) {
-        addWindowTokenWithOptions(binder, type, displayId, null /* options */,
-                null /* packageName */, false /* fromClientToken */);
-    }
-
-    @Override
-    public int addWindowTokenWithOptions(IBinder binder, int type, int displayId, Bundle options,
-            String packageName) {
-        if (tokenCountExceed()) {
-            return ADD_TOO_MANY_TOKENS;
+    public void addWindowToken(@NonNull IBinder binder, int type, int displayId,
+            @Nullable Bundle options) {
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
         }
-        return addWindowTokenWithOptions(binder, type, displayId, options, packageName,
-                true /* fromClientToken */);
-    }
 
-    private boolean tokenCountExceed() {
-        final int callingUid = Binder.getCallingUid();
-        // Don't check if caller is from system server.
-        if (callingUid == myPid()) {
-            return false;
-        }
-        final int limit =
-                (checkCallingPermission(STATUS_BAR_SERVICE, "addWindowTokenWithOptions"))
-                        ?  MAXIMUM_WINDOW_TOKEN_COUNT_WITHOUT_SURFACE * SYSTEM_UI_MULTIPLIER
-                        : MAXIMUM_WINDOW_TOKEN_COUNT_WITHOUT_SURFACE;
         synchronized (mGlobalLock) {
-            int[] count = new int[1];
-            mRoot.forAllDisplays(d -> count[0] += d.getWindowTokensWithoutSurfaceCount(callingUid));
-            return count[0] >= limit;
+            final DisplayContent dc = getDisplayContentOrCreate(displayId, null /* token */);
+            if (dc == null) {
+                ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add token: %s"
+                        + " for non-exiting displayId=%d", binder, displayId);
+                return;
+            }
+
+            WindowToken token = dc.getWindowToken(binder);
+            if (token != null) {
+                ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add binder token: %s"
+                        + " for already created window token: %s"
+                        + " displayId=%d", binder, token, displayId);
+                return;
+            }
+            if (type == TYPE_WALLPAPER) {
+                new WallpaperWindowToken(this, binder, true, dc,
+                        true /* ownerCanManageAppTokens */, options);
+            } else {
+                new WindowToken(this, binder, type, true /* persistOnEmpty */, dc,
+                        true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */,
+                        false /* fromClientToken */, options);
+            }
         }
     }
 
-    private int addWindowTokenWithOptions(IBinder binder, int type, int displayId, Bundle options,
-            String packageName, boolean fromClientToken) {
-        final boolean callerCanManageAppTokens =
-                checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()");
-        // WindowContext users usually don't hold MANAGE_APP_TOKEN permission. Check permissions
-        // by checkAddPermission.
-        if (!callerCanManageAppTokens) {
-            final int res = mPolicy.checkAddPermission(type, false /* isRoundedCornerOverlay */,
-                    packageName, new int[1]);
-            if (res != ADD_OKAY) {
-                return res;
-            }
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                if (!callerCanManageAppTokens) {
-                    if (packageName == null || !unprivilegedAppCanCreateTokenWith(
-                            null /* parentWindow */, callingUid, type, type, binder,
-                            packageName)) {
-                        throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
-                    }
-                }
-
-                final DisplayContent dc = getDisplayContentOrCreate(displayId, null /* token */);
-                if (dc == null) {
-                    ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add token: %s"
-                            + " for non-exiting displayId=%d", binder, displayId);
-                    return WindowManagerGlobal.ADD_INVALID_DISPLAY;
-                }
-
-                WindowToken token = dc.getWindowToken(binder);
-                if (token != null) {
-                    ProtoLog.w(WM_ERROR, "addWindowToken: Attempted to add binder token: %s"
-                            + " for already created window token: %s"
-                            + " displayId=%d", binder, token, displayId);
-                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
-                }
-                // TODO(window-container): Clean up dead tokens
-                if (type == TYPE_WALLPAPER) {
-                    new WallpaperWindowToken(this, binder, true, dc, callerCanManageAppTokens,
-                            options);
-                } else {
-                    new WindowToken(this, binder, type, true, dc, callerCanManageAppTokens,
-                            callingUid, false /* roundedCornerOverlay */, fromClientToken, options);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-        return WindowManagerGlobal.ADD_OKAY;
-    }
-
+    /**
+     * Registers a listener for a {@link android.app.WindowContext} to subscribe to configuration
+     * changes of a {@link DisplayArea}.
+     *
+     * @param clientToken the window context's token
+     * @param type Window type of the window context
+     * @param displayId The display associated with the window context
+     * @param options A bundle used to pass window-related options and choose the right DisplayArea
+     *
+     * @return {@code true} if the listener was registered successfully.
+     */
     @Override
     public boolean registerWindowContextListener(IBinder clientToken, int type, int displayId,
             Bundle options) {
@@ -2816,6 +2803,7 @@
         }
     }
 
+    /** Returns {@code true} if this binder is a registered window token. */
     @Override
     public boolean isWindowToken(IBinder binder) {
         synchronized (mGlobalLock) {
@@ -2826,38 +2814,26 @@
 
     @Override
     public void removeWindowToken(IBinder binder, int displayId) {
-        final boolean callerCanManageAppTokens =
-                checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()");
-        final WindowToken windowToken;
-        synchronized (mGlobalLock) {
-            windowToken = mRoot.getWindowToken(binder);
+        if (!checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
         }
-        if (windowToken == null) {
-            ProtoLog.w(WM_ERROR,
-                    "removeWindowToken: Attempted to remove non-existing token: %s", binder);
-            return;
-        }
-        final int callingUid = Binder.getCallingUid();
-
-        // If MANAGE_APP_TOKEN permission is not held(usually from WindowContext), callers can only
-        // remove the window tokens which they added themselves.
-        if (!callerCanManageAppTokens && (windowToken.getOwnerUid() == INVALID_UID
-                || callingUid != windowToken.getOwnerUid())) {
-            throw new SecurityException("removeWindowToken: Requires MANAGE_APP_TOKENS permission"
-                    + " to remove token owned by another uid");
-        }
-
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
                 final DisplayContent dc = mRoot.getDisplayContent(displayId);
+
                 if (dc == null) {
                     ProtoLog.w(WM_ERROR, "removeWindowToken: Attempted to remove token: %s"
                             + " for non-exiting displayId=%d", binder, displayId);
                     return;
                 }
-
-                dc.removeWindowToken(binder);
+                final WindowToken token = dc.removeWindowToken(binder);
+                if (token == null) {
+                    ProtoLog.w(WM_ERROR,
+                            "removeWindowToken: Attempted to remove non-existing token: %s",
+                            binder);
+                    return;
+                }
                 dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             }
         } finally {
@@ -3896,14 +3872,7 @@
      * the framework implementation will be used to determine the aspect ratio.
      */
     void setTaskLetterboxAspectRatio(float aspectRatio) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                mTaskLetterboxAspectRatio = aspectRatio;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
+        mTaskLetterboxAspectRatio = aspectRatio;
     }
 
     /**
@@ -3911,35 +3880,116 @@
      * com.android.internal.R.dimen.config_taskLetterboxAspectRatio}.
      */
     void resetTaskLetterboxAspectRatio() {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                mTaskLetterboxAspectRatio = mContext.getResources().getFloat(
-                            com.android.internal.R.dimen.config_taskLetterboxAspectRatio);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
+        mTaskLetterboxAspectRatio = mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_taskLetterboxAspectRatio);
     }
 
     /**
      * Gets the aspect ratio of task level letterboxing.
      */
     float getTaskLetterboxAspectRatio() {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                return mTaskLetterboxAspectRatio;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
+        return mTaskLetterboxAspectRatio;
+    }
+
+    /**
+     * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
+     * both it and a value of {@link
+     * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
+     * and corners of the activity won't be rounded.
+     */
+    void setLetterboxActivityCornersRadius(int cornersRadius) {
+        mLetterboxActivityCornersRadius = cornersRadius;
+    }
+
+    /**
+     * Resets corners raidus for activities presented in the letterbox mode to {@link
+     * com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
+     */
+    void resetLetterboxActivityCornersRadius() {
+        mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_letterboxActivityCornersRadius);
+    }
+
+    /**
+     * Whether corners of letterboxed activities are rounded.
+     */
+    boolean isLetterboxActivityCornersRounded() {
+        return getLetterboxActivityCornersRadius() > 0;
+    }
+
+    /**
+     * Gets corners raidus for activities presented in the letterbox mode.
+     */
+    int getLetterboxActivityCornersRadius() {
+        return mLetterboxActivityCornersRadius;
+    }
+
+    /**
+     * Gets color of letterbox background which is  used when {@link
+     * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+     * fallback for other backfround types.
+     */
+    Color getLetterboxBackgroundColor() {
+        return mLetterboxBackgroundColor;
+    }
+
+
+    /**
+     * Sets color of letterbox background which is used when {@link
+     * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+     * fallback for other backfround types.
+     */
+    void setLetterboxBackgroundColor(Color color) {
+        mLetterboxBackgroundColor = color;
+    }
+
+    /**
+     * Resets color of letterbox background to {@link
+     * com.android.internal.R.color.config_letterboxBackgroundColor}.
+     */
+    void resetLetterboxBackgroundColor() {
+        mLetterboxBackgroundColor = Color.valueOf(mContext.getResources().getColor(
+                com.android.internal.R.color.config_letterboxBackgroundColor));
+    }
+
+    /**
+     * Gets {@link LetterboxBackgroundType} specified in {@link
+     * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
+     */
+    @LetterboxBackgroundType
+    int getLetterboxBackgroundType() {
+        return mLetterboxBackgroundType;
+    }
+
+    /** Sets letterbox background type. */
+    void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
+        mLetterboxBackgroundType = backgroundType;
+    }
+
+    /**
+     * Resets cletterbox background type to {@link
+     * com.android.internal.R.integer.config_letterboxBackgroundType}.
+     */
+    void resetLetterboxBackgroundType() {
+        mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+    }
+
+    @LetterboxBackgroundType
+    private static int readLetterboxBackgroundTypeFromConfig(Context context) {
+        int backgroundType = context.getResources().getInteger(
+                com.android.internal.R.integer.config_letterboxBackgroundType);
+        return backgroundType == LETTERBOX_BACKGROUND_SOLID_COLOR
+                    || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND
+                    || backgroundType == LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING
+                    ? backgroundType : LETTERBOX_BACKGROUND_SOLID_COLOR;
     }
 
     @Override
     public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) {
-        mAtmInternal.enforceCallerIsRecentsOrHasPermission(
-                android.Manifest.permission.SET_ORIENTATION, "setIgnoreOrientationRequest()");
+        if (!checkCallingPermission(
+                android.Manifest.permission.SET_ORIENTATION, "setIgnoreOrientationRequest()")) {
+            throw new SecurityException("Requires SET_ORIENTATION permission");
+        }
 
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -5384,14 +5434,6 @@
         }
     }
 
-    void destroyPreservedSurfaceLocked() {
-        for (int i = mDestroyPreservedSurface.size() - 1; i >= 0 ; i--) {
-            final WindowState w = mDestroyPreservedSurface.get(i);
-            w.mWinAnimator.destroyPreservedSurfaceLocked(w.getSyncTransaction());
-        }
-        mDestroyPreservedSurface.clear();
-    }
-
     // -------------------------------------------------------------
     // IWindowManager API
     // -------------------------------------------------------------
@@ -6002,8 +6044,10 @@
 
     @Override
     public void setRecentsVisibility(boolean visible) {
-        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.STATUS_BAR,
-                "setRecentsVisibility()");
+        if (!checkCallingPermission(
+                android.Manifest.permission.STATUS_BAR, "setRecentsVisibility()")) {
+            throw new SecurityException("Requires STATUS_BAR permission");
+        }
         synchronized (mGlobalLock) {
             mPolicy.setRecentsVisibilityLw(visible);
         }
@@ -6011,8 +6055,11 @@
 
     @Override
     public void hideTransientBars(int displayId) {
-        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.STATUS_BAR,
-                "hideTransientBars()");
+        if (!checkCallingPermission(
+                android.Manifest.permission.STATUS_BAR, "hideTransientBars()")) {
+            throw new SecurityException("Requires STATUS_BAR permission");
+        }
+
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent != null) {
@@ -7029,6 +7076,7 @@
         checkCallerOwnsDisplay(displayId);
 
         synchronized (mGlobalLock) {
+            int uid = Binder.getCallingUid();
             final long token = Binder.clearCallingIdentity();
             try {
                 final WindowState win = windowForClientLocked(null, client, false);
@@ -7040,8 +7088,8 @@
                 // Notifies AccessibilityController to re-compute the window observer of
                 // this embedded display
                 if (mAccessibilityController != null) {
-                    mAccessibilityController.handleWindowObserverOfEmbeddedDisplayLocked(displayId,
-                            win);
+                    mAccessibilityController.handleWindowObserverOfEmbeddedDisplay(
+                            displayId, win, uid);
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -7407,6 +7455,12 @@
     private final class LocalService extends WindowManagerInternal {
 
         @Override
+        public AccessibilityControllerInternal getAccessibilityController() {
+            return AccessibilityController.getAccessibilityControllerInternal(
+                    WindowManagerService.this);
+        }
+
+        @Override
         public void clearSnapshotCache() {
             synchronized (mGlobalLock) {
                 mTaskSnapshotController.clearSnapshotCache();
@@ -7424,7 +7478,7 @@
         public void setMagnificationSpec(int displayId, MagnificationSpec spec) {
             synchronized (mGlobalLock) {
                 if (mAccessibilityController != null) {
-                    mAccessibilityController.setMagnificationSpecLocked(displayId, spec);
+                    mAccessibilityController.setMagnificationSpec(displayId, spec);
                 } else {
                     throw new IllegalStateException("Magnification callbacks not set!");
                 }
@@ -7435,7 +7489,7 @@
         public void setForceShowMagnifiableBounds(int displayId, boolean show) {
             synchronized (mGlobalLock) {
                 if (mAccessibilityController != null) {
-                    mAccessibilityController.setForceShowMagnifiableBoundsLocked(displayId, show);
+                    mAccessibilityController.setForceShowMagnifiableBounds(displayId, show);
                 } else {
                     throw new IllegalStateException("Magnification callbacks not set!");
                 }
@@ -7446,8 +7500,7 @@
         public void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion) {
             synchronized (mGlobalLock) {
                 if (mAccessibilityController != null) {
-                    mAccessibilityController.getMagnificationRegionLocked(displayId,
-                            magnificationRegion);
+                    mAccessibilityController.getMagnificationRegion(displayId, magnificationRegion);
                 } else {
                     throw new IllegalStateException("Magnification callbacks not set!");
                 }
@@ -7463,7 +7516,7 @@
                 }
                 MagnificationSpec spec = null;
                 if (mAccessibilityController != null) {
-                    spec = mAccessibilityController.getMagnificationSpecForWindowLocked(windowState);
+                    spec = mAccessibilityController.getMagnificationSpecForWindow(windowState);
                 }
                 if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
                     return null;
@@ -7485,9 +7538,9 @@
                     mAccessibilityController = new AccessibilityController(
                             WindowManagerService.this);
                 }
-                boolean result = mAccessibilityController.setMagnificationCallbacksLocked(
+                boolean result = mAccessibilityController.setMagnificationCallbacks(
                         displayId, callbacks);
-                if (!mAccessibilityController.hasCallbacksLocked()) {
+                if (!mAccessibilityController.hasCallbacks()) {
                     mAccessibilityController = null;
                 }
                 return result;
@@ -7503,9 +7556,9 @@
                             WindowManagerService.this);
                 }
                 final boolean result =
-                        mAccessibilityController.setWindowsForAccessibilityCallbackLocked(
+                        mAccessibilityController.setWindowsForAccessibilityCallback(
                         displayId, callback);
-                if (!mAccessibilityController.hasCallbacksLocked()) {
+                if (!mAccessibilityController.hasCallbacks()) {
                     mAccessibilityController = null;
                 }
                 return result;
@@ -7594,8 +7647,9 @@
         }
 
         @Override
-        public void addWindowToken(IBinder token, int type, int displayId) {
-            WindowManagerService.this.addWindowToken(token, type, displayId);
+        public void addWindowToken(IBinder token, int type, int displayId,
+                @Nullable Bundle options) {
+            WindowManagerService.this.addWindowToken(token, type, displayId, options);
         }
 
         @Override
@@ -7638,6 +7692,15 @@
         }
 
         @Override
+        public void registerKeyguardExitAnimationStartListener(
+                KeyguardExitAnimationStartListener listener) {
+            synchronized (mGlobalLock) {
+                getDefaultDisplayContentLocked().mAppTransition
+                        .registerKeygaurdExitAnimationStartListener(listener);
+            }
+        }
+
+        @Override
         public void reportPasswordChanged(int userId) {
             mKeyguardDisableHandler.updateKeyguardEnabled(userId);
         }
@@ -7694,7 +7757,7 @@
                 accessibilityController = mAccessibilityController;
             }
             if (accessibilityController != null) {
-                accessibilityController.performComputeChangedWindowsNotLocked(displayId, true);
+                accessibilityController.performComputeChangedWindowsNot(displayId, true);
             }
         }
 
@@ -8314,8 +8377,11 @@
 
     /** Return whether layer tracing is enabled */
     public boolean isLayerTracing() {
-        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
-                "isLayerTracing");
+        if (!checkCallingPermission(
+                android.Manifest.permission.DUMP, "isLayerTracing()")) {
+            throw new SecurityException("Requires DUMP permission");
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             Parcel data = null;
@@ -8347,8 +8413,11 @@
 
     /** Enable or disable layer tracing */
     public void setLayerTracing(boolean enabled) {
-        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
-                "setLayerTracing");
+        if (!checkCallingPermission(
+                android.Manifest.permission.DUMP, "setLayerTracing()")) {
+            throw new SecurityException("Requires DUMP permission");
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             Parcel data = null;
@@ -8374,8 +8443,11 @@
 
     /** Set layer tracing flags. */
     public void setLayerTracingFlags(int flags) {
-        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
-                "setLayerTracingFlags");
+        if (!checkCallingPermission(
+                android.Manifest.permission.DUMP, "setLayerTracingFlags")) {
+            throw new SecurityException("Requires DUMP permission");
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             Parcel data = null;
@@ -8435,8 +8507,8 @@
                             + "could not be found!");
                 }
                 final WindowToken windowToken = dc.getWindowToken(attrs.token);
-                return dc.getDisplayPolicy().getLayoutHint(attrs, windowToken,
-                        mTmpRect /* outFrame */, outInsetsState, fromLocal);
+                return dc.getDisplayPolicy().getLayoutHint(attrs, windowToken, outInsetsState,
+                        fromLocal);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -8541,38 +8613,38 @@
     }
 
     @Override
-    public String[] getSupportedImpressionAlgorithms() {
-        return mImpressionAttestationController.getSupportedImpressionAlgorithms();
+    public String[] getSupportedScreenshotHashingAlgorithms() {
+        return mScreenshotHashController.getSupportedHashingAlgorithms();
     }
 
     @Override
-    public boolean verifyImpressionToken(ImpressionToken impressionToken) {
-        return mImpressionAttestationController.verifyImpressionToken(impressionToken);
+    public boolean verifyScreenshotHash(ScreenshotHash screenshotHash) {
+        return mScreenshotHashController.verifyScreenshotHash(screenshotHash);
     }
 
-    ImpressionToken generateImpressionToken(Session session, IWindow window,
+    ScreenshotHash generateScreenshotHash(Session session, IWindow window,
             Rect boundsInWindow, String hashAlgorithm) {
         final SurfaceControl displaySurfaceControl;
         final Rect boundsInDisplay = new Rect(boundsInWindow);
         synchronized (mGlobalLock) {
             final WindowState win = windowForClientLocked(session, window, false);
             if (win == null) {
-                Slog.w(TAG, "Failed to generate impression token. Invalid window");
+                Slog.w(TAG, "Failed to generate ScreenshotHash. Invalid window");
                 return null;
             }
 
             DisplayContent displayContent = win.getDisplayContent();
             if (displayContent == null) {
-                Slog.w(TAG, "Failed to generate impression token. Window is not on a display");
+                Slog.w(TAG, "Failed to generate ScreenshotHash. Window is not on a display");
                 return null;
             }
 
             displaySurfaceControl = displayContent.getSurfaceControl();
-            mImpressionAttestationController.calculateImpressionTokenBoundsLocked(win,
+            mScreenshotHashController.calculateScreenshotHashBoundsLocked(win,
                     boundsInWindow, boundsInDisplay);
 
             if (boundsInDisplay.isEmpty()) {
-                Slog.w(TAG, "Failed to generate impression token. Bounds are not on screen");
+                Slog.w(TAG, "Failed to generate ScreenshotHash. Bounds are not on screen");
                 return null;
             }
         }
@@ -8592,11 +8664,11 @@
                 SurfaceControl.captureLayers(args);
         if (screenshotHardwareBuffer == null
                 || screenshotHardwareBuffer.getHardwareBuffer() == null) {
-            Slog.w(TAG, "Failed to generate impression token. Failed to take screenshot");
+            Slog.w(TAG, "Failed to generate ScreenshotHash. Failed to take screenshot");
             return null;
         }
 
-        return mImpressionAttestationController.generateImpressionToken(
+        return mScreenshotHashController.generateScreenshotHash(
                 screenshotHardwareBuffer.getHardwareBuffer(), boundsInWindow, hashAlgorithm);
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index a3a9c1c..645786c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -18,6 +18,11 @@
 
 import static android.os.Build.IS_USER;
 
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR;
+
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.ParcelFileDescriptor;
@@ -34,6 +39,7 @@
 import com.android.internal.protolog.ProtoLogImpl;
 import com.android.server.LocalServices;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerService.LetterboxBackgroundType;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -115,6 +121,18 @@
                     return runSetTaskLetterboxAspectRatio(pw);
                 case "get-task-letterbox-aspect-ratio":
                     return runGetTaskLetterboxAspectRatio(pw);
+                case "set-letterbox-activity-corners-radius":
+                    return runSetLetterboxActivityCornersRadius(pw);
+                case "get-letterbox-activity-corners-radius":
+                    return runGetLetterboxActivityCornersRadius(pw);
+                case "set-letterbox-background-type":
+                    return runSetLetterboxBackgroundType(pw);
+                case "get-letterbox-background-type":
+                    return runGetLetterboxBackgroundType(pw);
+                case "set-letterbox-background-color":
+                    return runSetLetterboxBackgroundColor(pw);
+                case "get-letterbox-background-color":
+                    return runGetLetterboxBackgroundColor(pw);
                 case "reset":
                     return runReset(pw);
                 default:
@@ -545,6 +563,111 @@
         return 0;
     }
 
+    private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException {
+        final int cornersRadius;
+        try {
+            String arg = getNextArgRequired();
+            if ("reset".equals(arg)) {
+                mInternal.resetLetterboxActivityCornersRadius();
+                return 0;
+            }
+            cornersRadius = Integer.parseInt(arg);
+        } catch (NumberFormatException  e) {
+            getErrPrintWriter().println("Error: bad corners radius format " + e);
+            return -1;
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or corners radius should be provided as an argument " + e);
+            return -1;
+        }
+
+        mInternal.setLetterboxActivityCornersRadius(cornersRadius);
+        return 0;
+    }
+
+    private int runGetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException {
+        final int cornersRadius = mInternal.getLetterboxActivityCornersRadius();
+        if (cornersRadius < 0) {
+            pw.println("Letterbox corners radius is not set");
+        } else {
+            pw.println("Letterbox corners radius is " + cornersRadius);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
+        @LetterboxBackgroundType final int backgroundType;
+
+        String arg = getNextArgRequired();
+        if ("reset".equals(arg)) {
+            mInternal.resetLetterboxBackgroundType();
+            return 0;
+        }
+        switch (arg) {
+            case "solid_color":
+                backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR;
+                break;
+            case "app_color_background":
+                backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+                break;
+            case "app_color_background_floating":
+                backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+                break;
+            default:
+                getErrPrintWriter().println(
+                        "Error: 'reset', 'solid_color' or 'app_color_background' should "
+                        + "be provided as an argument");
+                return -1;
+        }
+
+        mInternal.setLetterboxBackgroundType(backgroundType);
+        return 0;
+    }
+
+    private int runGetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
+        @LetterboxBackgroundType final int backgroundType = mInternal.getLetterboxBackgroundType();
+        switch (backgroundType) {
+            case LETTERBOX_BACKGROUND_SOLID_COLOR:
+                pw.println("Letterbox background type is 'solid_color'");
+                break;
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND:
+                pw.println("Letterbox background type is 'app_color_background'");
+                break;
+            case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING:
+                pw.println("Letterbox background type is 'app_color_background_floating'");
+                break;
+            default:
+                throw new AssertionError("Unexpected letterbox background type: " + backgroundType);
+        }
+        return 0;
+    }
+
+    private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
+        final Color color;
+        String arg = getNextArgRequired();
+        try {
+            if ("reset".equals(arg)) {
+                mInternal.resetLetterboxBackgroundColor();
+                return 0;
+            }
+            color = Color.valueOf(Color.parseColor(arg));
+        } catch (IllegalArgumentException  e) {
+            getErrPrintWriter().println(
+                    "Error: 'reset' or color in #RRGGBB format should be provided as "
+                            + "an argument " + e + " but got " + arg);
+            return -1;
+        }
+
+        mInternal.setLetterboxBackgroundColor(color);
+        return 0;
+    }
+
+    private int runGetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
+        final Color color = mInternal.getLetterboxBackgroundColor();
+        pw.println("Letterbox background color is " + Integer.toHexString(color.toArgb()));
+        return 0;
+    }
+
     private int runReset(PrintWriter pw) throws RemoteException {
         int displayId = getDisplayId(getNextArg());
 
@@ -572,6 +695,15 @@
         // set-task-letterbox-aspect-ratio
         mInternal.resetTaskLetterboxAspectRatio();
 
+        // set-letterbox-activity-corners-radius
+        mInternal.resetLetterboxActivityCornersRadius();
+
+        // set-letterbox-background-type
+        mInternal.resetLetterboxBackgroundType();
+
+        // set-letterbox-background-color
+        mInternal.resetLetterboxBackgroundColor();
+
         pw.println("Reset all settings for displayId=" + displayId);
         return 0;
     }
@@ -608,6 +740,21 @@
                 + WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO);
         pw.println("    both it and R.dimen.config_taskLetterboxAspectRatio will be ignored");
         pw.println("    and framework implementation will be used to determine aspect ratio.");
+        pw.println("  set-letterbox-activity-corners-radius [reset|cornersRadius]");
+        pw.println("  get-letterbox-activity-corners-radius");
+        pw.println("    Corners radius for activities in the letterbox mode. If radius < 0,");
+        pw.println("    both it and R.integer.config_letterboxActivityCornersRadius will be");
+        pw.println("    ignored and corners of the activity won't be rounded.");
+        pw.println("  set-letterbox-background-color [reset|colorName|'\\#RRGGBB']");
+        pw.println("  get-letterbox-background-color");
+        pw.println("    Color of letterbox background which is be used when letterbox background");
+        pw.println("    type is 'solid-color'. Use get(set)-letterbox-background-type to check");
+        pw.println("    and control letterbox background type. See Color#parseColor for allowed");
+        pw.println("    color formats (#RRGGBB and some colors by name, e.g. magenta or olive). ");
+        pw.println("  set-letterbox-background-type [reset|solid_color|app_color_background");
+        pw.println("    |app_color_background_floating]");
+        pw.println("  get-letterbox-background-type");
+        pw.println("    Type of background used in the letterbox mode.");
         pw.println("  reset [-d DISPLAY_ID]");
         pw.println("    Reset all override settings.");
         if (!IS_USER) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 043844b..63a0832 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.Manifest.permission.READ_FRAME_BUFFER;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
@@ -39,6 +40,7 @@
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -111,6 +113,16 @@
     }
 
     @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            throw ActivityTaskManagerService.logAndRethrowRuntimeExceptionOnTransact(TAG, e);
+        }
+    }
+
+    @Override
     public void applyTransaction(WindowContainerTransaction t) {
         enforceTaskPermission("applyTransaction()");
         if (t == null) {
@@ -264,57 +276,63 @@
             }
             // Hierarchy changes
             final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
-            for (int i = 0, n = hops.size(); i < n; ++i) {
-                final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
-                switch (hop.getType()) {
-                    case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
-                        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
-                        final Task task = wc != null ? wc.asTask() : null;
-                        if (task != null) {
-                            task.getDisplayArea().setLaunchRootTask(task,
-                                    hop.getWindowingModes(), hop.getActivityTypes());
-                        } else {
-                            throw new IllegalArgumentException(
-                                    "Cannot set non-task as launch root: " + wc);
+            if (!hops.isEmpty() && mService.isInLockTaskMode()) {
+                Slog.w(TAG, "Attempt to perform hierarchy operations while in lock task mode...");
+            } else {
+                for (int i = 0, n = hops.size(); i < n; ++i) {
+                    final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
+                    switch (hop.getType()) {
+                        case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
+                            final WindowContainer wc = WindowContainer.fromBinder(
+                                    hop.getContainer());
+                            final Task task = wc != null ? wc.asTask() : null;
+                            if (task != null) {
+                                task.getDisplayArea().setLaunchRootTask(task,
+                                        hop.getWindowingModes(), hop.getActivityTypes());
+                            } else {
+                                throw new IllegalArgumentException(
+                                        "Cannot set non-task as launch root: " + wc);
+                            }
+                            break;
                         }
-                        break;
-                    }
-                    case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
-                        effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
-                        break;
-                    case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
-                        effects |= setAdjacentRootsHierarchyOp(hop);
-                        break;
-                    case HIERARCHY_OP_TYPE_REORDER:
-                    case HIERARCHY_OP_TYPE_REPARENT:
-                        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
-                        if (wc == null || !wc.isAttached()) {
-                            Slog.e(TAG, "Attempt to operate on detached container: " + wc);
-                            continue;
-                        }
-                        if (syncId >= 0) {
-                            addToSyncSet(syncId, wc);
-                        }
-                        if (transition != null) {
-                            transition.collect(wc);
-                            if (hop.isReparent()) {
-                                if (wc.getParent() != null) {
-                                    // Collect the current parent. It's visibility may change as
-                                    // a result of this reparenting.
-                                    transition.collect(wc.getParent());
-                                }
-                                if (hop.getNewParent() != null) {
-                                    final WindowContainer parentWc =
-                                            WindowContainer.fromBinder(hop.getNewParent());
-                                    if (parentWc == null) {
-                                        Slog.e(TAG, "Can't resolve parent window from token");
-                                        continue;
+                        case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
+                            effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
+                            break;
+                        case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
+                            effects |= setAdjacentRootsHierarchyOp(hop);
+                            break;
+                        case HIERARCHY_OP_TYPE_REORDER:
+                        case HIERARCHY_OP_TYPE_REPARENT:
+                            final WindowContainer wc = WindowContainer.fromBinder(
+                                    hop.getContainer());
+                            if (wc == null || !wc.isAttached()) {
+                                Slog.e(TAG, "Attempt to operate on detached container: " + wc);
+                                continue;
+                            }
+                            if (syncId >= 0) {
+                                addToSyncSet(syncId, wc);
+                            }
+                            if (transition != null) {
+                                transition.collect(wc);
+                                if (hop.isReparent()) {
+                                    if (wc.getParent() != null) {
+                                        // Collect the current parent. It's visibility may change as
+                                        // a result of this reparenting.
+                                        transition.collect(wc.getParent());
                                     }
-                                    transition.collect(parentWc);
+                                    if (hop.getNewParent() != null) {
+                                        final WindowContainer parentWc =
+                                                WindowContainer.fromBinder(hop.getNewParent());
+                                        if (parentWc == null) {
+                                            Slog.e(TAG, "Can't resolve parent window from token");
+                                            continue;
+                                        }
+                                        transition.collect(parentWc);
+                                    }
                                 }
                             }
-                        }
-                        effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+                            effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+                    }
                 }
             }
             // Queue-up bounds-change transactions for tasks which are now organized. Do
@@ -398,11 +416,7 @@
                         new Configuration(container.getRequestedOverrideConfiguration());
                 c.setTo(change.getConfiguration(), configMask, windowMask);
                 container.onRequestedOverrideConfigurationChanged(c);
-                // TODO(b/145675353): remove the following once we could apply new bounds to the
-                // root pinned task together with its children.
             }
-            resizeRootPinnedTaskIfNeeded(container, configMask, windowMask,
-                    container.getRequestedOverrideConfiguration());
             effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
         }
         if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
@@ -412,6 +426,10 @@
         }
 
         if (windowingMode > -1) {
+            if (mService.isInLockTaskMode() && windowingMode != WINDOWING_MODE_FULLSCREEN) {
+                throw new UnsupportedOperationException("Not supported to set non-fullscreen"
+                        + " windowing mode during locked task mode.");
+            }
             container.setWindowingMode(windowingMode);
         }
         return effects;
@@ -648,19 +666,6 @@
         return effects;
     }
 
-    private void resizeRootPinnedTaskIfNeeded(ConfigurationContainer container, int configMask,
-            int windowMask, Configuration config) {
-        if ((container instanceof Task)
-                && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0)
-                && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) {
-            final Task rootTask = (Task) container;
-            if (rootTask.inPinnedWindowingMode()) {
-                rootTask.resize(config.windowConfiguration.getBounds(),
-                        PRESERVE_WINDOWS, true /* deferResume */);
-            }
-        }
-    }
-
     @Override
     public ITaskOrganizerController getTaskOrganizerController() {
         enforceTaskPermission("getTaskOrganizerController()");
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
similarity index 96%
rename from services/core/java/com/android/server/policy/WindowOrientationListener.java
rename to services/core/java/com/android/server/wm/WindowOrientationListener.java
index 17e81da..da31bb2 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.policy;
+package com.android.server.wm;
 
 import static com.android.server.wm.WindowOrientationListenerProto.ENABLED;
 import static com.android.server.wm.WindowOrientationListenerProto.ROTATION;
@@ -31,6 +31,8 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Surface;
 
+import com.android.internal.util.FrameworkStatsLog;
+
 import java.io.PrintWriter;
 import java.util.List;
 
@@ -63,6 +65,7 @@
     private OrientationJudge mOrientationJudge;
     private int mCurrentRotation = -1;
     private final Context mContext;
+    private final WindowManagerConstants mConstants;
 
     private final Object mLock = new Object();
 
@@ -71,9 +74,11 @@
      *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
+     * @param wmService WindowManagerService to read the device config from.
      */
-    public WindowOrientationListener(Context context, Handler handler) {
-        this(context, handler, SensorManager.SENSOR_DELAY_UI);
+    public WindowOrientationListener(
+            Context context, Handler handler, WindowManagerService wmService) {
+        this(context, handler, wmService, SensorManager.SENSOR_DELAY_UI);
     }
 
     /**
@@ -81,6 +86,7 @@
      *
      * @param context for the WindowOrientationListener.
      * @param handler Provides the Looper for receiving sensor updates.
+     * @param wmService WindowManagerService to read the device config from.
      * @param rate at which sensor events are processed (see also
      * {@link android.hardware.SensorManager SensorManager}). Use the default
      * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
@@ -88,10 +94,12 @@
      *
      * This constructor is private since no one uses it.
      */
-    private WindowOrientationListener(Context context, Handler handler, int rate) {
+    private WindowOrientationListener(
+            Context context, Handler handler, WindowManagerService wmService, int rate) {
         mContext = context;
         mHandler = handler;
-        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+        mConstants = wmService.mConstants;
+        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
         mRate = rate;
         List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION);
         Sensor wakeUpDeviceOrientationSensor = null;
@@ -497,7 +505,7 @@
         private static final float MIN_ACCELERATION_MAGNITUDE =
                 SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE;
         private static final float MAX_ACCELERATION_MAGNITUDE =
-            SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
+                SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
 
         // Maximum absolute tilt angle at which to consider orientation data.  Beyond this (i.e.
         // when screen is facing the sky or ground), we completely ignore orientation data
@@ -1072,8 +1080,14 @@
                 mDesiredRotation = reportedRotation;
                 newRotation = evaluateRotationChangeLocked();
             }
-            if (newRotation >=0) {
+            if (newRotation >= 0) {
                 onProposedRotationChanged(newRotation);
+                if (mConstants.mRawSensorLoggingEnabled) {
+                    FrameworkStatsLog.write(
+                            FrameworkStatsLog.DEVICE_ROTATED,
+                            event.timestamp,
+                            rotationToLogEnum(reportedRotation));
+                }
             }
         }
 
@@ -1180,5 +1194,20 @@
                 }
             }
         };
+
+        private int rotationToLogEnum(int rotation) {
+            switch (rotation) {
+                case 0:
+                    return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__ROTATION_0;
+                case 1:
+                    return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__ROTATION_90;
+                case 2:
+                    return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__ROTATION_180;
+                case 3:
+                    return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__ROTATION_270;
+                default:
+                    return FrameworkStatsLog.DEVICE_ROTATED__PROPOSED_ORIENTATION__UNKNOWN;
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 5676909..8b4d415 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -24,13 +24,11 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.util.Preconditions.checkArgument;
 import static com.android.server.am.ActivityManagerService.MY_PID;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
 import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 import static com.android.server.wm.Task.ActivityState.DESTROYED;
@@ -61,8 +59,6 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
@@ -103,10 +99,6 @@
     final String mName;
     final int mUid;
 
-    // A set of tokens that currently contribute to this process being temporarily allowed
-    // to start activities even if it's not in the foreground. The values of this map are optional
-    // (can be null) and are used to trace back the grant to the notification token mechanism.
-    private final ArrayMap<Binder, IBinder> mBackgroundActivityStartTokens = new ArrayMap<>();
     // The process of this application; 0 if none
     private volatile int mPid;
     // user of process.
@@ -118,6 +110,7 @@
     final ArraySet<String> mPkgList = new ArraySet<>();
     private final WindowProcessListener mListener;
     private final ActivityTaskManagerService mAtm;
+    private final BackgroundLaunchProcessController mBgLaunchController;
     // The actual proc...  may be null only if 'persistent' is true (in which case we are in the
     // process of launching the app)
     private IApplicationThread mThread;
@@ -169,8 +162,6 @@
     private volatile boolean mPerceptible;
     // Set to true when process was launched with a wrapper attached
     private volatile boolean mUsingWrapper;
-    // Set of UIDs of clients currently bound to this process
-    private volatile ArraySet<Integer> mBoundClientUids = new ArraySet<Integer>();
 
     // Thread currently set for VR scheduling
     int mVrThreadTid;
@@ -191,10 +182,10 @@
     // The most recent top-most activity that was resumed in the process for pre-Q app.
     private ActivityRecord mPreQTopResumedActivity = null;
     // The last time an activity was launched in the process
-    private long mLastActivityLaunchTime;
+    private volatile long mLastActivityLaunchTime;
     // The last time an activity was finished in the process while the process participated
     // in a visible task
-    private long mLastActivityFinishTime;
+    private volatile long mLastActivityFinishTime;
 
     // Last configuration that was reported to the process.
     private final Configuration mLastReportedConfiguration = new Configuration();
@@ -227,9 +218,6 @@
     /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
     private boolean mRunningRemoteAnimation;
 
-    @Nullable
-    private final BackgroundActivityStartCallback mBackgroundActivityStartCallback;
-
     // The bits used for mActivityStateFlags.
     private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
     private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
@@ -246,8 +234,8 @@
      */
     private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
 
-    public WindowProcessController(@NonNull ActivityTaskManagerService atm, ApplicationInfo info,
-            String name, int uid, int userId, Object owner,
+    public WindowProcessController(@NonNull ActivityTaskManagerService atm,
+            @NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
             @NonNull WindowProcessListener listener) {
         mInfo = info;
         mName = name;
@@ -256,7 +244,8 @@
         mOwner = owner;
         mListener = listener;
         mAtm = atm;
-        mBackgroundActivityStartCallback = mAtm.getBackgroundActivityStartCallback();
+        mBgLaunchController = new BackgroundLaunchProcessController(
+                atm::hasActiveVisibleWindow, atm.getBackgroundActivityStartCallback());
 
         boolean isSysUiPackage = info.packageName.equals(
                 mAtm.getSysUiServiceComponentLocked().getPackageName());
@@ -489,162 +478,59 @@
     }
 
     void setLastActivityFinishTimeIfNeeded(long finishTime) {
-        if (finishTime <= mLastActivityFinishTime || !hasActivityInVisibleTask()) {
+        if (finishTime <= mLastActivityFinishTime || !hasVisibleActivities()) {
             return;
         }
         mLastActivityFinishTime = finishTime;
     }
 
     /**
-     * Allows background activity starts using token {@code entity}. Optionally, you can provide
-     * {@code originatingToken} if you have one such originating token, this is useful for tracing
-     * back the grant in the case of the notification token.
-     *
-     * If {@code entity} is already added, this method will update its {@code originatingToken}.
+     * @see BackgroundLaunchProcessController#addOrUpdateAllowBackgroundActivityStartsToken(Binder,
+     * IBinder)
      */
     public void addOrUpdateAllowBackgroundActivityStartsToken(Binder entity,
             @Nullable IBinder originatingToken) {
-        synchronized (mAtm.mGlobalLock) {
-            mBackgroundActivityStartTokens.put(entity, originatingToken);
-        }
+        mBgLaunchController.addOrUpdateAllowBackgroundActivityStartsToken(entity, originatingToken);
     }
 
-    /**
-     * Removes token {@code entity} that allowed background activity starts added via {@link
-     * #addOrUpdateAllowBackgroundActivityStartsToken(Binder, IBinder)}.
-     */
+    /** @see BackgroundLaunchProcessController#removeAllowBackgroundActivityStartsToken(Binder) */
     public void removeAllowBackgroundActivityStartsToken(Binder entity) {
-        synchronized (mAtm.mGlobalLock) {
-            mBackgroundActivityStartTokens.remove(entity);
-        }
+        mBgLaunchController.removeAllowBackgroundActivityStartsToken(entity);
     }
 
     /**
      * Is this WindowProcessController in the state of allowing background FGS start?
      */
+    @HotPath(caller = HotPath.START_SERVICE)
     public boolean areBackgroundFgsStartsAllowed() {
-        synchronized (mAtm.mGlobalLock) {
-            return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesAllowed(), true);
-        }
+        return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesAllowed(),
+                true /* isCheckingForFgsStart */);
     }
 
     boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed) {
-        return areBackgroundActivityStartsAllowed(appSwitchAllowed, false);
+        return areBackgroundActivityStartsAllowed(appSwitchAllowed,
+                false /* isCheckingForFgsStart */);
     }
 
-    boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed,
+    private boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed,
             boolean isCheckingForFgsStart) {
-        // If app switching is not allowed, we ignore all the start activity grace period
-        // exception so apps cannot start itself in onPause() after pressing home button.
-        if (appSwitchAllowed) {
-            // allow if any activity in the caller has either started or finished very recently, and
-            // it must be started or finished after last stop app switches time.
-            final long now = SystemClock.uptimeMillis();
-            if (now - mLastActivityLaunchTime < ACTIVITY_BG_START_GRACE_PERIOD_MS
-                    || now - mLastActivityFinishTime < ACTIVITY_BG_START_GRACE_PERIOD_MS) {
-                // if activity is started and finished before stop app switch time, we should not
-                // let app to be able to start background activity even it's in grace period.
-                if (mLastActivityLaunchTime > mAtm.getLastStopAppSwitchesTime()
-                        || mLastActivityFinishTime > mAtm.getLastStopAppSwitchesTime()) {
-                    if (DEBUG_ACTIVITY_STARTS) {
-                        Slog.d(TAG, "[WindowProcessController(" + mPid
-                                + ")] Activity start allowed: within "
-                                + ACTIVITY_BG_START_GRACE_PERIOD_MS + "ms grace period");
-                    }
-                    return true;
-                }
-                if (DEBUG_ACTIVITY_STARTS) {
-                    Slog.d(TAG, "[WindowProcessController(" + mPid + ")] Activity start within "
-                            + ACTIVITY_BG_START_GRACE_PERIOD_MS
-                            + "ms grace period but also within stop app switch window");
-                }
-
-            }
-        }
-        // allow if the proc is instrumenting with background activity starts privs
-        if (mInstrumentingWithBackgroundActivityStartPrivileges) {
-            if (DEBUG_ACTIVITY_STARTS) {
-                Slog.d(TAG, "[WindowProcessController(" + mPid
-                        + ")] Activity start allowed: process instrumenting with background "
-                        + "activity starts privileges");
-            }
-            return true;
-        }
-        // allow if the caller has an activity in any foreground task
-        if (appSwitchAllowed && hasActivityInVisibleTask()) {
-            if (DEBUG_ACTIVITY_STARTS) {
-                Slog.d(TAG, "[WindowProcessController(" + mPid
-                        + ")] Activity start allowed: process has activity in foreground task");
-            }
-            return true;
-        }
-        // allow if the caller is bound by a UID that's currently foreground
-        if (isBoundByForegroundUid()) {
-            if (DEBUG_ACTIVITY_STARTS) {
-                Slog.d(TAG, "[WindowProcessController(" + mPid
-                        + ")] Activity start allowed: process bound by foreground uid");
-            }
-            return true;
-        }
-        // allow if the flag was explicitly set
-        if (isBackgroundStartAllowedByToken(isCheckingForFgsStart)) {
-            if (DEBUG_ACTIVITY_STARTS) {
-                Slog.d(TAG, "[WindowProcessController(" + mPid
-                        + ")] Activity start allowed: process allowed by token");
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * If there are no tokens, we don't allow *by token*. If there are tokens and
-     * isCheckingForFgsStart is false, we ask the callback if the start is allowed for these tokens,
-     * otherwise if there is no callback we allow.
-     */
-    private boolean isBackgroundStartAllowedByToken(boolean isCheckingForFgsStart) {
-        if (mBackgroundActivityStartTokens.isEmpty()) {
-            return false;
-        }
-
-        if (isCheckingForFgsStart) {
-            /// The checking is for BG-FGS-start.
-            return true;
-        }
-
-        if (mBackgroundActivityStartCallback == null) {
-            // We have tokens but no callback to decide => allow
-            return true;
-        }
-        // The callback will decide
-        return mBackgroundActivityStartCallback.isActivityStartAllowed(
-                mBackgroundActivityStartTokens.values(), mInfo.uid, mInfo.packageName);
+        return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName,
+                appSwitchAllowed, isCheckingForFgsStart, hasVisibleActivities(),
+                mInstrumentingWithBackgroundActivityStartPrivileges,
+                mAtm.getLastStopAppSwitchesTime(),
+                mLastActivityLaunchTime, mLastActivityFinishTime);
     }
 
     /**
      * Returns whether this process is allowed to close system dialogs via a background activity
      * start token that allows the close system dialogs operation (eg. notification).
      */
-    public boolean canCloseSystemDialogsByToken() {
-        synchronized (mAtm.mGlobalLock) {
-            return !mBackgroundActivityStartTokens.isEmpty()
-                    && mBackgroundActivityStartCallback != null
-                    && mBackgroundActivityStartCallback.canCloseSystemDialogs(
-                            mBackgroundActivityStartTokens.values(), mInfo.uid);
-        }
-    }
-
-    private boolean isBoundByForegroundUid() {
-        for (int i = mBoundClientUids.size() - 1; i >= 0; --i) {
-            if (mAtm.hasActiveVisibleWindow(mBoundClientUids.valueAt(i))) {
-                return true;
-            }
-        }
-        return false;
+    boolean canCloseSystemDialogsByToken() {
+        return mBgLaunchController.canCloseSystemDialogsByToken(mUid);
     }
 
     public void setBoundClientUids(ArraySet<Integer> boundClientUids) {
-        mBoundClientUids = boundClientUids;
+        mBgLaunchController.setBoundClientUids(boundClientUids);
     }
 
     /**
@@ -793,20 +679,6 @@
         return displayArea;
     }
 
-    private boolean hasActivityInVisibleTask() {
-        for (int i = mActivities.size() - 1; i >= 0; --i) {
-            Task task = mActivities.get(i).getTask();
-            if (task == null) {
-                continue;
-            }
-            ActivityRecord topActivity = task.getTopNonFinishingActivity();
-            if (topActivity != null && topActivity.mVisibleRequested) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Update the top resuming activity in process for pre-Q apps, only the top-most visible
      * activities are allowed to be resumed per process.
@@ -1701,17 +1573,8 @@
             if (mVrThreadTid != 0) {
                 pw.print(prefix); pw.print("mVrThreadTid="); pw.println(mVrThreadTid);
             }
-            if (mBackgroundActivityStartTokens.size() > 0) {
-                pw.print(prefix);
-                pw.println("Background activity start tokens (token: originating token):");
-                for (int i = 0; i < mBackgroundActivityStartTokens.size(); i++) {
-                    pw.print(prefix); pw.print("  - ");
-                    pw.print(mBackgroundActivityStartTokens.keyAt(i));
-                    pw.print(": ");
-                    pw.println(mBackgroundActivityStartTokens.valueAt(i));
 
-                }
-            }
+            mBgLaunchController.dump(pw, prefix);
         }
         pw.println(prefix + " Configuration=" + getConfiguration());
         pw.println(prefix + " OverrideConfiguration=" + getRequestedOverrideConfiguration());
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3be4e78..c1f2d02 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -104,7 +104,6 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
-import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -233,6 +232,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.Surface;
 import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
@@ -296,8 +296,6 @@
     final int mShowUserId;
     /** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
     final boolean mOwnerCanAddInternalSystemWindow;
-    /** The owner has {@link android.Manifest.permission#USE_BACKGROUND_BLUR} */
-    final boolean mOwnerCanUseBackgroundBlur;
     final WindowId mWindowId;
     WindowToken mToken;
     // The same object as mToken if this is an app window and null for non-app windows.
@@ -440,6 +438,7 @@
     float mGlobalScale=1;
     float mLastGlobalScale=1;
     float mInvGlobalScale=1;
+    float mOverrideScale = 1;
     float mHScale=1, mVScale=1;
     float mLastHScale=1, mLastVScale=1;
     final Matrix mTmpMatrix = new Matrix();
@@ -718,6 +717,12 @@
     private @Nullable InsetsSourceProvider mControllableInsetProvider;
     private final InsetsState mRequestedInsetsState = new InsetsState();
 
+    /**
+     * Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
+     * (e.g app exiting transition)
+     */
+    private InsetsState mFrozenInsetsState;
+
     @Nullable InsetsSourceProvider mPendingPositionChanged;
 
     private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
@@ -730,6 +735,13 @@
      */
     int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;
 
+    /**
+     * This is the frame rate which is passed to SurfaceFlinger if the window is part of the
+     * high refresh rate deny list. The variable is cached, so we do not send too many updates to
+     * SF.
+     */
+    float mDenyListFrameRate = 0f;
+
     static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
 
     private final WindowProcessController mWpcForDisplayAreaConfigChanges;
@@ -756,6 +768,33 @@
         }
     }
 
+    /**
+     * Set a freeze state for the window to ignore dispatching its insets state to the client.
+     *
+     * Used to keep the insets state for some use cases. (e.g. app exiting transition)
+     */
+    void freezeInsetsState() {
+        if (mFrozenInsetsState == null) {
+            mFrozenInsetsState = new InsetsState(getInsetsState(), true /* copySources */);
+        }
+    }
+
+    void clearFrozenInsetsState() {
+        mFrozenInsetsState = null;
+    }
+
+    InsetsState getFrozenInsetsState() {
+        return mFrozenInsetsState;
+    }
+
+    /**
+     * Check if the insets state of the window is ready to dispatch to the client when invoking
+     * {@link InsetsStateController#notifyInsetsChanged}.
+     */
+    boolean isReadyToDispatchInsetsState() {
+        return isVisible() && mFrozenInsetsState == null;
+    }
+
     void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
             @Rotation int rotation, boolean requested) {
         // Invisible windows and the wallpaper do not participate in the seamless rotation animation
@@ -855,11 +894,9 @@
 
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
-            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
-            boolean ownerCanUseBackgroundBlur) {
+            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
         this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
-                ownerCanAddInternalSystemWindow, ownerCanUseBackgroundBlur,
-                new PowerManagerWrapper() {
+                ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
                     @Override
                     public void wakeUp(long time, @WakeReason int reason, String details) {
                         service.mPowerManager.wakeUp(time, reason, details);
@@ -875,7 +912,7 @@
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
             int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
-            boolean ownerCanUseBackgroundBlur, PowerManagerWrapper powerManagerWrapper) {
+            PowerManagerWrapper powerManagerWrapper) {
         super(service);
         mTmpTransaction = service.mTransactionFactory.get();
         mSession = s;
@@ -886,7 +923,6 @@
         mOwnerUid = ownerId;
         mShowUserId = showUserId;
         mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
-        mOwnerCanUseBackgroundBlur = ownerCanUseBackgroundBlur;
         mWindowId = new WindowId(this);
         mAttrs.copyFrom(a);
         mLastSurfaceInsets.set(mAttrs.surfaceInsets);
@@ -973,6 +1009,8 @@
         mLastRequestedWidth = 0;
         mLastRequestedHeight = 0;
         mLayer = 0;
+        mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale(
+                mAttrs.packageName, s.mUid);
 
         // Make sure we initial all fields before adding to parentWindow, to prevent exception
         // during onDisplayChanged.
@@ -1005,8 +1043,15 @@
         mSession.windowAddedLocked(mAttrs.packageName);
     }
 
+    /**
+     * @return {@code true} if the application runs in size compatibility mode or has an app level
+     * scaling override set.
+     * @see CompatModePackages#getCompatScale
+     * @see android.content.res.CompatibilityInfo#supportsScreen
+     * @see ActivityRecord#inSizeCompatMode()
+     */
     boolean inSizeCompatMode() {
-        return inSizeCompatMode(mAttrs, mActivityRecord);
+        return mOverrideScale != 1f || inSizeCompatMode(mAttrs, mActivityRecord);
     }
 
     /**
@@ -1641,7 +1686,13 @@
 
     void prelayout() {
         if (inSizeCompatMode()) {
-            mGlobalScale = mToken.getSizeCompatScale();
+            if (mOverrideScale != 1f) {
+                mGlobalScale = mToken.hasSizeCompatBounds()
+                        ? mToken.getSizeCompatScale() * mOverrideScale
+                        : mOverrideScale;
+            } else {
+                mGlobalScale = mToken.getSizeCompatScale();
+            }
             mInvGlobalScale = 1 / mGlobalScale;
         } else {
             mGlobalScale = mInvGlobalScale = 1;
@@ -1969,7 +2020,7 @@
                 final int winTransit = TRANSIT_EXIT;
                 mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */);
                 if (accessibilityController != null) {
-                    accessibilityController.onWindowTransitionLocked(this, winTransit);
+                    accessibilityController.onWindowTransition(this, winTransit);
                 }
             }
             setDisplayLayoutNeeded();
@@ -1983,7 +2034,7 @@
         if (isVisibleNow()) {
             mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
             if (mWmService.mAccessibilityController != null) {
-                mWmService.mAccessibilityController.onWindowTransitionLocked(this, TRANSIT_EXIT);
+                mWmService.mAccessibilityController.onWindowTransition(this, TRANSIT_EXIT);
             }
             changed = true;
             if (displayContent != null) {
@@ -2061,7 +2112,7 @@
         }
 
         if (mWmService.mAccessibilityController != null) {
-            mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(getDisplayId());
+            mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
         }
         updateLocationInParentDisplayIfNeeded();
 
@@ -2108,12 +2159,12 @@
         return getDisplayContent().getBounds().equals(getBounds());
     }
 
-    boolean matchesRootDisplayAreaBounds() {
-        RootDisplayArea root = getRootDisplayArea();
-        if (root == null || root == getDisplayContent()) {
+    boolean matchesDisplayAreaBounds() {
+        final DisplayArea displayArea = getDisplayArea();
+        if (displayArea == null) {
             return matchesDisplayBounds();
         }
-        return root.getBounds().equals(getBounds());
+        return displayArea.getBounds().equals(getBounds());
     }
 
     /**
@@ -2191,7 +2242,6 @@
 
         disposeInputChannel();
 
-        mWinAnimator.destroyDeferredSurfaceLocked(mTmpTransaction);
         mWinAnimator.destroySurfaceLocked(mTmpTransaction);
         mTmpTransaction.apply();
         mSession.windowRemovedLocked();
@@ -2295,7 +2345,7 @@
                         mWmService.requestTraversal();
                     }
                     if (mWmService.mAccessibilityController != null) {
-                        mWmService.mAccessibilityController.onWindowTransitionLocked(this, transit);
+                        mWmService.mAccessibilityController.onWindowTransition(this, transit);
                     }
                 }
                 final boolean isAnimating = mAnimatingExit || isAnimating(TRANSITION | PARENTS,
@@ -2599,8 +2649,7 @@
         // scaling but the existing logic doesn't expect that. The result is that the already-
         // scaled region ends up getting sent to surfaceflinger which then applies the scale
         // (again). Until this is resolved, apply an inverse-scale here.
-        if (mActivityRecord != null && mActivityRecord.hasSizeCompatBounds()
-                && mGlobalScale != 1.f) {
+        if (mInvGlobalScale != 1.f) {
             region.scale(mInvGlobalScale);
         }
 
@@ -3258,11 +3307,6 @@
             return destroyedSomething;
         }
 
-        if (appStopped || mWindowRemovalAllowed) {
-            mWinAnimator.destroyPreservedSurfaceLocked(mTmpTransaction);
-            mTmpTransaction.apply();
-        }
-
         if (mDestroying) {
             ProtoLog.e(WM_DEBUG_ADD_REMOVE, "win=%s"
                     + " destroySurfaces: appStopped=%b"
@@ -3628,7 +3672,7 @@
                     displayId);
 
             if (mWmService.mAccessibilityController != null) {
-                mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(displayId);
+                mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
             }
             updateLocationInParentDisplayIfNeeded();
         } catch (RemoteException e) {
@@ -3760,16 +3804,20 @@
         return getDisplayContent().mCurrentFocus == this;
     }
 
-
     /** Is this window in a container that takes up the entire screen space? */
     private boolean inAppWindowThatMatchesParentBounds() {
         return mActivityRecord == null || (mActivityRecord.matchParentBounds() && !inMultiWindowMode());
     }
 
-    /** @return true when the window is in fullscreen mode, but has non-fullscreen bounds set, or
-     *          is transitioning into/out-of fullscreen. */
+    /** @return true when the window should be letterboxed. */
     boolean isLetterboxedAppWindow() {
-        return !inMultiWindowMode() && !matchesRootDisplayAreaBounds()
+        // Fullscreen mode but doesn't fill display area.
+        return (!inMultiWindowMode() && !matchesDisplayAreaBounds())
+                // Activity in size compat.
+                || (mActivityRecord != null && mActivityRecord.inSizeCompatMode())
+                // Task letterboxed.
+                || (getTask() != null && getTask().isTaskLetterboxed())
+                // Letterboxed for display cutout.
                 || isLetterboxedForDisplayCutout();
     }
 
@@ -3807,11 +3855,11 @@
     }
 
     /**
-     * @see Letterbox#notIntersectsOrFullyContains(Rect)
+     * @return {@code true} if bar shown within a given frame is allowed to be fully transparent
+     *     when the current window is displayed.
      */
-    boolean letterboxNotIntersectsOrFullyContains(Rect rect) {
-        return mActivityRecord == null
-                || mActivityRecord.letterboxNotIntersectsOrFullyContains(rect);
+    boolean isFullyTransparentBarAllowed(Rect frame) {
+        return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
     }
 
     public boolean isLetterboxedOverlappingWith(Rect rect) {
@@ -4727,7 +4775,7 @@
             return;
         }
         if (mWmService.mAccessibilityController != null) {
-            mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(getDisplayId());
+            mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
         }
 
         if (!isSelfOrAncestorWindowAnimatingExit()) {
@@ -4946,23 +4994,8 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
-        // When we change the Surface size, in scenarios which may require changing
-        // the surface position in sync with the resize, we use a preserved surface
-        // so we can freeze it while waiting for the client to report draw on the newly
-        // sized surface. At the moment this logic is only in place for switching
-        // in and out of the big surface for split screen resize.
         if (isDragResizeChanged()) {
             setDragResizing();
-            // We can only change top level windows to the full-screen surface when
-            // resizing (as we only have one full-screen surface). So there is no need
-            // to preserve and destroy windows which are attached to another, they
-            // will keep their surface and its size may change over time.
-            if (mHasSurface && !isChildWindow()) {
-                mWinAnimator.preserveSurfaceLocked(getSyncTransaction());
-                result |= RELAYOUT_RES_SURFACE_CHANGED |
-                    RELAYOUT_RES_FIRST_TIME;
-                scheduleAnimation();
-            }
         }
         final boolean freeformResizing = isDragResizing()
                 && getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
@@ -5215,7 +5248,7 @@
         if (!mAnimatingExit && mAppDied) {
             mIsDimming = true;
             getDimmer().dimAbove(getSyncTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
-        } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || isBlurEnabled())
+        } else if (((mAttrs.flags & FLAG_DIM_BEHIND) != 0 || (mAttrs.flags & FLAG_BLUR_BEHIND) != 0)
                    && isVisibleNow() && !mHidden) {
             // Only show the Dimmer when the following is satisfied:
             // 1. The window has the flag FLAG_DIM_BEHIND or background blur is requested
@@ -5224,16 +5257,13 @@
             // 4. The WS is not hidden.
             mIsDimming = true;
             final float dimAmount = (mAttrs.flags & FLAG_DIM_BEHIND) != 0 ? mAttrs.dimAmount : 0;
-            final int blurRadius = isBlurEnabled() ? mAttrs.backgroundBlurRadius : 0;
-            getDimmer().dimBelow(getSyncTransaction(), this, dimAmount, blurRadius);
+            final int blurRadius =
+                    (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 ? mAttrs.blurBehindRadius : 0;
+            getDimmer().dimBelow(
+                    getSyncTransaction(), this, mAttrs.dimAmount, mAttrs.blurBehindRadius);
         }
     }
 
-    private boolean isBlurEnabled() {
-        return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 && mOwnerCanUseBackgroundBlur;
-    }
-
-
     /**
      * Notifies SF about the priority of the window, if it changed. SF then uses this information
      * to decide which window's desired rendering rate should have a priority when deciding about
@@ -5242,13 +5272,21 @@
      */
     @VisibleForTesting
     void updateFrameRateSelectionPriorityIfNeeded() {
-        final int priority = getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
-                .calculatePriority(this);
+        RefreshRatePolicy refreshRatePolicy =
+                getDisplayContent().getDisplayPolicy().getRefreshRatePolicy();
+        final int priority = refreshRatePolicy.calculatePriority(this);
         if (mFrameRateSelectionPriority != priority) {
             mFrameRateSelectionPriority = priority;
             getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl,
                     mFrameRateSelectionPriority);
         }
+
+        final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
+        if (mDenyListFrameRate != refreshRate) {
+            mDenyListFrameRate = refreshRate;
+            getPendingTransaction().setFrameRate(
+                    mSurfaceControl, mDenyListFrameRate, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+        }
     }
 
     private void updateGlobalScaleIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index c8b940a..2da3dda 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -116,15 +116,7 @@
     boolean mAnimationIsEntrance;
 
     WindowSurfaceController mSurfaceController;
-    private WindowSurfaceController mPendingDestroySurface;
 
-    /**
-     * Set if the client has asked that the destroy of its surface be delayed
-     * until it explicitly says it is okay.
-     */
-    boolean mSurfaceDestroyDeferred;
-
-    private boolean mDestroyPreservedSurfaceUponRedraw;
     float mShownAlpha = 0;
     float mAlpha = 0;
     float mLastAlpha = 0;
@@ -257,11 +249,6 @@
             //dump();
             mLastHidden = true;
 
-            // We may have a preserved surface which we no longer need. If there was a quick
-            // VISIBLE, GONE, VISIBLE, GONE sequence, the surface may never draw, so we don't mark
-            // it to be destroyed in prepareSurfaceLocked.
-            markPreservedSurfaceForDestroy();
-
             if (mSurfaceController != null) {
                 mSurfaceController.hide(transaction, reason);
             }
@@ -323,70 +310,6 @@
         return result;
     }
 
-    void preserveSurfaceLocked(SurfaceControl.Transaction t) {
-        if (mDestroyPreservedSurfaceUponRedraw) {
-            // This could happen when switching the surface mode very fast. For example,
-            // we preserved a surface when dragResizing changed to true. Then before the
-            // preserved surface is removed, dragResizing changed to false again.
-            // In this case, we need to leave the preserved surface alone, and destroy
-            // the actual surface, so that the createSurface call could create a surface
-            // of the proper size. The preserved surface will still be removed when client
-            // finishes drawing to the new surface.
-            mSurfaceDestroyDeferred = false;
-
-            // Make sure to reparent any children of the new surface back to the preserved
-            // surface before destroying it.
-            if (mSurfaceController != null && mPendingDestroySurface != null) {
-                mPostDrawTransaction.reparentChildren(
-                    mSurfaceController.mSurfaceControl,
-                    mPendingDestroySurface.mSurfaceControl).apply();
-            }
-            destroySurfaceLocked(t);
-            mSurfaceDestroyDeferred = true;
-            return;
-        }
-        ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SET FREEZE LAYER: %s", mWin);
-        if (mSurfaceController != null) {
-            // Our SurfaceControl is always at layer 0 within the parent Surface managed by
-            // window-state. We want this old Surface to stay on top of the new one
-            // until we do the swap, so we place it at a positive layer.
-            t.setLayer(mSurfaceController.mSurfaceControl, PRESERVED_SURFACE_LAYER);
-        }
-        mDestroyPreservedSurfaceUponRedraw = true;
-        mSurfaceDestroyDeferred = true;
-        destroySurfaceLocked(t);
-    }
-
-    void destroyPreservedSurfaceLocked(SurfaceControl.Transaction t) {
-        if (!mDestroyPreservedSurfaceUponRedraw) {
-            return;
-        }
-
-        // If we are preserving a surface but we aren't relaunching that means
-        // we are just doing an in-place switch. In that case any SurfaceFlinger side
-        // child layers need to be reparented to the new surface to make this
-        // transparent to the app.
-        // If the children are detached, we don't want to reparent them to the new surface.
-        // Instead let the children get removed when the old surface is deleted.
-        if (mSurfaceController != null && mPendingDestroySurface != null
-                && !mPendingDestroySurface.mChildrenDetached
-                && (mWin.mActivityRecord == null || !mWin.mActivityRecord.isRelaunching())) {
-            mPostDrawTransaction.reparentChildren(
-                    mPendingDestroySurface.mSurfaceControl,
-                    mSurfaceController.mSurfaceControl).apply();
-        }
-
-        destroyDeferredSurfaceLocked(t);
-        mDestroyPreservedSurfaceUponRedraw = false;
-    }
-
-    private void markPreservedSurfaceForDestroy() {
-        if (mDestroyPreservedSurfaceUponRedraw
-                && !mService.mDestroyPreservedSurface.contains(mWin)) {
-            mService.mDestroyPreservedSurface.add(mWin);
-        }
-    }
-
     void resetDrawState() {
         mDrawState = DRAW_PENDING;
 
@@ -508,39 +431,23 @@
             return;
         }
 
-        // When destroying a surface we want to make sure child windows are hidden. If we are
-        // preserving the surface until redraw though we intend to swap it out with another surface
-        // for resizing. In this case the window always remains visible to the user and the child
-        // windows should likewise remain visible.
-        if (!mDestroyPreservedSurfaceUponRedraw) {
-            mWin.mHidden = true;
-        }
+        mWin.mHidden = true;
 
         try {
-            if (DEBUG_VISIBILITY) logWithStack(TAG, "Window " + this + " destroying surface "
-                    + mSurfaceController + ", session " + mSession);
-            if (mSurfaceDestroyDeferred) {
-                if (mSurfaceController != null && mPendingDestroySurface != mSurfaceController) {
-                    if (mPendingDestroySurface != null) {
-                        ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY PENDING: %s. %s",
-                                mWin, new RuntimeException().fillInStackTrace());
-                        mPendingDestroySurface.destroy(t);
-                    }
-                    mPendingDestroySurface = mSurfaceController;
-                }
-            } else {
-                ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s",
-                        mWin, new RuntimeException().fillInStackTrace());
-                destroySurface(t);
+            if (DEBUG_VISIBILITY) {
+                logWithStack(TAG, "Window " + this + " destroying surface "
+                        + mSurfaceController + ", session " + mSession);
             }
+            ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s",
+                    mWin, new RuntimeException().fillInStackTrace());
+            destroySurface(t);
             // Don't hide wallpaper if we're deferring the surface destroy
             // because of a surface change.
-            if (!mDestroyPreservedSurfaceUponRedraw) {
-                mWallpaperControllerLocked.hideWallpapers(mWin);
-            }
+            mWallpaperControllerLocked.hideWallpapers(mWin);
         } catch (RuntimeException e) {
             Slog.w(TAG, "Exception thrown when destroying Window " + this
-                + " surface " + mSurfaceController + " session " + mSession + ": " + e.toString());
+                    + " surface " + mSurfaceController + " session " + mSession + ": "
+                    + e.toString());
         }
 
         // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it
@@ -554,27 +461,6 @@
         mDrawState = NO_SURFACE;
     }
 
-    void destroyDeferredSurfaceLocked(SurfaceControl.Transaction t) {
-        try {
-            if (mPendingDestroySurface != null) {
-                ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY PENDING: %s. %s",
-                        mWin, new RuntimeException().fillInStackTrace());
-                mPendingDestroySurface.destroy(t);
-                // Don't hide wallpaper if we're destroying a deferred surface
-                // after a surface mode change.
-                if (!mDestroyPreservedSurfaceUponRedraw) {
-                    mWallpaperControllerLocked.hideWallpapers(mWin);
-                }
-            }
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Exception thrown when destroying Window "
-                    + this + " surface " + mPendingDestroySurface
-                    + " session " + mSession + ": " + e.toString());
-        }
-        mSurfaceDestroyDeferred = false;
-        mPendingDestroySurface = null;
-    }
-
     void computeShownFrameLocked() {
         if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
             return;
@@ -744,7 +630,6 @@
             if (prepared && mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
                     if (showSurfaceRobustlyLocked(t)) {
-                        markPreservedSurfaceForDestroy();
                         mAnimator.requestRemovalOfReplacedWindows(w);
                         mLastHidden = false;
                         if (mIsWallpaper) {
@@ -905,20 +790,6 @@
         if (!shown)
             return false;
 
-        // If we had a preserved surface it's no longer needed, and it may be harmful
-        // if we are transparent.
-        if (mPendingDestroySurface != null && mDestroyPreservedSurfaceUponRedraw) {
-            final SurfaceControl pendingSurfaceControl = mPendingDestroySurface.mSurfaceControl;
-            mPostDrawTransaction.reparent(pendingSurfaceControl, null);
-            // If the children are detached, we don't want to reparent them to the new surface.
-            // Instead let the children get removed when the old surface is deleted.
-            if (!mPendingDestroySurface.mChildrenDetached) {
-                mPostDrawTransaction.reparentChildren(
-                        mPendingDestroySurface.mSurfaceControl,
-                        mSurfaceController.mSurfaceControl);
-            }
-        }
-
         t.merge(mPostDrawTransaction);
         return true;
     }
@@ -946,7 +817,7 @@
         }
 
         if (mService.mAccessibilityController != null) {
-            mService.mAccessibilityController.onWindowTransitionLocked(mWin, transit);
+            mService.mAccessibilityController.onWindowTransition(mWin, transit);
         }
     }
 
@@ -1058,13 +929,6 @@
             pw.println();
         }
 
-        if (mPendingDestroySurface != null) {
-            pw.print(prefix); pw.print("mPendingDestroySurface=");
-                    pw.println(mPendingDestroySurface);
-        }
-        if (mSurfaceDestroyDeferred) {
-                    pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred);
-        }
         if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) {
             pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha);
                     pw.print(" mAlpha="); pw.print(mAlpha);
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 5f4477d..82ba3c1 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -55,8 +55,6 @@
     private boolean mSurfaceShown = false;
     private float mSurfaceX = 0;
     private float mSurfaceY = 0;
-    private int mSurfaceW = 0;
-    private int mSurfaceH = 0;
 
     // Initialize to the identity matrix.
     private float mLastDsdx = 1;
@@ -82,9 +80,6 @@
             int flags, WindowStateAnimator animator, int windowType) {
         mAnimator = animator;
 
-        mSurfaceW = w;
-        mSurfaceH = h;
-
         title = name;
 
         mService = animator.mService;
@@ -104,8 +99,8 @@
                 .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
                 .setCallsite("WindowSurfaceController");
 
-        final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags &
-                WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0);
+        final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags
+                & WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0);
 
         if (useBLAST) {
             b.setBLASTLayer();
@@ -119,7 +114,6 @@
     void hide(SurfaceControl.Transaction transaction, String reason) {
         ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
 
-        mAnimator.destroyPreservedSurfaceLocked(transaction);
         if (mSurfaceShown) {
             hideSurface(transaction);
         }
@@ -335,9 +329,7 @@
         pw.print(" layer="); pw.print(mSurfaceLayer);
         pw.print(" alpha="); pw.print(mSurfaceAlpha);
         pw.print(" rect=("); pw.print(mSurfaceX);
-        pw.print(","); pw.print(mSurfaceY);
-        pw.print(") "); pw.print(mSurfaceW);
-        pw.print(" x "); pw.print(mSurfaceH);
+        pw.print(","); pw.print(mSurfaceY); pw.print(") ");
         pw.print(" transform=("); pw.print(mLastDsdx); pw.print(", ");
         pw.print(mLastDtdx); pw.print(", "); pw.print(mLastDsdy);
         pw.print(", "); pw.print(mLastDtdy); pw.println(")");
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 8a512bc..c3a4609 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.os.Process.INVALID_UID;
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -26,7 +24,6 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
-import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -41,7 +38,6 @@
 
 import android.annotation.CallSuper;
 import android.annotation.Nullable;
-import android.app.IWindowToken;
 import android.app.servertransaction.FixedRotationAdjustmentsItem;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -58,7 +54,6 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.policy.WindowManagerPolicy;
 
@@ -112,19 +107,10 @@
 
     private FixedRotationTransformState mFixedRotationTransformState;
 
-    private Configuration mLastReportedConfig;
-    private int mLastReportedDisplay = INVALID_DISPLAY;
-
     /**
      * When set to {@code true}, this window token is created from {@link android.app.WindowContext}
      */
-    @VisibleForTesting
-    final boolean mFromClientToken;
-
-    private DeathRecipient mDeathRecipient;
-    private boolean mBinderDied = false;
-
-    private final int mOwnerUid;
+    private final boolean mFromClientToken;
 
     /**
      * Used to fix the transform of the token to be rotated to a rotation different than it's
@@ -188,30 +174,6 @@
         }
     }
 
-    private class DeathRecipient implements IBinder.DeathRecipient {
-        private boolean mHasUnlinkToDeath = false;
-
-        @Override
-        public void binderDied() {
-            synchronized (mWmService.mGlobalLock) {
-                mBinderDied = true;
-                removeImmediately();
-            }
-        }
-
-        void linkToDeath() throws RemoteException {
-            token.linkToDeath(DeathRecipient.this, 0);
-        }
-
-        void unlinkToDeath() {
-            if (mHasUnlinkToDeath) {
-                return;
-            }
-            token.unlinkToDeath(DeathRecipient.this, 0);
-            mHasUnlinkToDeath = true;
-        }
-    }
-
     /**
      * Compares two child window of this token and returns -1 if the first is lesser than the
      * second in terms of z-order and 1 otherwise.
@@ -240,43 +202,24 @@
 
     WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
             DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
-        this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens, INVALID_UID,
-                roundedCornerOverlay, false /* fromClientToken */);
+        this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens,
+                roundedCornerOverlay, false /* fromClientToken */, null /* options */);
     }
 
     WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
-            DisplayContent dc, boolean ownerCanManageAppTokens, int ownerUid,
-            boolean roundedCornerOverlay, boolean fromClientToken) {
-        this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens, ownerUid,
-                roundedCornerOverlay, fromClientToken, null /* options */);
-    }
-
-    WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
-            DisplayContent dc, boolean ownerCanManageAppTokens, int ownerUid,
-            boolean roundedCornerOverlay, boolean fromClientToken, @Nullable Bundle options) {
+            DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay,
+            boolean fromClientToken, @Nullable Bundle options) {
         super(service);
         token = _token;
         windowType = type;
         mOptions = options;
         mPersistOnEmpty = persistOnEmpty;
         mOwnerCanManageAppTokens = ownerCanManageAppTokens;
-        mOwnerUid = ownerUid;
         mRoundedCornerOverlay = roundedCornerOverlay;
         mFromClientToken = fromClientToken;
         if (dc != null) {
             dc.addWindowToken(token, this);
         }
-        if (shouldReportToClient()) {
-            try {
-                mDeathRecipient = new DeathRecipient();
-                mDeathRecipient.linkToDeath();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to add window token with type " + windowType + " on "
-                        + "display " + dc.getDisplayId(), e);
-                mDeathRecipient = null;
-                return;
-            }
-        }
     }
 
     void removeAllWindowsIfPossible() {
@@ -414,22 +357,6 @@
         // Needs to occur after the token is removed from the display above to avoid attempt at
         // duplicate removal of this window container from it's parent.
         super.removeImmediately();
-
-        reportWindowTokenRemovedToClient();
-    }
-
-    // TODO(b/159767464): Remove after we migrate to listener approach.
-    private void reportWindowTokenRemovedToClient() {
-        if (!shouldReportToClient()) {
-            return;
-        }
-        mDeathRecipient.unlinkToDeath();
-        IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token);
-        try {
-            windowTokenClient.onWindowTokenRemoved();
-        } catch (RemoteException e) {
-            ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client.");
-        }
     }
 
     @Override
@@ -441,51 +368,11 @@
         // to another display before the window behind
         // it is ready.
         super.onDisplayChanged(dc);
-        reportConfigToWindowTokenClient();
     }
 
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         super.onConfigurationChanged(newParentConfig);
-        reportConfigToWindowTokenClient();
-    }
-
-    void reportConfigToWindowTokenClient() {
-        if (!shouldReportToClient()) {
-            return;
-        }
-        if (mLastReportedConfig == null) {
-            mLastReportedConfig = new Configuration();
-        }
-        final Configuration config = getConfiguration();
-        final int displayId = getDisplayContent().getDisplayId();
-        if (config.diff(mLastReportedConfig) == 0 && displayId == mLastReportedDisplay) {
-            // No changes since last reported time.
-            return;
-        }
-
-        mLastReportedConfig.setTo(config);
-        mLastReportedDisplay = displayId;
-
-        IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token);
-        try {
-            windowTokenClient.onConfigurationChanged(config, displayId);
-        } catch (RemoteException e) {
-            ProtoLog.w(WM_ERROR,
-                    "Could not report config changes to the window token client.");
-        }
-    }
-
-    /**
-     * @return {@code true} if this {@link WindowToken} is not an {@link ActivityRecord} and
-     * registered from client side.
-     */
-    private boolean shouldReportToClient() {
-        // Only report to client for WindowToken because Activities are updated through ATM
-        // callbacks.
-        return asActivityRecord() == null
-        // Report to {@link android.view.WindowTokenClient} if this token was registered from it.
-                && mFromClientToken && !mBinderDied;
     }
 
     @Override
@@ -841,10 +728,6 @@
                 mRoundedCornerOverlay);
     }
 
-    int getOwnerUid() {
-        return mOwnerUid;
-    }
-
     boolean isFromClient() {
         return mFromClientToken;
     }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 9d013c1..91be056 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -25,8 +25,6 @@
         "gnss/GnssMeasurement.cpp",
         "gnss/GnssMeasurementCallback.cpp",
         "gnss/Utils.cpp",
-        "stats/PowerStatsPuller.cpp",
-        "stats/SubsystemSleepStatePuller.cpp",
         "com_android_server_adb_AdbDebuggingManager.cpp",
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
@@ -45,7 +43,6 @@
         "com_android_server_SerialService.cpp",
         "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
         "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
-        "com_android_server_stats_pull_StatsPullAtomService.cpp",
         "com_android_server_storage_AppFuseBridge.cpp",
         "com_android_server_SystemServer.cpp",
         "com_android_server_tv_TvUinputBridge.cpp",
@@ -70,6 +67,7 @@
         "frameworks/base/libs",
         "frameworks/native/services",
         "system/gatekeeper/include",
+        "system/memory/libmeminfo/include",
     ],
 
     header_libs: [
@@ -97,6 +95,7 @@
         "libhardware_legacy",
         "libhidlbase",
         "libkeystore_binder",
+        "libmeminfo",
         "libmtp",
         "libnativehelper",
         "libnativewindow",
@@ -135,7 +134,7 @@
         "android.hardware.broadcastradio@1.0",
         "android.hardware.broadcastradio@1.1",
         "android.hardware.contexthub@1.0",
-        "android.hardware.gnss-cpp",
+        "android.hardware.gnss-V1-cpp",
         "android.hardware.gnss@1.0",
         "android.hardware.gnss@1.1",
         "android.hardware.gnss@2.0",
@@ -148,11 +147,12 @@
         "android.hardware.light@2.0",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power-cpp",
+        "android.hardware.power-V1-cpp",
         "android.hardware.power.stats@1.0",
+        "android.hardware.power.stats-ndk_platform",
         "android.hardware.thermal@1.0",
         "android.hardware.tv.input@1.0",
-        "android.hardware.vibrator-unstable-cpp",
+        "android.hardware.vibrator-V2-cpp",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
@@ -162,7 +162,7 @@
         "android.frameworks.schedulerservice@1.0",
         "android.frameworks.sensorservice@1.0",
         "android.frameworks.stats@1.0",
-        "android.system.suspend.control-cpp",
+        "android.system.suspend.control-V1-cpp",
         "android.system.suspend.control.internal-cpp",
         "android.system.suspend@1.0",
         "service.incremental",
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 995cfe9..9a8942b 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -12,6 +12,9 @@
 per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com
 per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com
 
+# BatteryStats
+per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS
+
 per-file Android.bp = file:platform/build/soong:/OWNERS
 per-file com_android_server_Usb* = file:/services/usb/OWNERS
 per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index c3e7c7a..16eaa77 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -55,15 +55,8 @@
 using android::hardware::Return;
 using android::hardware::Void;
 using android::hardware::power::stats::V1_0::IPowerStats;
-using android::hardware::power::V1_0::PowerStatePlatformSleepState;
-using android::hardware::power::V1_0::PowerStateVoter;
-using android::hardware::power::V1_0::Status;
-using android::hardware::power::V1_1::PowerStateSubsystem;
-using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
 using android::system::suspend::BnSuspendCallback;
 using android::system::suspend::ISuspendControlService;
-using IPowerV1_1 = android::hardware::power::V1_1::IPower;
-using IPowerV1_0 = android::hardware::power::V1_0::IPower;
 
 namespace android
 {
@@ -74,23 +67,9 @@
 static sem_t wakeup_sem;
 extern sp<ISuspendControlService> getSuspendControl();
 
-// Java methods used in getLowPowerStats
-static jmethodID jgetAndUpdatePlatformState = NULL;
-static jmethodID jgetSubsystem = NULL;
-static jmethodID jputVoter = NULL;
-static jmethodID jputState = NULL;
-
 std::mutex gPowerStatsHalMutex;
-std::unordered_map<uint32_t, std::string> gPowerStatsHalEntityNames = {};
-std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>>
-    gPowerStatsHalStateNames = {};
-std::vector<uint32_t> gPowerStatsHalPlatformIds = {};
-std::vector<uint32_t> gPowerStatsHalSubsystemIds = {};
 sp<IPowerStats> gPowerStatsHalV1_0 = nullptr;
 
-std::function<void(JNIEnv*, jobject)> gGetLowPowerStatsImpl = {};
-std::function<jint(JNIEnv*, jobject)> gGetPlatformLowPowerStatsImpl = {};
-std::function<jint(JNIEnv*, jobject)> gGetSubsystemLowPowerStatsImpl = {};
 std::function<void(JNIEnv*, jobject)> gGetRailEnergyPowerStatsImpl = {};
 
 // Cellular/Wifi power monitor rail information
@@ -222,68 +201,14 @@
     return true;
 }
 
-static bool checkPowerHalResult(const Return<void>& ret, const char* function) {
-    if (!ret.isOk()) {
-        ALOGE("%s failed: requested HAL service not available.", function);
-        power::PowerHalLoader::unloadAll();
-        return false;
-    }
-    return true;
-}
-
 // gPowerStatsHalV1_0 must not be null
 static bool initializePowerStatsLocked() EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
     using android::hardware::power::stats::V1_0::Status;
-    using android::hardware::power::stats::V1_0::PowerEntityType;
 
     // Clear out previous content if we are re-initializing
-    gPowerStatsHalEntityNames.clear();
-    gPowerStatsHalStateNames.clear();
-    gPowerStatsHalPlatformIds.clear();
-    gPowerStatsHalSubsystemIds.clear();
     gPowerStatsHalRailNames.clear();
 
     Return<void> ret;
-    ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) {
-        if (status != Status::SUCCESS) {
-            ALOGE("Error getting power entity info");
-            return;
-        }
-
-        // construct lookup table of powerEntityId to power entity name
-        // also construct vector of platform and subsystem IDs
-        for (auto info : infos) {
-            gPowerStatsHalEntityNames.emplace(info.powerEntityId, info.powerEntityName);
-            if (info.type == PowerEntityType::POWER_DOMAIN) {
-                gPowerStatsHalPlatformIds.emplace_back(info.powerEntityId);
-            } else {
-                gPowerStatsHalSubsystemIds.emplace_back(info.powerEntityId);
-            }
-        }
-    });
-    if (!checkPowerStatsHalResultLocked(ret, __func__)) {
-        return false;
-    }
-
-    ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) {
-        if (status != Status::SUCCESS) {
-            ALOGE("Error getting state info");
-            return;
-        }
-
-        // construct lookup table of powerEntityId, powerEntityStateId to power entity state name
-        for (auto stateSpace : stateSpaces) {
-            std::unordered_map<uint32_t, std::string> stateNames = {};
-            for (auto state : stateSpace.states) {
-                stateNames.emplace(state.powerEntityStateId,
-                    state.powerEntityStateName);
-            }
-            gPowerStatsHalStateNames.emplace(stateSpace.powerEntityId, stateNames);
-        }
-    });
-    if (!checkPowerStatsHalResultLocked(ret, __func__)) {
-        return false;
-    }
 
     // Get Power monitor rails available
     ret = gPowerStatsHalV1_0->getRailInfo([](auto rails, auto status) {
@@ -306,7 +231,7 @@
         return false;
     }
 
-    return (!gPowerStatsHalEntityNames.empty()) && (!gPowerStatsHalStateNames.empty());
+    return true;
 }
 
 static bool getPowerStatsHalLocked() EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
@@ -333,196 +258,6 @@
     return true;
 }
 
-static void getPowerStatsHalLowPowerDataLocked(JNIEnv* env, jobject jrpmStats)
-        EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
-    using android::hardware::power::stats::V1_0::Status;
-
-    if (!getPowerStatsHalLocked()) {
-        ALOGE("failed to get low power stats");
-        return;
-    }
-
-    // Get power entity state residency data
-    bool success = false;
-    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData({},
-        [&env, &jrpmStats, &success](auto results, auto status) {
-            if (status == Status::NOT_SUPPORTED) {
-                ALOGW("getPowerEntityStateResidencyData is not supported");
-                success = false;
-                return;
-            }
-
-            for (auto result : results) {
-                jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem,
-                    env->NewStringUTF(gPowerStatsHalEntityNames.at(result.powerEntityId).c_str()));
-                if (jsubsystem == NULL) {
-                    ALOGE("The rpmstats jni jobject jsubsystem is null.");
-                    return;
-                }
-                for (auto stateResidency : result.stateResidencyData) {
-
-                    env->CallVoidMethod(jsubsystem, jputState,
-                        env->NewStringUTF(gPowerStatsHalStateNames.at(result.powerEntityId)
-                        .at(stateResidency.powerEntityStateId).c_str()),
-                        stateResidency.totalTimeInStateMs,
-                        stateResidency.totalStateEntryCount);
-                }
-            }
-            success = true;
-        });
-    checkPowerStatsHalResultLocked(ret, __func__);
-    if (!success) {
-        ALOGE("getPowerEntityStateResidencyData failed");
-    }
-}
-
-static jint getPowerStatsHalPlatformDataLocked(JNIEnv* env, jobject outBuf)
-        EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
-    using android::hardware::power::stats::V1_0::Status;
-    using hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
-    using hardware::power::stats::V1_0::PowerEntityStateResidencyData;
-
-    if (!getPowerStatsHalLocked()) {
-        ALOGE("failed to get low power stats");
-        return -1;
-    }
-
-    char *output = (char*)env->GetDirectBufferAddress(outBuf);
-    char *offset = output;
-    int remaining = (int)env->GetDirectBufferCapacity(outBuf);
-    int total_added = -1;
-
-    // Get power entity state residency data
-    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
-        gPowerStatsHalPlatformIds,
-        [&offset, &remaining, &total_added](auto results, auto status) {
-            if (status == Status::NOT_SUPPORTED) {
-                ALOGW("getPowerEntityStateResidencyData is not supported");
-                return;
-            }
-
-            for (size_t i = 0; i < results.size(); i++) {
-                const PowerEntityStateResidencyResult& result = results[i];
-
-                for (size_t j = 0; j < result.stateResidencyData.size(); j++) {
-                    const PowerEntityStateResidencyData& stateResidency =
-                        result.stateResidencyData[j];
-                    int added = snprintf(offset, remaining,
-                        "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
-                        j + 1, gPowerStatsHalStateNames.at(result.powerEntityId)
-                           .at(stateResidency.powerEntityStateId).c_str(),
-                        stateResidency.totalTimeInStateMs,
-                        stateResidency.totalStateEntryCount);
-                    if (added < 0) {
-                        break;
-                    }
-                    if (added > remaining) {
-                        added = remaining;
-                    }
-                    offset += added;
-                    remaining -= added;
-                    total_added += added;
-                }
-                if (remaining <= 0) {
-                    /* rewrite NULL character*/
-                    offset--;
-                    total_added--;
-                    ALOGE("power.stats Hal: buffer not enough");
-                    break;
-                }
-            }
-        });
-    if (!checkPowerStatsHalResultLocked(ret, __func__)) {
-        return -1;
-    }
-
-    total_added += 1;
-    return total_added;
-}
-
-static jint getPowerStatsHalSubsystemDataLocked(JNIEnv* env, jobject outBuf)
-        EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
-    using android::hardware::power::stats::V1_0::Status;
-    using hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
-    using hardware::power::stats::V1_0::PowerEntityStateResidencyData;
-
-    if (!getPowerStatsHalLocked()) {
-        ALOGE("failed to get low power stats");
-        return -1;
-    }
-
-    char *output = (char*)env->GetDirectBufferAddress(outBuf);
-    char *offset = output;
-    int remaining = (int)env->GetDirectBufferCapacity(outBuf);
-    int total_added = -1;
-
-    // Get power entity state residency data
-    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
-        gPowerStatsHalSubsystemIds,
-        [&offset, &remaining, &total_added](auto results, auto status) {
-            if (status == Status::NOT_SUPPORTED) {
-                ALOGW("getPowerEntityStateResidencyData is not supported");
-                return;
-            }
-
-            int added = snprintf(offset, remaining, "SubsystemPowerState ");
-            offset += added;
-            remaining -= added;
-            total_added += added;
-
-            for (size_t i = 0; i < results.size(); i++) {
-                const PowerEntityStateResidencyResult& result = results[i];
-                added = snprintf(offset, remaining, "subsystem_%zu name=%s ",
-                        i + 1, gPowerStatsHalEntityNames.at(result.powerEntityId).c_str());
-                if (added < 0) {
-                    break;
-                }
-
-                if (added > remaining) {
-                    added = remaining;
-                }
-
-                offset += added;
-                remaining -= added;
-                total_added += added;
-
-                for (size_t j = 0; j < result.stateResidencyData.size(); j++) {
-                    const PowerEntityStateResidencyData& stateResidency =
-                        result.stateResidencyData[j];
-                    added = snprintf(offset, remaining,
-                        "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%"
-                        PRIu64 " ", j + 1, gPowerStatsHalStateNames.at(result.powerEntityId)
-                           .at(stateResidency.powerEntityStateId).c_str(),
-                        stateResidency.totalTimeInStateMs,
-                        stateResidency.totalStateEntryCount,
-                        stateResidency.lastEntryTimestampMs);
-                    if (added < 0) {
-                        break;
-                    }
-                    if (added > remaining) {
-                        added = remaining;
-                    }
-                    offset += added;
-                    remaining -= added;
-                    total_added += added;
-                }
-                if (remaining <= 0) {
-                    /* rewrite NULL character*/
-                    offset--;
-                    total_added--;
-                    ALOGE("power.stats Hal: buffer not enough");
-                    break;
-                }
-            }
-        });
-    if (!checkPowerStatsHalResultLocked(ret, __func__)) {
-        return -1;
-    }
-
-    total_added += 1;
-    return total_added;
-}
-
 static void getPowerStatsHalRailEnergyDataLocked(JNIEnv* env, jobject jrailStats)
         EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
     using android::hardware::power::stats::V1_0::Status;
@@ -568,325 +303,17 @@
     }
 }
 
-static void getPowerHalLowPowerData(JNIEnv* env, jobject jrpmStats) {
-    sp<IPowerV1_0> powerHalV1_0 = power::PowerHalLoader::loadHidlV1_0();
-    if (powerHalV1_0 == nullptr) {
-        ALOGE("Power Hal not loaded");
-        return;
-    }
-
-    Return<void> ret = powerHalV1_0->getPlatformLowPowerStats(
-            [&env, &jrpmStats](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
-
-            if (status != Status::SUCCESS) return;
-
-            for (size_t i = 0; i < states.size(); i++) {
-                const PowerStatePlatformSleepState& state = states[i];
-
-                jobject jplatformState = env->CallObjectMethod(jrpmStats,
-                        jgetAndUpdatePlatformState,
-                        env->NewStringUTF(state.name.c_str()),
-                        state.residencyInMsecSinceBoot,
-                        state.totalTransitions);
-                if (jplatformState == NULL) {
-                    ALOGE("The rpmstats jni jobject jplatformState is null.");
-                    return;
-                }
-
-                for (size_t j = 0; j < state.voters.size(); j++) {
-                    const PowerStateVoter& voter = state.voters[j];
-                    env->CallVoidMethod(jplatformState, jputVoter,
-                            env->NewStringUTF(voter.name.c_str()),
-                            voter.totalTimeInMsecVotedForSinceBoot,
-                            voter.totalNumberOfTimesVotedSinceBoot);
-                }
-            }
-    });
-    if (!checkPowerHalResult(ret, "getPlatformLowPowerStats")) {
-        return;
-    }
-
-    // Trying to get IPower 1.1, this will succeed only for devices supporting 1.1
-    sp<IPowerV1_1> powerHal_1_1 = power::PowerHalLoader::loadHidlV1_1();
-    if (powerHal_1_1 == nullptr) {
-        // This device does not support IPower@1.1, exiting gracefully
-        return;
-    }
-    ret = powerHal_1_1->getSubsystemLowPowerStats(
-            [&env, &jrpmStats](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
-
-        if (status != Status::SUCCESS) return;
-
-        if (subsystems.size() > 0) {
-            for (size_t i = 0; i < subsystems.size(); i++) {
-                const PowerStateSubsystem &subsystem = subsystems[i];
-
-                jobject jsubsystem = env->CallObjectMethod(jrpmStats, jgetSubsystem,
-                        env->NewStringUTF(subsystem.name.c_str()));
-                if (jsubsystem == NULL) {
-                    ALOGE("The rpmstats jni jobject jsubsystem is null.");
-                    return;
-                }
-
-                for (size_t j = 0; j < subsystem.states.size(); j++) {
-                    const PowerStateSubsystemSleepState& state = subsystem.states[j];
-                    env->CallVoidMethod(jsubsystem, jputState,
-                            env->NewStringUTF(state.name.c_str()),
-                            state.residencyInMsecSinceBoot,
-                            state.totalTransitions);
-                }
-            }
-        }
-    });
-    checkPowerHalResult(ret, "getSubsystemLowPowerStats");
-}
-
-static jint getPowerHalPlatformData(JNIEnv* env, jobject outBuf) {
-    char *output = (char*)env->GetDirectBufferAddress(outBuf);
-    char *offset = output;
-    int remaining = (int)env->GetDirectBufferCapacity(outBuf);
-    int total_added = -1;
-
-    {
-        sp<IPowerV1_0> powerHalV1_0 = power::PowerHalLoader::loadHidlV1_0();
-        if (powerHalV1_0 == nullptr) {
-            ALOGE("Power Hal not loaded");
-            return -1;
-        }
-
-        Return<void> ret = powerHalV1_0->getPlatformLowPowerStats(
-            [&offset, &remaining, &total_added](hidl_vec<PowerStatePlatformSleepState> states,
-                    Status status) {
-                if (status != Status::SUCCESS)
-                    return;
-                for (size_t i = 0; i < states.size(); i++) {
-                    int added;
-                    const PowerStatePlatformSleepState& state = states[i];
-
-                    added = snprintf(offset, remaining,
-                        "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
-                        i + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
-                        state.totalTransitions);
-                    if (added < 0) {
-                        break;
-                    }
-                    if (added > remaining) {
-                        added = remaining;
-                    }
-                    offset += added;
-                    remaining -= added;
-                    total_added += added;
-
-                    for (size_t j = 0; j < state.voters.size(); j++) {
-                        const PowerStateVoter& voter = state.voters[j];
-                        added = snprintf(offset, remaining,
-                                "voter_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " ",
-                                j + 1, voter.name.c_str(),
-                                voter.totalTimeInMsecVotedForSinceBoot,
-                                voter.totalNumberOfTimesVotedSinceBoot);
-                        if (added < 0) {
-                            break;
-                        }
-                        if (added > remaining) {
-                            added = remaining;
-                        }
-                        offset += added;
-                        remaining -= added;
-                        total_added += added;
-                    }
-
-                    if (remaining <= 0) {
-                        /* rewrite NULL character*/
-                        offset--;
-                        total_added--;
-                        ALOGE("PowerHal: buffer not enough");
-                        break;
-                    }
-                }
-            }
-        );
-
-        if (!checkPowerHalResult(ret, "getPlatformLowPowerStats")) {
-            return -1;
-        }
-    }
-    *offset = 0;
-    total_added += 1;
-    return total_added;
-}
-
-static jint getPowerHalSubsystemData(JNIEnv* env, jobject outBuf) {
-    char *output = (char*)env->GetDirectBufferAddress(outBuf);
-    char *offset = output;
-    int remaining = (int)env->GetDirectBufferCapacity(outBuf);
-    int total_added = -1;
-
-    // This is a IPower 1.1 API
-    sp<IPowerV1_1> powerHal_1_1 = nullptr;
-
-    {
-        // Trying to get 1.1, this will succeed only for devices supporting 1.1
-        powerHal_1_1 = power::PowerHalLoader::loadHidlV1_1();
-        if (powerHal_1_1 == nullptr) {
-            //This device does not support IPower@1.1, exiting gracefully
-            return 0;
-        }
-
-        Return<void> ret = powerHal_1_1->getSubsystemLowPowerStats(
-           [&offset, &remaining, &total_added](hidl_vec<PowerStateSubsystem> subsystems,
-                Status status) {
-
-            if (status != Status::SUCCESS)
-                return;
-
-            if (subsystems.size() > 0) {
-                int added = snprintf(offset, remaining, "SubsystemPowerState ");
-                offset += added;
-                remaining -= added;
-                total_added += added;
-
-                for (size_t i = 0; i < subsystems.size(); i++) {
-                    const PowerStateSubsystem &subsystem = subsystems[i];
-
-                    added = snprintf(offset, remaining,
-                                     "subsystem_%zu name=%s ", i + 1, subsystem.name.c_str());
-                    if (added < 0) {
-                        break;
-                    }
-
-                    if (added > remaining) {
-                        added = remaining;
-                    }
-
-                    offset += added;
-                    remaining -= added;
-                    total_added += added;
-
-                    for (size_t j = 0; j < subsystem.states.size(); j++) {
-                        const PowerStateSubsystemSleepState& state = subsystem.states[j];
-                        added = snprintf(offset, remaining,
-                                         "state_%zu name=%s time=%" PRIu64 " count=%" PRIu64 " last entry=%" PRIu64 " ",
-                                         j + 1, state.name.c_str(), state.residencyInMsecSinceBoot,
-                                         state.totalTransitions, state.lastEntryTimestampMs);
-                        if (added < 0) {
-                            break;
-                        }
-
-                        if (added > remaining) {
-                            added = remaining;
-                        }
-
-                        offset += added;
-                        remaining -= added;
-                        total_added += added;
-                    }
-
-                    if (remaining <= 0) {
-                        /* rewrite NULL character*/
-                        offset--;
-                        total_added--;
-                        ALOGE("PowerHal: buffer not enough");
-                        break;
-                    }
-                }
-            }
-        }
-        );
-
-        if (!checkPowerHalResult(ret, "getSubsystemLowPowerStats")) {
-            return -1;
-        }
-    }
-
-    *offset = 0;
-    total_added += 1;
-    return total_added;
-}
-
 static void setUpPowerStatsLocked() EXCLUSIVE_LOCKS_REQUIRED(gPowerStatsHalMutex) {
     // First see if power.stats HAL is available. Fall back to power HAL if
     // power.stats HAL is unavailable.
     if (IPowerStats::getService() != nullptr) {
         ALOGI("Using power.stats HAL");
-        gGetLowPowerStatsImpl = getPowerStatsHalLowPowerDataLocked;
-        gGetPlatformLowPowerStatsImpl = getPowerStatsHalPlatformDataLocked;
-        gGetSubsystemLowPowerStatsImpl = getPowerStatsHalSubsystemDataLocked;
         gGetRailEnergyPowerStatsImpl = getPowerStatsHalRailEnergyDataLocked;
-    } else if (IPowerV1_0::getService() != nullptr) {
-        ALOGI("Using power HAL");
-        gGetLowPowerStatsImpl = getPowerHalLowPowerData;
-        gGetPlatformLowPowerStatsImpl = getPowerHalPlatformData;
-        gGetSubsystemLowPowerStatsImpl = getPowerHalSubsystemData;
+    } else {
         gGetRailEnergyPowerStatsImpl = NULL;
     }
 }
 
-static void getLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrpmStats) {
-    if (jrpmStats == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException",
-                "The rpmstats jni input jobject jrpmStats is null.");
-        return;
-    }
-    if (jgetAndUpdatePlatformState == NULL || jgetSubsystem == NULL
-            || jputVoter == NULL || jputState == NULL) {
-        ALOGE("A rpmstats jni jmethodID is null.");
-        return;
-    }
-
-    std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-
-    if (!gGetLowPowerStatsImpl) {
-        setUpPowerStatsLocked();
-    }
-
-    if (gGetLowPowerStatsImpl) {
-        return gGetLowPowerStatsImpl(env, jrpmStats);
-    }
-
-    ALOGE("Unable to load Power Hal or power.stats HAL");
-    return;
-}
-
-static jint getPlatformLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
-    if (outBuf == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", "null argument");
-        return -1;
-    }
-
-    std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-
-    if (!gGetPlatformLowPowerStatsImpl) {
-        setUpPowerStatsLocked();
-    }
-
-    if (gGetPlatformLowPowerStatsImpl) {
-        return gGetPlatformLowPowerStatsImpl(env, outBuf);
-    }
-
-    ALOGE("Unable to load Power Hal or power.stats HAL");
-    return -1;
-}
-
-static jint getSubsystemLowPowerStats(JNIEnv* env, jobject /* clazz */, jobject outBuf) {
-    if (outBuf == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", "null argument");
-        return -1;
-    }
-
-    std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-
-    if (!gGetSubsystemLowPowerStatsImpl) {
-        setUpPowerStatsLocked();
-    }
-
-    if (gGetSubsystemLowPowerStatsImpl) {
-        return gGetSubsystemLowPowerStatsImpl(env, outBuf);
-    }
-
-    ALOGE("Unable to load Power Hal or power.stats HAL");
-    return -1;
-}
-
 static void getRailEnergyPowerStats(JNIEnv* env, jobject /* clazz */, jobject jrailStats) {
     if (jrailStats == NULL) {
         jniThrowException(env, "java/lang/NullPointerException",
@@ -920,9 +347,6 @@
 
 static const JNINativeMethod method_table[] = {
     { "nativeWaitWakeup", "(Ljava/nio/ByteBuffer;)I", (void*)nativeWaitWakeup },
-    { "getLowPowerStats", "(Lcom/android/internal/os/RpmStats;)V", (void*)getLowPowerStats },
-    { "getPlatformLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getPlatformLowPowerStats },
-    { "getSubsystemLowPowerStats", "(Ljava/nio/ByteBuffer;)I", (void*)getSubsystemLowPowerStats },
     { "getRailEnergyPowerStats", "(Lcom/android/internal/os/RailStats;)V",
         (void*)getRailEnergyPowerStats },
 };
@@ -930,24 +354,10 @@
 int register_android_server_BatteryStatsService(JNIEnv *env)
 {
     // get java classes and methods
-    jclass clsRpmStats = env->FindClass("com/android/internal/os/RpmStats");
-    jclass clsPowerStatePlatformSleepState =
-            env->FindClass("com/android/internal/os/RpmStats$PowerStatePlatformSleepState");
-    jclass clsPowerStateSubsystem =
-            env->FindClass("com/android/internal/os/RpmStats$PowerStateSubsystem");
     jclass clsRailStats = env->FindClass("com/android/internal/os/RailStats");
-    if (clsRpmStats == NULL || clsPowerStatePlatformSleepState == NULL
-            || clsPowerStateSubsystem == NULL || clsRailStats == NULL) {
+    if (clsRailStats == NULL) {
         ALOGE("A rpmstats jni jclass is null.");
     } else {
-        jgetAndUpdatePlatformState = env->GetMethodID(clsRpmStats, "getAndUpdatePlatformState",
-                "(Ljava/lang/String;JI)Lcom/android/internal/os/RpmStats$PowerStatePlatformSleepState;");
-        jgetSubsystem = env->GetMethodID(clsRpmStats, "getSubsystem",
-                "(Ljava/lang/String;)Lcom/android/internal/os/RpmStats$PowerStateSubsystem;");
-        jputVoter = env->GetMethodID(clsPowerStatePlatformSleepState, "putVoter",
-                "(Ljava/lang/String;JI)V");
-        jputState = env->GetMethodID(clsPowerStateSubsystem, "putState",
-                "(Ljava/lang/String;JI)V");
         jupdateRailData = env->GetMethodID(clsRailStats, "updateRailData",
                 "(JLjava/lang/String;Ljava/lang/String;JJ)V");
         jsetRailStatsAvailability = env->GetMethodID(clsRailStats, "setRailStatsAvailability",
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 678308a..31cc295 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -17,15 +17,25 @@
 #define LOG_TAG "CachedAppOptimizer"
 //#define LOG_NDEBUG 0
 
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <cutils/compiler.h>
 #include <dirent.h>
+#include <jni.h>
+#include <linux/errno.h>
+#include <log/log.h>
+#include <meminfo/procmeminfo.h>
+#include <nativehelper/JNIHelp.h>
 #include <stddef.h>
 #include <stdio.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <sys/types.h>
 #include <unistd.h>
 
-#include <android-base/stringprintf.h>
-#include <android-base/file.h>
+#include <algorithm>
 
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
@@ -35,12 +45,149 @@
 
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
+using android::meminfo::ProcMemInfo;
+using namespace android::meminfo;
+
+// This is temporarily hard-coded and should be removed once
+// bionic/libc/kernel/uapi/asm-generic/unistd.h are updated with process_madvise syscall header
+#ifndef __NR_process_madvise
+#define __NR_process_madvise 440
+#define MADV_COLD 20 /* deactivate these pages */
+#define MADV_PAGEOUT 21
+#endif
+
+#define COMPACT_ACTION_FILE_FLAG 1
+#define COMPACT_ACTION_ANON_FLAG 2
+
+using VmaToAdviseFunc = std::function<int(const Vma&)>;
 
 #define SYNC_RECEIVED_WHILE_FROZEN (1)
 #define ASYNC_RECEIVED_WHILE_FROZEN (2)
 
 namespace android {
 
+// Legacy method for compacting processes, any new code should
+// use compactProcess instead.
+static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
+    std::string reclaim_path = StringPrintf("/proc/%d/reclaim", pid);
+    WriteStringToFile(compactionType, reclaim_path);
+}
+
+static int compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) {
+    // UIO_MAXIOV is currently a small value and we might have more addresses
+    // we do multiple syscalls if we exceed its maximum
+    static struct iovec vmasToKernel[UIO_MAXIOV];
+
+    int err = 0;
+
+    if (vmas.empty()) {
+        return err;
+    }
+
+    int pidfd = syscall(__NR_pidfd_open, pid, 0);
+    err = -errno;
+    if (err < 0) {
+        // Skip compaction if failed to open pidfd with any error
+        return err;
+    }
+
+    for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) {
+        int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase));
+        for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) {
+            vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start;
+            vmasToKernel[iVec].iov_len = vmas[iVma].end - vmas[iVma].start;
+        }
+
+        process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0);
+        err = -errno;
+        if (CC_UNLIKELY(err == -ENOSYS)) {
+            // Syscall does not exist, skip trying more calls process_madvise
+            break;
+        }
+    }
+
+    close(pidfd);
+
+    return err;
+}
+
+static int getFilePageAdvice(const Vma& vma) {
+    if (vma.inode > 0 && !vma.is_shared) {
+        return MADV_COLD;
+    }
+    return -1;
+}
+static int getAnonPageAdvice(const Vma& vma) {
+    if (vma.inode == 0 && !vma.is_shared) {
+        return MADV_PAGEOUT;
+    }
+    return -1;
+}
+static int getAnyPageAdvice(const Vma& vma) {
+    if (vma.inode == 0 && !vma.is_shared) {
+        return MADV_PAGEOUT;
+    }
+    return MADV_COLD;
+}
+
+// Perform a full process compaction using process_madvise syscall
+// reading all filtering VMAs and filtering pages as specified by pageFilter
+static int compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) {
+    ProcMemInfo meminfo(pid);
+    std::vector<Vma> pageoutVmas, coldVmas;
+    auto vmaCollectorCb = [&](Vma vma) {
+        int advice = vmaToAdviseFunc(vma);
+        switch (advice) {
+            case MADV_COLD:
+                coldVmas.push_back(vma);
+                break;
+            case MADV_PAGEOUT:
+                pageoutVmas.push_back(vma);
+                break;
+        }
+    };
+    meminfo.ForEachVma(vmaCollectorCb);
+
+    int err = compactMemory(pageoutVmas, pid, MADV_PAGEOUT);
+    if (!err) {
+        err = compactMemory(coldVmas, pid, MADV_COLD);
+    }
+    return err;
+}
+
+// Compact process using process_madvise syscall or fallback to procfs in
+// case syscall does not exist.
+static void compactProcessOrFallback(int pid, int compactionFlags) {
+    if ((compactionFlags & (COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG)) == 0) return;
+
+    bool compactAnon = compactionFlags & COMPACT_ACTION_ANON_FLAG;
+    bool compactFile = compactionFlags & COMPACT_ACTION_FILE_FLAG;
+
+    // Set when the system does not support process_madvise syscall to avoid
+    // gathering VMAs in subsequent calls prior to falling back to procfs
+    static bool shouldForceProcFs = false;
+    std::string compactionType;
+    VmaToAdviseFunc vmaToAdviseFunc;
+
+    if (compactAnon) {
+        if (compactFile) {
+            compactionType = "all";
+            vmaToAdviseFunc = getAnyPageAdvice;
+        } else {
+            compactionType = "anon";
+            vmaToAdviseFunc = getAnonPageAdvice;
+        }
+    } else {
+        compactionType = "file";
+        vmaToAdviseFunc = getFilePageAdvice;
+    }
+
+    if (shouldForceProcFs || compactProcess(pid, vmaToAdviseFunc) == -ENOSYS) {
+        shouldForceProcFs = true;
+        compactProcessProcfs(pid, compactionType);
+    }
+}
+
 // This performs per-process reclaim on all processes belonging to non-app UIDs.
 // For the most part, these are non-zygote processes like Treble HALs, but it
 // also includes zygote-derived processes that run in system UIDs, like bluetooth
@@ -74,11 +221,17 @@
             continue;
         }
 
-        std::string reclaim_path = StringPrintf("/proc/%s/reclaim", current->d_name);
-        WriteStringToFile(std::string("all"), reclaim_path);
+        int pid = atoi(current->d_name);
+
+        compactProcessOrFallback(pid, COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG);
     }
 }
 
+static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid,
+                                                                    jint compactionFlags) {
+    compactProcessOrFallback(pid, compactionFlags);
+}
+
 static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal(
         JNIEnv *env, jobject clazz, jboolean enable) {
     bool success = true;
@@ -126,14 +279,14 @@
 }
 
 static const JNINativeMethod sMethods[] = {
-    /* name, signature, funcPtr */
-    {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
-    {"enableFreezerInternal", "(Z)V",
-        (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
-    {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
-    {"getBinderFreezeInfo", "(I)I",
-        (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}
-};
+        /* name, signature, funcPtr */
+        {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
+        {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
+        {"enableFreezerInternal", "(Z)V",
+         (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal},
+        {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+        {"getBinderFreezeInfo", "(I)I",
+         (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}};
 
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
 {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index dc15b07..5b587e9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -26,14 +26,14 @@
 // Log debug messages about InputDispatcherPolicy
 #define DEBUG_INPUT_DISPATCHER_POLICY 0
 
-
-#include <atomic>
-#include <cinttypes>
-#include <limits.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
+#include <android/os/IInputConstants.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
+#include <limits.h>
+#include <atomic>
+#include <cinttypes>
 
 #include <utils/Log.h>
 #include <utils/Looper.h>
@@ -46,6 +46,7 @@
 #include <input/SpriteController.h>
 #include <ui/Region.h>
 
+#include <batteryservice/include/batteryservice/BatteryServiceConstants.h>
 #include <inputflinger/InputManager.h>
 
 #include <android_os_MessageQueue.h>
@@ -1908,6 +1909,20 @@
     return vibIdArray;
 }
 
+static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    std::optional<int32_t> ret = im->getInputManager()->getReader()->getBatteryCapacity(deviceId);
+    return static_cast<jint>(ret.value_or(android::os::IInputConstants::INVALID_BATTERY_CAPACITY));
+}
+
+static jint nativeGetBatteryStatus(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    std::optional<int32_t> ret = im->getInputManager()->getReader()->getBatteryStatus(deviceId);
+    return static_cast<jint>(ret.value_or(BATTERY_STATUS_UNKNOWN));
+}
+
 static void nativeReloadKeyboardLayouts(JNIEnv* /* env */,
         jclass /* clazz */, jlong ptr) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -2163,6 +2178,8 @@
         {"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate},
         {"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating},
         {"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds},
+        {"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity},
+        {"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus},
         {"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts},
         {"nativeReloadDeviceAliases", "(J)V", (void*)nativeReloadDeviceAliases},
         {"nativeDump", "(J)Ljava/lang/String;", (void*)nativeDump},
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 7b379e5..5a5b0a8 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -22,6 +22,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 #include <core_jni_helpers.h>
+#include <cutils/multiuser.h>
 #include <cutils/trace.h>
 #include <endian.h>
 #include <nativehelper/JNIHelp.h>
@@ -375,6 +376,9 @@
     }
 
 private:
+    // Bitmask of supported features.
+    DataLoaderFeatures getFeatures() const final { return DATA_LOADER_FEATURE_UID; }
+
     // Lifecycle.
     bool onCreate(const android::dataloader::DataLoaderParams& params,
                   android::dataloader::FilesystemConnectorPtr ifs,
@@ -554,51 +558,6 @@
         return true;
     }
 
-    // Read tracing.
-    struct TracedRead {
-        uint64_t timestampUs;
-        android::dataloader::FileId fileId;
-        uint32_t firstBlockIdx;
-        uint32_t count;
-    };
-
-    void onPageReads(android::dataloader::PageReads pageReads) final {
-        auto trace = atrace_is_tag_enabled(ATRACE_TAG);
-        if (CC_LIKELY(!trace)) {
-            return;
-        }
-
-        TracedRead last = {};
-        for (auto&& read : pageReads) {
-            if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) {
-                traceRead(last);
-                last = TracedRead{
-                        .timestampUs = read.bootClockTsUs,
-                        .fileId = read.id,
-                        .firstBlockIdx = (uint32_t)read.block,
-                        .count = 1,
-                };
-            } else {
-                ++last.count;
-            }
-        }
-        traceRead(last);
-    }
-
-    void traceRead(const TracedRead& read) {
-        if (!read.count) {
-            return;
-        }
-
-        FileIdx fileIdx = convertFileIdToFileIndex(read.fileId);
-        auto str = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d",
-                                               static_cast<long long>(read.firstBlockIdx),
-                                               static_cast<long long>(read.count),
-                                               static_cast<int>(fileIdx));
-        ATRACE_BEGIN(str.c_str());
-        ATRACE_END();
-    }
-
     // Streaming.
     bool initStreaming(unique_fd inout, MetadataMode mode) {
         mEventFd.reset(eventfd(0, EFD_CLOEXEC));
@@ -634,7 +593,10 @@
     }
 
     // IFS callbacks.
-    void onPendingReads(dataloader::PendingReads pendingReads) final {
+    void onPendingReads(dataloader::PendingReads pendingReads) final {}
+    void onPageReads(dataloader::PageReads pageReads) final {}
+
+    void onPendingReadsWithUid(dataloader::PendingReadsWithUid pendingReads) final {
         std::lock_guard lock{mOutFdLock};
         if (mOutFd < 0) {
             return;
@@ -660,6 +622,67 @@
         }
     }
 
+    // Read tracing.
+    struct TracedRead {
+        uint64_t timestampUs;
+        android::dataloader::FileId fileId;
+        android::dataloader::Uid uid;
+        uint32_t firstBlockIdx;
+        uint32_t count;
+    };
+
+    void onPageReadsWithUid(dataloader::PageReadsWithUid pageReads) final {
+        auto trace = atrace_is_tag_enabled(ATRACE_TAG);
+        if (CC_LIKELY(!trace)) {
+            return;
+        }
+
+        TracedRead last = {};
+        for (auto&& read : pageReads) {
+            if (read.id != last.fileId || read.uid != last.uid ||
+                read.block != last.firstBlockIdx + last.count) {
+                traceRead(last);
+                last = TracedRead{
+                        .timestampUs = read.bootClockTsUs,
+                        .fileId = read.id,
+                        .uid = read.uid,
+                        .firstBlockIdx = (uint32_t)read.block,
+                        .count = 1,
+                };
+            } else {
+                ++last.count;
+            }
+        }
+        traceRead(last);
+    }
+
+    void traceRead(const TracedRead& read) {
+        if (!read.count) {
+            return;
+        }
+
+        FileIdx fileIdx = convertFileIdToFileIndex(read.fileId);
+
+        std::string trace;
+        if (read.uid != kIncFsNoUid) {
+            auto appId = multiuser_get_app_id(read.uid);
+            auto userId = multiuser_get_user_id(read.uid);
+            trace = android::base::
+                    StringPrintf("page_read: index=%lld count=%lld file=%d appid=%d userid=%d",
+                                 static_cast<long long>(read.firstBlockIdx),
+                                 static_cast<long long>(read.count), static_cast<int>(fileIdx),
+                                 static_cast<int>(appId), static_cast<int>(userId));
+        } else {
+            trace = android::base::StringPrintf("page_read: index=%lld count=%lld file=%d",
+                                                static_cast<long long>(read.firstBlockIdx),
+                                                static_cast<long long>(read.count),
+                                                static_cast<int>(fileIdx));
+        }
+
+        ATRACE_BEGIN(trace.c_str());
+        ATRACE_END();
+    }
+
     void receiver(unique_fd inout, MetadataMode mode) {
         std::vector<uint8_t> data;
         std::vector<IncFsDataBlock> instructions;
diff --git a/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp b/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp
index ec2549c..3f54529 100644
--- a/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp
+++ b/services/core/jni/com_android_server_powerstats_PowerStatsService.cpp
@@ -33,6 +33,7 @@
 static jmethodID method_C_init;
 static jfieldID field_C_id;
 static jfieldID field_C_name;
+static jfieldID field_C_subsystem;
 
 // EnergyMeasurement
 static jclass class_EM;
@@ -277,11 +278,14 @@
                     channelArray = env->NewObjectArray(railInfo.size(), class_C, nullptr);
                     for (int i = 0; i < railInfo.size(); i++) {
                         jstring name = env->NewStringUTF(railInfo[i].railName.c_str());
+                        jstring subsystem = env->NewStringUTF(railInfo[i].subsysName.c_str());
                         jobject channel = env->NewObject(class_C, method_C_init);
                         env->SetIntField(channel, field_C_id, railInfo[i].index);
                         env->SetObjectField(channel, field_C_name, name);
+                        env->SetObjectField(channel, field_C_subsystem, subsystem);
                         env->SetObjectArrayElement(channelArray, i, channel);
                         env->DeleteLocalRef(name);
+                        env->DeleteLocalRef(subsystem);
                         env->DeleteLocalRef(channel);
                     }
                 }
@@ -331,7 +335,8 @@
                                                                   field_EM_timestampMs,
                                                                   energyData[i].timestamp);
                                                 env->SetLongField(energyMeasurement,
-                                                                  field_EM_durationMs, -1);
+                                                                  field_EM_durationMs,
+                                                                  energyData[i].timestamp);
                                                 env->SetLongField(energyMeasurement,
                                                                   field_EM_energyUWs,
                                                                   energyData[i].energy);
@@ -359,6 +364,7 @@
     method_C_init = env->GetMethodID(class_C, "<init>", "()V");
     field_C_id = env->GetFieldID(class_C, "id", "I");
     field_C_name = env->GetFieldID(class_C, "name", "Ljava/lang/String;");
+    field_C_subsystem = env->GetFieldID(class_C, "subsystem", "Ljava/lang/String;");
 
     // EnergyMeasurement
     temp = env->FindClass("android/hardware/power/stats/EnergyMeasurement");
diff --git a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp
deleted file mode 100644
index b1fbe64..0000000
--- a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-#define LOG_TAG "StatsPullAtomService"
-
-#include <jni.h>
-#include <log/log.h>
-#include <nativehelper/JNIHelp.h>
-#include <stats_event.h>
-#include <stats_pull_atom_callback.h>
-#include <statslog.h>
-
-#include "stats/PowerStatsPuller.h"
-#include "stats/SubsystemSleepStatePuller.h"
-
-namespace android {
-
-static server::stats::PowerStatsPuller gPowerStatsPuller;
-static server::stats::SubsystemSleepStatePuller gSubsystemSleepStatePuller;
-
-static AStatsManager_PullAtomCallbackReturn onDevicePowerMeasurementCallback(int32_t atom_tag,
-                                                                             AStatsEventList* data,
-                                                                             void* cookie) {
-    return gPowerStatsPuller.Pull(atom_tag, data);
-}
-
-static AStatsManager_PullAtomCallbackReturn subsystemSleepStateCallback(int32_t atom_tag,
-                                                                        AStatsEventList* data,
-                                                                        void* cookie) {
-    return gSubsystemSleepStatePuller.Pull(atom_tag, data);
-}
-
-static void nativeInit(JNIEnv* env, jobject javaObject) {
-    // on device power measurement
-    gPowerStatsPuller = server::stats::PowerStatsPuller();
-    AStatsManager_setPullAtomCallback(android::util::ON_DEVICE_POWER_MEASUREMENT,
-                                      /* metadata= */ nullptr, onDevicePowerMeasurementCallback,
-                                      /* cookie= */ nullptr);
-
-    // subsystem sleep state
-    gSubsystemSleepStatePuller = server::stats::SubsystemSleepStatePuller();
-    AStatsManager_setPullAtomCallback(android::util::SUBSYSTEM_SLEEP_STATE,
-                                      /* metadata= */ nullptr, subsystemSleepStateCallback,
-                                      /* cookie= */ nullptr);
-}
-
-static const JNINativeMethod sMethods[] = {{"nativeInit", "()V", (void*)nativeInit}};
-
-int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env) {
-    int res = jniRegisterNativeMethods(env, "com/android/server/stats/pull/StatsPullAtomService",
-                                       sMethods, NELEM(sMethods));
-    if (res < 0) {
-        ALOGE("failed to register native methods");
-    }
-    return res;
-}
-
-} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 1893321..c5394f3 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -59,7 +59,6 @@
 int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
     JNIEnv* env);
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
-int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
 int register_android_server_AdbDebuggingManager(JNIEnv* env);
 int register_android_server_FaceService(JNIEnv* env);
 int register_android_server_GpuService(JNIEnv* env);
@@ -115,7 +114,6 @@
     register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
         env);
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
-    register_android_server_stats_pull_StatsPullAtomService(env);
     register_android_server_AdbDebuggingManager(env);
     register_android_server_FaceService(env);
     register_android_server_GpuService(env);
diff --git a/services/core/jni/stats/OWNERS b/services/core/jni/stats/OWNERS
deleted file mode 100644
index a61babf..0000000
--- a/services/core/jni/stats/OWNERS
+++ /dev/null
@@ -1,9 +0,0 @@
-jeffreyhuang@google.com
-joeo@google.com
-jtnguyen@google.com
-muhammadq@google.com
-ruchirr@google.com
-singhtejinder@google.com
-tsaichristine@google.com
-yaochen@google.com
-yro@google.com
diff --git a/services/core/jni/stats/PowerStatsPuller.cpp b/services/core/jni/stats/PowerStatsPuller.cpp
deleted file mode 100644
index 7f788c2..0000000
--- a/services/core/jni/stats/PowerStatsPuller.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#define DEBUG false // STOPSHIP if true
-#define LOG_TAG "PowerStatsPuller"
-
-#include <android/hardware/power/stats/1.0/IPowerStats.h>
-#include <log/log.h>
-#include <statslog.h>
-
-#include <vector>
-
-#include "PowerStatsPuller.h"
-
-using android::hardware::hidl_vec;
-using android::hardware::Return;
-using android::hardware::Void;
-using android::hardware::power::stats::V1_0::EnergyData;
-using android::hardware::power::stats::V1_0::IPowerStats;
-using android::hardware::power::stats::V1_0::RailInfo;
-using android::hardware::power::stats::V1_0::Status;
-
-namespace android {
-namespace server {
-namespace stats {
-
-static sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHal = nullptr;
-static std::mutex gPowerStatsHalMutex;
-static bool gPowerStatsExist = true; // Initialized to ensure making a first attempt.
-static std::vector<RailInfo> gRailInfo;
-
-struct PowerStatsPullerDeathRecipient : virtual public hardware::hidl_death_recipient {
-    virtual void serviceDied(uint64_t cookie,
-                             const wp<android::hidl::base::V1_0::IBase>& who) override {
-        // The HAL just died. Reset all handles to HAL services.
-        std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-        gPowerStatsHal = nullptr;
-    }
-};
-
-static sp<PowerStatsPullerDeathRecipient> gDeathRecipient = new PowerStatsPullerDeathRecipient();
-
-static bool getPowerStatsHalLocked() {
-    if (gPowerStatsHal == nullptr && gPowerStatsExist) {
-        gPowerStatsHal = android::hardware::power::stats::V1_0::IPowerStats::getService();
-        if (gPowerStatsHal == nullptr) {
-            ALOGW("Couldn't load power.stats HAL service");
-            gPowerStatsExist = false;
-        } else {
-            // Link death recipient to power.stats service handle
-            hardware::Return<bool> linked = gPowerStatsHal->linkToDeath(gDeathRecipient, 0);
-            if (!linked.isOk()) {
-                ALOGE("Transaction error in linking to power.stats HAL death: %s",
-                      linked.description().c_str());
-                gPowerStatsHal = nullptr;
-                return false;
-            } else if (!linked) {
-                ALOGW("Unable to link to power.stats HAL death notifications");
-                // We should still continue even though linking failed
-            }
-        }
-    }
-    return gPowerStatsHal != nullptr;
-}
-
-PowerStatsPuller::PowerStatsPuller() {}
-
-AStatsManager_PullAtomCallbackReturn PowerStatsPuller::Pull(int32_t atomTag,
-                                                            AStatsEventList* data) {
-    std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
-
-    if (!getPowerStatsHalLocked()) {
-        return AStatsManager_PULL_SKIP;
-    }
-
-    // Pull getRailInfo if necessary
-    if (gRailInfo.empty()) {
-        bool resultSuccess = true;
-        Return<void> ret = gPowerStatsHal->getRailInfo(
-                [&resultSuccess](const hidl_vec<RailInfo>& list, Status status) {
-                    resultSuccess = (status == Status::SUCCESS || status == Status::NOT_SUPPORTED);
-                    if (status != Status::SUCCESS) return;
-                    gRailInfo.reserve(list.size());
-                    for (size_t i = 0; i < list.size(); ++i) {
-                        gRailInfo.push_back(list[i]);
-                    }
-                });
-        if (!resultSuccess || !ret.isOk()) {
-            ALOGE("power.stats getRailInfo() failed. Description: %s", ret.description().c_str());
-            gPowerStatsHal = nullptr;
-            return AStatsManager_PULL_SKIP;
-        }
-        // If SUCCESS but empty, or if NOT_SUPPORTED, then never try again.
-        if (gRailInfo.empty()) {
-            ALOGE("power.stats has no rail information");
-            gPowerStatsExist = false; // No rail info, so never try again.
-            gPowerStatsHal = nullptr;
-            return AStatsManager_PULL_SKIP;
-        }
-    }
-
-    // Pull getEnergyData and write the data out
-    const hidl_vec<uint32_t> desiredRailIndices; // Empty vector indicates we want all.
-    bool resultSuccess = true;
-    Return<void> ret =
-            gPowerStatsHal
-                    ->getEnergyData(desiredRailIndices,
-                                    [&data, &resultSuccess](hidl_vec<EnergyData> energyDataList,
-                                                            Status status) {
-                                        resultSuccess = (status == Status::SUCCESS);
-                                        if (!resultSuccess) return;
-
-                                        for (size_t i = 0; i < energyDataList.size(); i++) {
-                                            const EnergyData& energyData = energyDataList[i];
-
-                                            if (energyData.index >= gRailInfo.size()) {
-                                                ALOGE("power.stats getEnergyData() returned an "
-                                                      "invalid rail index %u.",
-                                                      energyData.index);
-                                                resultSuccess = false;
-                                                return;
-                                            }
-                                            const RailInfo& rail = gRailInfo[energyData.index];
-
-                                            android::util::addAStatsEvent(
-                                                    data,
-                                                    android::util::ON_DEVICE_POWER_MEASUREMENT,
-                                                    rail.subsysName.c_str(), rail.railName.c_str(),
-                                                    energyData.timestamp, energyData.energy);
-
-                                            ALOGV("power.stat: %s.%s: %llu, %llu",
-                                                  rail.subsysName.c_str(), rail.railName.c_str(),
-                                                  (unsigned long long)energyData.timestamp,
-                                                  (unsigned long long)energyData.energy);
-                                        }
-                                    });
-    if (!resultSuccess || !ret.isOk()) {
-        ALOGE("power.stats getEnergyData() failed. Description: %s", ret.description().c_str());
-        gPowerStatsHal = nullptr;
-        return AStatsManager_PULL_SKIP;
-    }
-    return AStatsManager_PULL_SUCCESS;
-}
-
-} // namespace stats
-} // namespace server
-} // namespace android
diff --git a/services/core/jni/stats/PowerStatsPuller.h b/services/core/jni/stats/PowerStatsPuller.h
deleted file mode 100644
index db07d60..0000000
--- a/services/core/jni/stats/PowerStatsPuller.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-#pragma once
-
-#include <stats_event.h>
-#include <stats_pull_atom_callback.h>
-
-namespace android {
-namespace server {
-namespace stats {
-
-/**
- * Reads hal for power.stats
- */
-class PowerStatsPuller {
-public:
-    PowerStatsPuller();
-    AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data);
-};
-
-} // namespace stats
-} // namespace server
-} // namespace android
diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.cpp b/services/core/jni/stats/SubsystemSleepStatePuller.cpp
deleted file mode 100644
index 1ba98ef..0000000
--- a/services/core/jni/stats/SubsystemSleepStatePuller.cpp
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG false  // STOPSHIP if true
-#define LOG_TAG "SubsystemSleepStatePuller"
-
-#include <log/log.h>
-#include <statslog.h>
-
-#include <android/hardware/power/1.0/IPower.h>
-#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/stats/1.0/IPowerStats.h>
-
-#include <fcntl.h>
-#include <hardware/power.h>
-#include <hardware_legacy/power.h>
-#include <inttypes.h>
-#include <semaphore.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <unordered_map>
-#include "SubsystemSleepStatePuller.h"
-
-using android::hardware::hidl_vec;
-using android::hardware::power::V1_0::IPower;
-using android::hardware::power::V1_0::PowerStatePlatformSleepState;
-using android::hardware::power::V1_0::PowerStateVoter;
-using android::hardware::power::V1_1::PowerStateSubsystem;
-using android::hardware::power::V1_1::PowerStateSubsystemSleepState;
-using android::hardware::power::stats::V1_0::PowerEntityInfo;
-using android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult;
-using android::hardware::power::stats::V1_0::PowerEntityStateSpace;
-
-using android::hardware::Return;
-using android::hardware::Void;
-
-namespace android {
-namespace server {
-namespace stats {
-
-static std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)>
-        gPuller = {};
-
-static sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr;
-static sp<android::hardware::power::V1_1::IPower> gPowerHalV1_1 = nullptr;
-static sp<android::hardware::power::stats::V1_0::IPowerStats> gPowerStatsHalV1_0 = nullptr;
-
-static std::unordered_map<uint32_t, std::string> gEntityNames = {};
-static std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> gStateNames = {};
-
-static std::mutex gPowerHalMutex;
-
-// The caller must be holding gPowerHalMutex.
-static void deinitPowerStatsLocked() {
-    gPowerHalV1_0 = nullptr;
-    gPowerHalV1_1 = nullptr;
-    gPowerStatsHalV1_0 = nullptr;
-}
-
-struct SubsystemSleepStatePullerDeathRecipient : virtual public hardware::hidl_death_recipient {
-    virtual void serviceDied(uint64_t cookie,
-            const wp<android::hidl::base::V1_0::IBase>& who) override {
-
-        // The HAL just died. Reset all handles to HAL services.
-        std::lock_guard<std::mutex> lock(gPowerHalMutex);
-        deinitPowerStatsLocked();
-    }
-};
-
-static sp<SubsystemSleepStatePullerDeathRecipient> gDeathRecipient =
-        new SubsystemSleepStatePullerDeathRecipient();
-
-SubsystemSleepStatePuller::SubsystemSleepStatePuller() {}
-
-// The caller must be holding gPowerHalMutex.
-static bool checkResultLocked(const Return<void> &ret, const char* function) {
-    if (!ret.isOk()) {
-        ALOGE("%s failed: requested HAL service not available. Description: %s",
-            function, ret.description().c_str());
-        if (ret.isDeadObject()) {
-            deinitPowerStatsLocked();
-        }
-        return false;
-    }
-    return true;
-}
-
-// The caller must be holding gPowerHalMutex.
-// gPowerStatsHalV1_0 must not be null
-static bool initializePowerStats() {
-    using android::hardware::power::stats::V1_0::Status;
-
-    // Clear out previous content if we are re-initializing
-    gEntityNames.clear();
-    gStateNames.clear();
-
-    Return<void> ret;
-    ret = gPowerStatsHalV1_0->getPowerEntityInfo([](auto infos, auto status) {
-        if (status != Status::SUCCESS) {
-            ALOGE("Error getting power entity info");
-            return;
-        }
-
-        // construct lookup table of powerEntityId to power entity name
-        for (auto info : infos) {
-            gEntityNames.emplace(info.powerEntityId, info.powerEntityName);
-        }
-    });
-    if (!checkResultLocked(ret, __func__)) {
-        return false;
-    }
-
-    ret = gPowerStatsHalV1_0->getPowerEntityStateInfo({}, [](auto stateSpaces, auto status) {
-        if (status != Status::SUCCESS) {
-            ALOGE("Error getting state info");
-            return;
-        }
-
-        // construct lookup table of powerEntityId, powerEntityStateId to power entity state name
-        for (auto stateSpace : stateSpaces) {
-            std::unordered_map<uint32_t, std::string> stateNames = {};
-            for (auto state : stateSpace.states) {
-                stateNames.emplace(state.powerEntityStateId,
-                    state.powerEntityStateName);
-            }
-            gStateNames.emplace(stateSpace.powerEntityId, stateNames);
-        }
-    });
-    if (!checkResultLocked(ret, __func__)) {
-        return false;
-    }
-
-    return (!gEntityNames.empty()) && (!gStateNames.empty());
-}
-
-// The caller must be holding gPowerHalMutex.
-static bool getPowerStatsHalLocked() {
-    if(gPowerStatsHalV1_0 == nullptr) {
-        gPowerStatsHalV1_0 = android::hardware::power::stats::V1_0::IPowerStats::getService();
-        if (gPowerStatsHalV1_0 == nullptr) {
-            ALOGE("Unable to get power.stats HAL service.");
-            return false;
-        }
-
-        // Link death recipient to power.stats service handle
-        hardware::Return<bool> linked = gPowerStatsHalV1_0->linkToDeath(gDeathRecipient, 0);
-        if (!linked.isOk()) {
-            ALOGE("Transaction error in linking to power.stats HAL death: %s",
-                    linked.description().c_str());
-            deinitPowerStatsLocked();
-            return false;
-        } else if (!linked) {
-            ALOGW("Unable to link to power.stats HAL death notifications");
-            // We should still continue even though linking failed
-        }
-        return initializePowerStats();
-    }
-    return true;
-}
-
-// The caller must be holding gPowerHalMutex.
-static AStatsManager_PullAtomCallbackReturn getIPowerStatsDataLocked(int32_t atomTag,
-                                                                     AStatsEventList* data) {
-    using android::hardware::power::stats::V1_0::Status;
-
-    if(!getPowerStatsHalLocked()) {
-        return AStatsManager_PULL_SKIP;
-    }
-    // Get power entity state residency data
-    bool success = false;
-    Return<void> ret = gPowerStatsHalV1_0->getPowerEntityStateResidencyData(
-            {}, [&data, &success](auto results, auto status) {
-                if (status == Status::NOT_SUPPORTED) {
-                    ALOGW("getPowerEntityStateResidencyData is not supported");
-                    success = false;
-                    return;
-                }
-                for (auto result : results) {
-                    for (auto stateResidency : result.stateResidencyData) {
-                        android::util::addAStatsEvent(data, android::util::SUBSYSTEM_SLEEP_STATE,
-                                                      gEntityNames.at(result.powerEntityId).c_str(),
-                                                      gStateNames.at(result.powerEntityId)
-                                                              .at(stateResidency.powerEntityStateId)
-                                                              .c_str(),
-                                                      stateResidency.totalStateEntryCount,
-                                                      stateResidency.totalTimeInStateMs);
-                    }
-                }
-                success = true;
-            });
-    // Intentionally not returning early here.
-    // bool success determines if this succeeded or not.
-    checkResultLocked(ret, __func__);
-    if (!success) {
-        return AStatsManager_PULL_SKIP;
-    }
-    return AStatsManager_PULL_SUCCESS;
-}
-
-// The caller must be holding gPowerHalMutex.
-static bool getPowerHalLocked() {
-    if(gPowerHalV1_0 == nullptr) {
-        gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService();
-        if(gPowerHalV1_0 == nullptr) {
-            ALOGE("Unable to get power HAL service.");
-            return false;
-        }
-        gPowerHalV1_1 = android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
-
-        // Link death recipient to power service handle
-        hardware::Return<bool> linked = gPowerHalV1_0->linkToDeath(gDeathRecipient, 0);
-        if (!linked.isOk()) {
-            ALOGE("Transaction error in linking to power HAL death: %s",
-                    linked.description().c_str());
-            gPowerHalV1_0 = nullptr;
-            return false;
-        } else if (!linked) {
-            ALOGW("Unable to link to power. death notifications");
-            // We should still continue even though linking failed
-        }
-    }
-    return true;
-}
-
-// The caller must be holding gPowerHalMutex.
-static AStatsManager_PullAtomCallbackReturn getIPowerDataLocked(int32_t atomTag,
-                                                                AStatsEventList* data) {
-    using android::hardware::power::V1_0::Status;
-
-    if(!getPowerHalLocked()) {
-        return AStatsManager_PULL_SKIP;
-    }
-
-        Return<void> ret;
-        ret = gPowerHalV1_0->getPlatformLowPowerStats(
-                [&data](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
-                    if (status != Status::SUCCESS) return;
-
-                    for (size_t i = 0; i < states.size(); i++) {
-                        const PowerStatePlatformSleepState& state = states[i];
-                        android::util::addAStatsEvent(data, android::util::SUBSYSTEM_SLEEP_STATE,
-                                                      state.name.c_str(), "",
-                                                      state.totalTransitions,
-                                                      state.residencyInMsecSinceBoot);
-
-                        ALOGV("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
-                              (long long)state.residencyInMsecSinceBoot,
-                              (long long)state.totalTransitions,
-                              state.supportedOnlyInSuspend ? 1 : 0);
-                        for (const auto& voter : state.voters) {
-                            android::util::addAStatsEvent(data,
-                                                          android::util::SUBSYSTEM_SLEEP_STATE,
-                                                          state.name.c_str(), voter.name.c_str(),
-                                                          voter.totalNumberOfTimesVotedSinceBoot,
-                                                          voter.totalTimeInMsecVotedForSinceBoot);
-
-                            ALOGV("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(),
-                                  voter.name.c_str(),
-                                  (long long)voter.totalTimeInMsecVotedForSinceBoot,
-                                  (long long)voter.totalNumberOfTimesVotedSinceBoot);
-                        }
-                    }
-                });
-        if (!checkResultLocked(ret, __func__)) {
-            return AStatsManager_PULL_SKIP;
-        }
-
-        // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1
-        sp<android::hardware::power::V1_1::IPower> gPowerHal_1_1 =
-                android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
-        if (gPowerHal_1_1 != nullptr) {
-            ret = gPowerHal_1_1->getSubsystemLowPowerStats(
-                    [&data](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
-                        if (status != Status::SUCCESS) return;
-
-                        if (subsystems.size() > 0) {
-                            for (size_t i = 0; i < subsystems.size(); i++) {
-                                const PowerStateSubsystem& subsystem = subsystems[i];
-                                for (size_t j = 0; j < subsystem.states.size(); j++) {
-                                    const PowerStateSubsystemSleepState& state =
-                                            subsystem.states[j];
-                                    android::util::
-                                            addAStatsEvent(data,
-                                                           android::util::SUBSYSTEM_SLEEP_STATE,
-                                                           subsystem.name.c_str(),
-                                                           state.name.c_str(),
-                                                           state.totalTransitions,
-                                                           state.residencyInMsecSinceBoot);
-
-                                    ALOGV("subsystemstate: %s, %s, %lld, %lld, %lld",
-                                          subsystem.name.c_str(), state.name.c_str(),
-                                          (long long)state.residencyInMsecSinceBoot,
-                                          (long long)state.totalTransitions,
-                                          (long long)state.lastEntryTimestampMs);
-                                }
-                            }
-                        }
-                    });
-        }
-        return AStatsManager_PULL_SUCCESS;
-}
-
-// The caller must be holding gPowerHalMutex.
-std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)>
-getPullerLocked() {
-    std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList * data)>
-            ret = {};
-
-    // First see if power.stats HAL is available. Fall back to power HAL if
-    // power.stats HAL is unavailable.
-    if(android::hardware::power::stats::V1_0::IPowerStats::getService() != nullptr) {
-        ALOGI("Using power.stats HAL");
-        ret = getIPowerStatsDataLocked;
-    } else if(android::hardware::power::V1_0::IPower::getService() != nullptr) {
-        ALOGI("Using power HAL");
-        ret = getIPowerDataLocked;
-    }
-
-    return ret;
-}
-
-AStatsManager_PullAtomCallbackReturn SubsystemSleepStatePuller::Pull(int32_t atomTag,
-                                                                     AStatsEventList* data) {
-    std::lock_guard<std::mutex> lock(gPowerHalMutex);
-
-    if(!gPuller) {
-        gPuller = getPullerLocked();
-    }
-
-    if(gPuller) {
-        return gPuller(atomTag, data);
-    }
-
-    ALOGE("Unable to load Power Hal or power.stats HAL");
-    return AStatsManager_PULL_SKIP;
-}
-
-} // namespace stats
-} // namespace server
-}  // namespace android
diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.h b/services/core/jni/stats/SubsystemSleepStatePuller.h
deleted file mode 100644
index da9679c..0000000
--- a/services/core/jni/stats/SubsystemSleepStatePuller.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <stats_event.h>
-#include <stats_pull_atom_callback.h>
-
-namespace android {
-namespace server {
-namespace stats {
-
-/**
- * Reads hal for sleep states
- */
-class SubsystemSleepStatePuller {
-public:
-    SubsystemSleepStatePuller();
-    AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data);
-};
-
-} // namespace stats
-} // namespace server
-} // namespace android
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index d1918d8..bdccf45 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -8,11 +8,19 @@
 
 xsd_config {
     name: "platform-compat-config",
-    srcs: ["platform-compat-config.xsd"],
-    api_dir: "platform-compat-schema",
+    srcs: ["platform-compat/config/platform-compat-config.xsd"],
+    api_dir: "platform-compat/config/schema",
     package_name: "com.android.server.compat.config",
 }
 
+xsd_config {
+    name: "platform-compat-overrides",
+    srcs: ["platform-compat/overrides/platform-compat-overrides.xsd"],
+    api_dir: "platform-compat/overrides/schema",
+    package_name: "com.android.server.compat.overrides",
+    gen_writer: true,
+}
+
 
 xsd_config {
     name: "display-device-config",
diff --git a/services/core/xsd/platform-compat-schema/OWNERS b/services/core/xsd/platform-compat/OWNERS
similarity index 100%
rename from services/core/xsd/platform-compat-schema/OWNERS
rename to services/core/xsd/platform-compat/OWNERS
diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat/config/platform-compat-config.xsd
similarity index 100%
rename from services/core/xsd/platform-compat-config.xsd
rename to services/core/xsd/platform-compat/config/platform-compat-config.xsd
diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat/config/schema/current.txt
similarity index 100%
rename from services/core/xsd/platform-compat-schema/current.txt
rename to services/core/xsd/platform-compat/config/schema/current.txt
diff --git a/services/core/xsd/platform-compat-schema/last_current.txt b/services/core/xsd/platform-compat/config/schema/last_current.txt
similarity index 100%
rename from services/core/xsd/platform-compat-schema/last_current.txt
rename to services/core/xsd/platform-compat/config/schema/last_current.txt
diff --git a/services/core/xsd/platform-compat-schema/last_removed.txt b/services/core/xsd/platform-compat/config/schema/last_removed.txt
similarity index 100%
rename from services/core/xsd/platform-compat-schema/last_removed.txt
rename to services/core/xsd/platform-compat/config/schema/last_removed.txt
diff --git a/services/core/xsd/platform-compat-schema/removed.txt b/services/core/xsd/platform-compat/config/schema/removed.txt
similarity index 100%
rename from services/core/xsd/platform-compat-schema/removed.txt
rename to services/core/xsd/platform-compat/config/schema/removed.txt
diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
new file mode 100644
index 0000000..e27e1b8
--- /dev/null
+++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2019 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.
+  -->
+
+<!-- This defines the format of the XML file used to store compat config overrides in
+  ~ /data/misc/appcompat/compat_framework_overrides.xml
+-->
+<xs:schema version="2.0" elementFormDefault="qualified"
+    xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+
+    <xs:complexType name="override-value">
+        <xs:attribute type="xs:string" name="packageName" use="required" />
+        <xs:attribute type="xs:boolean" name="enabled" use="required" />
+    </xs:complexType>
+
+    <xs:complexType name="change-overrides">
+        <xs:attribute type="xs:long" name="changeId" use="required"/>
+        <xs:element name="validated">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" />
+                </xs:sequence>
+            </xs:complexType>
+        </xs:element>
+        <xs:element name="deferred">
+            <xs:complexType>
+                <xs:sequence>
+                    <xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" />
+                </xs:sequence>
+            </xs:complexType>
+        </xs:element>
+    </xs:complexType>
+
+    <xs:element name="overrides">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element name="change-overrides" type="change-overrides" maxOccurs="unbounded" minOccurs="0" />
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+</xs:schema>
diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt
new file mode 100644
index 0000000..08b8207
--- /dev/null
+++ b/services/core/xsd/platform-compat/overrides/schema/current.txt
@@ -0,0 +1,51 @@
+// Signature format: 2.0
+package com.android.server.compat.overrides {
+
+  public class ChangeOverrides {
+    ctor public ChangeOverrides();
+    method public long getChangeId();
+    method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred();
+    method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated();
+    method public void setChangeId(long);
+    method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred);
+    method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated);
+  }
+
+  public static class ChangeOverrides.Deferred {
+    ctor public ChangeOverrides.Deferred();
+    method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
+  }
+
+  public static class ChangeOverrides.Validated {
+    ctor public ChangeOverrides.Validated();
+    method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue();
+  }
+
+  public class OverrideValue {
+    ctor public OverrideValue();
+    method public boolean getEnabled();
+    method public String getPackageName();
+    method public void setEnabled(boolean);
+    method public void setPackageName(String);
+  }
+
+  public class Overrides {
+    ctor public Overrides();
+    method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides();
+  }
+
+  public class XmlParser {
+    ctor public XmlParser();
+    method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+    method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+  }
+
+  public class XmlWriter implements java.io.Closeable {
+    ctor public XmlWriter(java.io.PrintWriter);
+    method public void close();
+    method public static void write(com.android.server.compat.overrides.XmlWriter, com.android.server.compat.overrides.Overrides) throws java.io.IOException;
+  }
+
+}
+
diff --git a/services/core/xsd/platform-compat-schema/last_current.txt b/services/core/xsd/platform-compat/overrides/schema/last_current.txt
similarity index 100%
copy from services/core/xsd/platform-compat-schema/last_current.txt
copy to services/core/xsd/platform-compat/overrides/schema/last_current.txt
diff --git a/services/core/xsd/platform-compat-schema/last_removed.txt b/services/core/xsd/platform-compat/overrides/schema/last_removed.txt
similarity index 100%
copy from services/core/xsd/platform-compat-schema/last_removed.txt
copy to services/core/xsd/platform-compat/overrides/schema/last_removed.txt
diff --git a/services/core/xsd/platform-compat-schema/removed.txt b/services/core/xsd/platform-compat/overrides/schema/removed.txt
similarity index 100%
copy from services/core/xsd/platform-compat-schema/removed.txt
copy to services/core/xsd/platform-compat/overrides/schema/removed.txt
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 48f8b15..59b7367 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -136,6 +136,8 @@
     private static final String TAG_PASSWORD_COMPLEXITY = "password-complexity";
     private static final String TAG_ORGANIZATION_ID = "organization-id";
     private static final String TAG_ENROLLMENT_SPECIFIC_ID = "enrollment-specific-id";
+    private static final String TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS =
+            "admin-can-grant-sensors-permissions";
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -277,6 +279,7 @@
     boolean mCommonCriteriaMode;
     public String mOrganizationId;
     public String mEnrollmentSpecificId;
+    public boolean mAdminCanGrantSensorsPermissions;
 
     ActiveAdmin(DeviceAdminInfo info, boolean isParent) {
         this.info = info;
@@ -543,6 +546,8 @@
         if (!TextUtils.isEmpty(mEnrollmentSpecificId)) {
             writeTextToXml(out, TAG_ENROLLMENT_SPECIFIC_ID, mEnrollmentSpecificId);
         }
+        writeAttributeValueToXml(out, TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS,
+                mAdminCanGrantSensorsPermissions);
     }
 
     void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException {
@@ -792,6 +797,9 @@
                     Log.w(DevicePolicyManagerService.LOG_TAG,
                             "Missing Enrollment-specific ID.");
                 }
+            } else if (TAG_ADMIN_CAN_GRANT_SENSORS_PERMISSIONS.equals(tag)) {
+                mAdminCanGrantSensorsPermissions = parser.getAttributeBoolean(null, ATTR_VALUE,
+                        false);
             } else {
                 Slog.w(DevicePolicyManagerService.LOG_TAG, "Unknown admin tag: " + tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -1143,5 +1151,8 @@
             pw.print("mEnrollmentSpecificId=");
             pw.println(mEnrollmentSpecificId);
         }
+
+        pw.print("mAdminCanGrantSensorsPermissions");
+        pw.println(mAdminCanGrantSensorsPermissions);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 1194099..b52347f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -16,6 +16,7 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.app.admin.FullyManagedDeviceProvisioningParams;
 import android.app.admin.IDevicePolicyManager;
@@ -120,11 +121,17 @@
             @NonNull String callerPackage, @NonNull String enterpriseId, int userId) {}
 
     public UserHandle createAndProvisionManagedProfile(
-            @NonNull ManagedProfileProvisioningParams provisioningParams) {
+            @NonNull ManagedProfileProvisioningParams provisioningParams, String callerPackage) {
         return null;
     }
 
     public void provisionFullyManagedDevice(
-            FullyManagedDeviceProvisioningParams provisioningParams) {
+            FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) {
+    }
+
+    public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) {}
+
+    public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
+        return false;
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 15bc93e..8ea21ec 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -43,10 +43,18 @@
     @GuardedBy("mLock")
     private final SparseIntArray mPasswordQuality = new SparseIntArray();
 
+    @GuardedBy("mLock")
+    private final SparseIntArray mPermissionPolicy = new SparseIntArray();
+
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mCanGrantSensorsPermissions = new SparseBooleanArray();
+
     public void onUserRemoved(int userHandle) {
         synchronized (mLock) {
             mScreenCaptureDisabled.delete(userHandle);
             mPasswordQuality.delete(userHandle);
+            mPermissionPolicy.delete(userHandle);
+            mCanGrantSensorsPermissions.delete(userHandle);
         }
     }
 
@@ -78,12 +86,45 @@
         }
     }
 
+    @Override
+    public int getPermissionPolicy(@UserIdInt int userHandle) {
+        synchronized (mLock) {
+            return mPermissionPolicy.get(userHandle,
+                    DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+        }
+    }
+
+    /** Update the permission policy for the given user. */
+    public void setPermissionPolicy(@UserIdInt int userHandle, int policy) {
+        synchronized (mLock) {
+            mPermissionPolicy.put(userHandle, policy);
+        }
+    }
+
+    @Override
+    public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle) {
+        synchronized (mLock) {
+            return mCanGrantSensorsPermissions.get(userHandle, false);
+        }
+    }
+
+    /** Sets ahmin control over permission grants for user. */
+    public void setAdminCanGrantSensorsPermissions(@UserIdInt int userHandle,
+            boolean canGrant) {
+        synchronized (mLock) {
+            mCanGrantSensorsPermissions.put(userHandle, canGrant);
+        }
+    }
+
     /** Dump content */
     public void dump(IndentingPrintWriter pw) {
         pw.println("Device policy cache:");
         pw.increaseIndent();
         pw.println("Screen capture disabled: " + mScreenCaptureDisabled.toString());
         pw.println("Password quality: " + mPasswordQuality.toString());
+        pw.println("Permission policy: " + mPermissionPolicy.toString());
+        pw.println("Admin can grant sensors permission: "
+                + mCanGrantSensorsPermissions.toString());
         pw.decreaseIndent();
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c3bb757..404b0cf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -35,10 +35,8 @@
 import static android.app.admin.DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.CODE_NONSYSTEM_USER_EXISTS;
 import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER_SPLIT;
 import static android.app.admin.DevicePolicyManager.CODE_OK;
 import static android.app.admin.DevicePolicyManager.CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
-import static android.app.admin.DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.CODE_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER;
 import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING;
@@ -94,6 +92,7 @@
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
 import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
+import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -130,6 +129,7 @@
 import android.accounts.AccountManagerFuture;
 import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -159,6 +159,7 @@
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason;
+import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.app.admin.DeviceStateCache;
@@ -312,6 +313,7 @@
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
+import com.android.server.devicepolicy.Owners.OwnerDto;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.RestrictionsSet;
@@ -560,6 +562,21 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long USE_SET_LOCATION_ENABLED = 117835097L;
 
+    // Only add to the end of the list. Do not change or rearrange these values, that will break
+    // historical data. Do not use negative numbers or zero, logger only handles positive
+    // integers.
+    private static final int COPY_ACCOUNT_SUCCEEDED = 1;
+    private static final int COPY_ACCOUNT_FAILED = 2;
+    private static final int COPY_ACCOUNT_TIMED_OUT = 3;
+    private static final int COPY_ACCOUNT_EXCEPTION = 4;
+
+    @IntDef({
+            COPY_ACCOUNT_SUCCEEDED,
+            COPY_ACCOUNT_FAILED,
+            COPY_ACCOUNT_TIMED_OUT,
+            COPY_ACCOUNT_EXCEPTION})
+    private @interface CopyAccountStatus {}
+
     /**
      * Admin apps targeting Android S+ may not use
      * {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
@@ -894,12 +911,20 @@
     };
 
     protected static class RestrictionsListener implements UserRestrictionsListener {
-        private Context mContext;
+        private final Context mContext;
+        private final UserManagerInternal mUserManagerInternal;
+        private final DevicePolicyManagerService mDpms;
 
-        public RestrictionsListener(Context context) {
+        public RestrictionsListener(
+                Context context,
+                UserManagerInternal userManagerInternal,
+                DevicePolicyManagerService dpms) {
             mContext = context;
+            mUserManagerInternal = userManagerInternal;
+            mDpms = dpms;
         }
 
+        @Override
         public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
                 Bundle prevRestrictions) {
             final boolean newlyDisallowed =
@@ -909,13 +934,19 @@
             final boolean restrictionChanged = (newlyDisallowed != previouslyDisallowed);
 
             if (restrictionChanged) {
-                // Notify ManagedProvisioning to update the built-in cross profile intent filters.
-                Intent intent = new Intent(
-                        DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED);
-                intent.setPackage(getManagedProvisioningPackage(mContext));
-                intent.putExtra(Intent.EXTRA_USER_ID, userId);
-                intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-                mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+                final int parentId = mUserManagerInternal.getProfileParentId(userId);
+                if (parentId == userId) {
+                    return;
+                }
+
+                // Always reset filters on the parent user, which handles cross profile intent
+                // filters between the parent and its profiles.
+                Slog.i(LOG_TAG, "Resetting cross-profile intent filters on restriction "
+                        + "change");
+                mDpms.resetDefaultCrossProfileIntentFilters(parentId);
+                mContext.sendBroadcastAsUser(new Intent(
+                                DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED),
+                        UserHandle.of(userId));
             }
         }
     }
@@ -1069,30 +1100,51 @@
      * @throws UnsafeStateException if it's not safe to execute the operation.
      */
     private void checkCanExecuteOrThrowUnsafe(@DevicePolicyOperation int operation) {
-        if (!canExecute(operation)) {
-            if (mSafetyChecker == null) {
-                // Happens on CTS after it's set just once (by OneTimeSafetyChecker)
-                throw new UnsafeStateException(operation);
-            }
-            // Let mSafetyChecker customize it (for example, by explaining how to retry)
-            throw mSafetyChecker.newUnsafeStateException(operation);
+        int reason = getUnsafeOperationReason(operation);
+        if (reason == UNSAFE_OPERATION_REASON_NONE) return;
+
+        if (mSafetyChecker == null) {
+            // Happens on CTS after it's set just once (by OneTimeSafetyChecker)
+            throw new UnsafeStateException(operation, reason);
         }
+        // Let mSafetyChecker customize it (for example, by explaining how to retry)
+        throw mSafetyChecker.newUnsafeStateException(operation, reason);
     }
 
     /**
-     * Returns whether it's safe to execute the given {@code operation}.
+     * Returns whether it's safe to execute the given {@code operation}, and why.
      */
-    boolean canExecute(@DevicePolicyOperation int operation) {
-        return mSafetyChecker == null || mSafetyChecker.isDevicePolicyOperationSafe(operation);
+    @UnsafeOperationReason
+    int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
+        return mSafetyChecker == null ? UNSAFE_OPERATION_REASON_NONE
+                : mSafetyChecker.getUnsafeOperationReason(operation);
     }
 
     @Override
-    public void setNextOperationSafety(@DevicePolicyOperation int operation, boolean safe) {
+    public void setNextOperationSafety(@DevicePolicyOperation int operation,
+            @UnsafeOperationReason int reason) {
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
-        Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %b)",
-                DevicePolicyManager.operationToString(operation), safe));
-        mSafetyChecker = new OneTimeSafetyChecker(this, operation, safe);
+        Slog.i(LOG_TAG, String.format("setNextOperationSafety(%s, %s)",
+                DevicePolicyManager.operationToString(operation),
+                DevicePolicyManager.unsafeOperationReasonToString(reason)));
+        mSafetyChecker = new OneTimeSafetyChecker(this, operation, reason);
+    }
+
+    // Used by DevicePolicyManagerServiceShellCommand
+    List<OwnerDto> listAllOwners() {
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+
+        List<OwnerDto> owners = mOwners.listAllOwners();
+        synchronized (getLockObject()) {
+            for (int i = 0; i < owners.size(); i++) {
+                OwnerDto owner = owners.get(i);
+                owner.isAffiliated = isUserAffiliatedWithDeviceLocked(owner.userId);
+            }
+        }
+
+        return owners;
     }
 
     /**
@@ -1380,11 +1432,6 @@
             SystemProperties.set(key, value);
         }
 
-        // TODO (b/137101239): clean up split system user codes
-        boolean userManagerIsSplitSystemUser() {
-            return UserManager.isSplitSystemUser();
-        }
-
         boolean userManagerIsHeadlessSystemUserMode() {
             return UserManager.isHeadlessSystemUserMode();
         }
@@ -1605,7 +1652,8 @@
 
         mSetupContentObserver = new SetupContentObserver(mHandler);
 
-        mUserManagerInternal.addUserRestrictionsListener(new RestrictionsListener(mContext));
+        mUserManagerInternal.addUserRestrictionsListener(
+                new RestrictionsListener(mContext, mUserManagerInternal, this));
         mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
 
         loadOwners();
@@ -2942,6 +2990,8 @@
         // reading the value during user switch, due to onStartUser() being asynchronous.
         updatePasswordQualityCacheForUserGroup(
                 userId == UserHandle.USER_SYSTEM ? UserHandle.USER_ALL : userId);
+        updatePermissionPolicyCache(userId);
+        updateAdminCanGrantSensorsPermissionCache(userId);
 
         startOwnerService(userId, "start-user");
     }
@@ -7404,48 +7454,18 @@
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
     }
 
+    // TODO (b/137101239): remove this method in follow-up CL
+    // since it's only used for split system user.
     @Override
     public void setForceEphemeralUsers(ComponentName who, boolean forceEphemeralUsers) {
-        if (!mHasFeature) {
-            return;
-        }
-        Objects.requireNonNull(who, "ComponentName is null");
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
-
-        // Allow setting this policy to true only if there is a split system user.
-        if (forceEphemeralUsers && !mInjector.userManagerIsSplitSystemUser()) {
-            throw new UnsupportedOperationException(
-                    "Cannot force ephemeral users on systems without split system user.");
-        }
-        boolean removeAllUsers = false;
-        synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            if (deviceOwner.forceEphemeralUsers != forceEphemeralUsers) {
-                deviceOwner.forceEphemeralUsers = forceEphemeralUsers;
-                saveSettingsLocked(caller.getUserId());
-                mUserManagerInternal.setForceEphemeralUsers(forceEphemeralUsers);
-                removeAllUsers = forceEphemeralUsers;
-            }
-        }
-        if (removeAllUsers) {
-            mInjector.binderWithCleanCallingIdentity(() -> mUserManagerInternal.removeAllUsers());
-        }
+        throw new UnsupportedOperationException("This method was used by split system user only.");
     }
 
+    // TODO (b/137101239): remove this method in follow-up CL
+    // since it's only used for split system user.
     @Override
     public boolean getForceEphemeralUsers(ComponentName who) {
-        if (!mHasFeature) {
-            return false;
-        }
-        Objects.requireNonNull(who, "ComponentName is null");
-        final CallerIdentity caller = getCallerIdentity(who);
-        Preconditions.checkCallAuthorization(isDeviceOwner(caller));
-
-        synchronized (getLockObject()) {
-            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            return deviceOwner.forceEphemeralUsers;
-        }
+        throw new UnsupportedOperationException("This method was used by split system user only.");
     }
 
     @Override
@@ -12498,11 +12518,13 @@
                 || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT)));
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY);
 
+        final int forUser = caller.getUserId();
         synchronized (getLockObject()) {
-            DevicePolicyData userPolicy = getUserData(caller.getUserId());
+            DevicePolicyData userPolicy = getUserData(forUser);
             if (userPolicy.mPermissionPolicy != policy) {
                 userPolicy.mPermissionPolicy = policy;
-                saveSettingsLocked(caller.getUserId());
+                mPolicyCache.setPermissionPolicy(forUser, policy);
+                saveSettingsLocked(forUser);
             }
         }
         DevicePolicyEventLogger
@@ -12513,13 +12535,17 @@
                 .write();
     }
 
+    private void updatePermissionPolicyCache(int userId) {
+        synchronized (getLockObject()) {
+            DevicePolicyData userPolicy = getUserData(userId);
+            mPolicyCache.setPermissionPolicy(userId, userPolicy.mPermissionPolicy);
+        }
+    }
+
     @Override
     public int getPermissionPolicy(ComponentName admin) throws RemoteException {
         int userId = UserHandle.getCallingUserId();
-        synchronized (getLockObject()) {
-            DevicePolicyData userPolicy = getUserData(userId);
-            return userPolicy.mPermissionPolicy;
-        }
+        return mPolicyCache.getPermissionPolicy(userId);
     }
 
     @Override
@@ -12733,13 +12759,6 @@
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
                 case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
                     return checkDeviceOwnerProvisioningPreCondition(callingUserId);
-                // TODO (b/137101239): clean up split system user codes
-                //  ACTION_PROVISION_MANAGED_USER and ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE
-                //  only supported on split-user systems.
-                case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
-                    return checkManagedUserProvisioningPreCondition(callingUserId);
-                case DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE:
-                    return checkManagedShareableDeviceProvisioningPreCondition(callingUserId);
             }
         }
         throw new IllegalArgumentException("Unknown provisioning action " + action);
@@ -12775,14 +12794,12 @@
             }
         }
 
-        // TODO (b/137101239): clean up split system user codes
         if (isAdb) {
             // If shell command runs after user setup completed check device status. Otherwise, OK.
             if (mIsWatch || hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
                 // In non-headless system user mode, DO can be setup only if
                 // there's no non-system user
                 if (!mInjector.userManagerIsHeadlessSystemUserMode()
-                        && !mInjector.userManagerIsSplitSystemUser()
                         && mUserManager.getUserCount() > 1) {
                     return CODE_NONSYSTEM_USER_EXISTS;
                 }
@@ -12801,16 +12818,13 @@
             }
             return CODE_OK;
         } else {
-            if (!mInjector.userManagerIsSplitSystemUser()) {
-                // In non-split user mode, DO has to be user 0
-                if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
-                    return CODE_NOT_SYSTEM_USER;
-                }
-                // Only provision DO before setup wizard completes
-                // TODO (b/171423186): implement deferred DO setup for headless system user mode
-                if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
-                    return CODE_USER_SETUP_COMPLETED;
-                }
+            // DO has to be user 0
+            if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+                return CODE_NOT_SYSTEM_USER;
+            }
+            // Only provision DO before setup wizard completes
+            if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+                return CODE_USER_SETUP_COMPLETED;
             }
             return CODE_OK;
         }
@@ -12831,17 +12845,11 @@
         }
     }
 
-    // TODO (b/137101239): clean up split system user codes
     private int checkManagedProfileProvisioningPreCondition(String packageName,
             @UserIdInt int callingUserId) {
         if (!hasFeatureManagedUsers()) {
             return CODE_MANAGED_USERS_NOT_SUPPORTED;
         }
-        if (callingUserId == UserHandle.USER_SYSTEM
-                && mInjector.userManagerIsSplitSystemUser()) {
-            // Managed-profiles cannot be setup on the system user.
-            return CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER;
-        }
         if (getProfileOwnerAsUser(callingUserId) != null) {
             // Managed user cannot have a managed profile.
             return CODE_USER_HAS_PROFILE_OWNER;
@@ -12917,37 +12925,6 @@
         return null;
     }
 
-    // TODO (b/137101239): clean up split system user codes
-    private int checkManagedUserProvisioningPreCondition(int callingUserId) {
-        if (!hasFeatureManagedUsers()) {
-            return CODE_MANAGED_USERS_NOT_SUPPORTED;
-        }
-        if (!mInjector.userManagerIsSplitSystemUser()) {
-            // ACTION_PROVISION_MANAGED_USER only supported on split-user systems.
-            return CODE_NOT_SYSTEM_USER_SPLIT;
-        }
-        if (callingUserId == UserHandle.USER_SYSTEM) {
-            // System user cannot be a managed user.
-            return CODE_SYSTEM_USER;
-        }
-        if (hasUserSetupCompleted(callingUserId)) {
-            return CODE_USER_SETUP_COMPLETED;
-        }
-        if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) {
-            return CODE_HAS_PAIRED;
-        }
-        return CODE_OK;
-    }
-
-    // TODO (b/137101239): clean up split system user codes
-    private int checkManagedShareableDeviceProvisioningPreCondition(int callingUserId) {
-        if (!mInjector.userManagerIsSplitSystemUser()) {
-            // ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE only supported on split-user systems.
-            return CODE_NOT_SYSTEM_USER_SPLIT;
-        }
-        return checkDeviceOwnerProvisioningPreCondition(callingUserId);
-    }
-
     private boolean hasFeatureManagedUsers() {
         try {
             return mIPackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0);
@@ -13507,15 +13484,15 @@
         if (!mOwners.hasDeviceOwner()) {
             return false;
         }
-        if (userId == mOwners.getDeviceOwnerUserId()) {
-            // The user that the DO is installed on is always affiliated with the device.
-            return true;
-        }
         if (userId == UserHandle.USER_SYSTEM) {
             // The system user is always affiliated in a DO device,
             // even if in headless system user mode.
             return true;
         }
+        if (userId == mOwners.getDeviceOwnerUserId()) {
+            // The user that the DO is installed on is always affiliated with the device.
+            return true;
+        }
 
         final ComponentName profileOwner = getProfileOwnerAsUser(userId);
         if (profileOwner == null) {
@@ -16006,11 +15983,12 @@
 
     @Override
     public UserHandle createAndProvisionManagedProfile(
-            @NonNull ManagedProfileProvisioningParams provisioningParams) {
+            @NonNull ManagedProfileProvisioningParams provisioningParams,
+            @NonNull String callerPackage) {
         final ComponentName admin = provisioningParams.getProfileAdminComponentName();
         Objects.requireNonNull(admin, "admin is null");
 
-        final CallerIdentity caller = getCallerIdentity();
+        final CallerIdentity caller = getCallerIdentity(callerPackage);
         Preconditions.checkCallAuthorization(
                 hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
 
@@ -16025,6 +16003,7 @@
                         "Provisioning preconditions failed with result: " + result);
             }
 
+            final long startTime = SystemClock.elapsedRealtime();
             final Set<String> nonRequiredApps = provisioningParams.isLeaveAllSystemAppsEnabled()
                     ? Collections.emptySet()
                     : mOverlayPackagesProvider.getNonRequiredApps(
@@ -16041,8 +16020,12 @@
                         "Error creating profile, createProfileForUserEvenWhenDisallowed "
                                 + "returned null.");
             }
-
             resetInteractAcrossProfilesAppOps();
+            logEventDuration(
+                    DevicePolicyEnums.PLATFORM_PROVISIONING_CREATE_PROFILE_MS,
+                    startTime,
+                    callerPackage);
+
             installExistingAdminPackage(userInfo.id, admin.getPackageName());
             if (!enableAdminAndSetProfileOwner(
                     userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) {
@@ -16052,28 +16035,22 @@
             }
             setUserSetupComplete(userInfo.id);
 
-            startUser(userInfo.id);
+            startUser(userInfo.id, callerPackage);
             maybeMigrateAccount(
                     userInfo.id, caller.getUserId(), provisioningParams.getAccountToMigrate(),
-                    provisioningParams.isKeepAccountMigrated());
+                    provisioningParams.isKeepAccountMigrated(), callerPackage);
 
             if (provisioningParams.isOrganizationOwnedProvisioning()) {
-                markIsProfileOwnerOnOrganizationOwnedDevice(admin, userInfo.id);
-                restrictRemovalOfManagedProfile(admin, userInfo.id);
+                setProfileOwnerOnOrgOwnedDeviceState(admin, userInfo.id, caller.getUserId());
             }
 
-            final Intent intent = new Intent(DevicePolicyManager.ACTION_MANAGED_PROFILE_CREATED)
-                    .putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id)
-                    .putExtra(
-                            DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
-                            provisioningParams.isLeaveAllSystemAppsEnabled())
-                    .setPackage(getManagedProvisioningPackage(mContext))
-                    .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-
             return userInfo.getUserHandle();
         } catch (Exception e) {
-            // in case of any errors during provisioning, remove the newly created profile.
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
+                    .setStrings(callerPackage)
+                    .write();
+            // In case of any errors during provisioning, remove the newly created profile.
             if (userInfo != null) {
                 mUserManager.removeUserEvenWhenDisallowed(userInfo.id);
             }
@@ -16178,7 +16155,9 @@
                 mContext.getContentResolver(), USER_SETUP_COMPLETE, 1, userId);
     }
 
-    private void startUser(@UserIdInt int userId) throws IllegalStateException {
+    private void startUser(@UserIdInt int userId, String callerPackage)
+            throws IllegalStateException {
+        final long startTime = SystemClock.elapsedRealtime();
         final UserUnlockedBlockingReceiver unlockedReceiver = new UserUnlockedBlockingReceiver(
                 userId);
         mContext.registerReceiverAsUser(
@@ -16197,6 +16176,10 @@
                 throw new ServiceSpecificException(PROVISIONING_RESULT_STARTING_PROFILE_FAILED,
                         String.format("Timeout whilst waiting for unlock of user %d.", userId));
             }
+            logEventDuration(
+                    DevicePolicyEnums.PLATFORM_PROVISIONING_START_PROFILE_MS,
+                    startTime,
+                    callerPackage);
         } catch (RemoteException e) {
             // Shouldn't happen.
         } finally {
@@ -16204,9 +16187,9 @@
         }
     }
 
-    void maybeMigrateAccount(
+    private void maybeMigrateAccount(
             @UserIdInt int targetUserId, @UserIdInt int sourceUserId, Account accountToMigrate,
-            boolean keepAccountMigrated) {
+            boolean keepAccountMigrated, String callerPackage) {
         final UserHandle sourceUser = UserHandle.of(sourceUserId);
         final UserHandle targetUser = UserHandle.of(targetUserId);
         if (accountToMigrate == null) {
@@ -16217,13 +16200,16 @@
             Slog.w(LOG_TAG, "sourceUser and targetUser are the same, won't migrate account.");
             return;
         }
-        copyAccount(targetUser, sourceUser, accountToMigrate);
+        copyAccount(targetUser, sourceUser, accountToMigrate, callerPackage);
         if (!keepAccountMigrated) {
             removeAccount(accountToMigrate);
         }
     }
 
-    void copyAccount(UserHandle targetUser, UserHandle sourceUser, Account accountToMigrate) {
+    private void copyAccount(
+            UserHandle targetUser, UserHandle sourceUser, Account accountToMigrate,
+            String callerPackage) {
+        final long startTime = SystemClock.elapsedRealtime();
         try {
             final AccountManager accountManager = mContext.getSystemService(AccountManager.class);
             final boolean copySucceeded = accountManager.copyAccountToUser(
@@ -16232,16 +16218,35 @@
                     targetUser,
                     /* callback= */ null, /* handler= */ null)
                     .getResult(60 * 3, TimeUnit.SECONDS);
-            if (!copySucceeded) {
+            if (copySucceeded) {
+                logCopyAccountStatus(COPY_ACCOUNT_SUCCEEDED, callerPackage);
+                logEventDuration(
+                        DevicePolicyEnums.PLATFORM_PROVISIONING_COPY_ACCOUNT_MS,
+                        startTime,
+                        callerPackage);
+            } else {
+                logCopyAccountStatus(COPY_ACCOUNT_FAILED, callerPackage);
                 Slog.e(LOG_TAG, "Failed to copy account to " + targetUser);
             }
-        } catch (OperationCanceledException | AuthenticatorException | IOException e) {
+        } catch (OperationCanceledException e) {
             // Account migration is not considered a critical operation.
+            logCopyAccountStatus(COPY_ACCOUNT_TIMED_OUT, callerPackage);
+            Slog.e(LOG_TAG, "Exception copying account to " + targetUser, e);
+        } catch (AuthenticatorException | IOException e) {
+            logCopyAccountStatus(COPY_ACCOUNT_EXCEPTION, callerPackage);
             Slog.e(LOG_TAG, "Exception copying account to " + targetUser, e);
         }
     }
 
-    void removeAccount(Account account) {
+    private static void logCopyAccountStatus(@CopyAccountStatus int status, String callerPackage) {
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_COPY_ACCOUNT_STATUS)
+                .setInt(status)
+                .setStrings(callerPackage)
+                .write();
+    }
+
+    private void removeAccount(Account account) {
         final AccountManager accountManager =
                 mContext.getSystemService(AccountManager.class);
         final AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
@@ -16268,26 +16273,25 @@
         }
     }
 
-    private void markIsProfileOwnerOnOrganizationOwnedDevice(
-            ComponentName admin, @UserIdInt int profileId) {
-        getDpmForProfile(profileId).markProfileOwnerOnOrganizationOwnedDevice(admin);
+    private void setProfileOwnerOnOrgOwnedDeviceState(
+            ComponentName admin, @UserIdInt int profileId, @UserIdInt int parentUserId) {
+        synchronized (getLockObject()) {
+            markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(admin, profileId);
+        }
+        restrictRemovalOfManagedProfile(parentUserId);
     }
 
-    private void restrictRemovalOfManagedProfile(
-            ComponentName admin, @UserIdInt int profileId) {
-        getDpmForProfile(profileId).addUserRestriction(
-                admin, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
-    }
-
-    private DevicePolicyManager getDpmForProfile(@UserIdInt int profileId) {
-        final Context profileContext = mContext.createContextAsUser(
-                UserHandle.of(profileId), /* flags= */ 0);
-        return profileContext.getSystemService(DevicePolicyManager.class);
+    private void restrictRemovalOfManagedProfile(@UserIdInt int parentUserId) {
+        final UserHandle parentUserHandle = UserHandle.of(parentUserId);
+        mUserManager.setUserRestriction(
+                UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+                /* value= */ true,
+                parentUserHandle);
     }
 
     @Override
     public void provisionFullyManagedDevice(
-            FullyManagedDeviceProvisioningParams provisioningParams) {
+            FullyManagedDeviceProvisioningParams provisioningParams, String callerPackage) {
         ComponentName deviceAdmin = provisioningParams.getDeviceAdminComponentName();
 
         Objects.requireNonNull(deviceAdmin, "admin is null.");
@@ -16299,12 +16303,16 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
-            int result = checkProvisioningPreConditionSkipPermission(
-                    ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
-            if (result != CODE_OK) {
-                throw new ServiceSpecificException(
-                        PROVISIONING_RESULT_PRE_CONDITION_FAILED,
-                        "Provisioning preconditions failed with result: " + result);
+            // TODO(b/178187130): This check fails silent provisioning, uncomment once silent
+            //  provisioning is no longer used.
+            if (false) {
+                int result = checkProvisioningPreConditionSkipPermission(
+                        ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
+                if (result != CODE_OK) {
+                    throw new ServiceSpecificException(
+                            PROVISIONING_RESULT_PRE_CONDITION_FAILED,
+                            "Provisioning preconditions failed with result: " + result);
+                }
             }
 
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
@@ -16327,15 +16335,14 @@
             }
 
             disallowAddUser();
-
-            final Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISIONED_MANAGED_DEVICE)
-                    .putExtra(Intent.EXTRA_USER_HANDLE, caller.getUserId())
-                    .putExtra(
-                            DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
-                            provisioningParams.isLeaveAllSystemAppsEnabled())
-                    .setPackage(getManagedProvisioningPackage(mContext))
-                    .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+            setAdminCanGrantSensorsPermissionForUserUnchecked(caller.getUserId(),
+                    provisioningParams.canDeviceOwnerGrantSensorsPermissions());
+        } catch (Exception e) {
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
+                    .setStrings(callerPackage)
+                    .write();
+            throw e;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -16412,6 +16419,92 @@
     private boolean setActiveAdminAndDeviceOwner(
             @UserIdInt int userId, ComponentName adminComponent, String name) {
         enableAndSetActiveAdmin(userId, userId, adminComponent);
-        return setDeviceOwner(adminComponent, name, userId);
+        // TODO(b/178187130): Directly set DO and remove the check once silent provisioning is no
+        //  longer used.
+        if (getDeviceOwnerComponent(/* callingUserOnly= */ true) == null) {
+            return setDeviceOwner(adminComponent, name, userId);
+        }
+        return true;
+    }
+
+    private static void logEventDuration(int eventId, long startTime, String callerPackage) {
+        final long duration = SystemClock.elapsedRealtime() - startTime;
+        DevicePolicyEventLogger
+                .createEvent(eventId)
+                .setTimePeriod(duration)
+                .setStrings(callerPackage)
+                .write();
+    }
+
+    @Override
+    public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) {
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            try {
+                final List<UserInfo> profiles = mUserManager.getProfiles(userId);
+                final int numOfProfiles = profiles.size();
+                if (numOfProfiles <= 1) {
+                    return;
+                }
+
+                final String managedProvisioningPackageName = getManagedProvisioningPackage(
+                        mContext);
+                // Removes cross profile intent filters from the parent to all the profiles.
+                mIPackageManager.clearCrossProfileIntentFilters(
+                        userId, mContext.getOpPackageName());
+                // Setting and resetting default cross profile intent filters was previously handled
+                // by Managed Provisioning. For backwards compatibility, clear any intent filters
+                // that were set by ManagedProvisioning.
+                mIPackageManager.clearCrossProfileIntentFilters(
+                        userId, managedProvisioningPackageName);
+
+                // For each profile reset cross profile intent filters
+                for (int i = 0; i < numOfProfiles; i++) {
+                    UserInfo profile = profiles.get(i);
+                    mIPackageManager.clearCrossProfileIntentFilters(
+                            profile.id, mContext.getOpPackageName());
+                    // Clear any intent filters that were set by ManagedProvisioning.
+                    mIPackageManager.clearCrossProfileIntentFilters(
+                            profile.id, managedProvisioningPackageName);
+
+                    mUserManagerInternal.setDefaultCrossProfileIntentFilters(userId, profile.id);
+                }
+            } catch (RemoteException e) {
+                // Shouldn't happen.
+            }
+        });
+    }
+
+    private void setAdminCanGrantSensorsPermissionForUserUnchecked(int userId, boolean canGrant) {
+        synchronized (getLockObject()) {
+            ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
+
+            Preconditions.checkState(
+                    isDeviceOwner(owner) && owner.getUserHandle().getIdentifier() == userId,
+                    "May only be set on a the user of a device owner.");
+
+            owner.mAdminCanGrantSensorsPermissions = canGrant;
+            mPolicyCache.setAdminCanGrantSensorsPermissions(userId, canGrant);
+            saveSettingsLocked(userId);
+        }
+    }
+
+    private void updateAdminCanGrantSensorsPermissionCache(int userId) {
+        synchronized (getLockObject()) {
+            ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
+            final boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
+            mPolicyCache.setAdminCanGrantSensorsPermissions(userId, canGrant);
+        }
+    }
+
+    @Override
+    public boolean canAdminGrantSensorsPermissionsForUser(int userId) {
+        if (!mHasFeature) {
+            return false;
+        }
+
+        return mPolicyCache.canAdminGrantSensorsPermissionsForUser(userId);
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 22866b4..222c987 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -18,13 +18,17 @@
 import android.app.admin.DevicePolicyManager;
 import android.os.ShellCommand;
 
+import com.android.server.devicepolicy.Owners.OwnerDto;
+
 import java.io.PrintWriter;
+import java.util.List;
 import java.util.Objects;
 
 final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
 
     private static final String CMD_IS_SAFE_OPERATION = "is-operation-safe";
     private static final String CMD_SET_SAFE_OPERATION = "set-operation-safe";
+    private static final String CMD_LIST_OWNERS = "list-owners";
 
     private final DevicePolicyManagerService mService;
 
@@ -51,6 +55,8 @@
                     return runIsSafeOperation(pw);
                 case CMD_SET_SAFE_OPERATION:
                     return runSetSafeOperation(pw);
+                case CMD_LIST_OWNERS:
+                    return runListOwners(pw);
                 default:
                     return onInvalidCommand(pw, cmd);
             }
@@ -73,25 +79,62 @@
         pw.printf("    Prints this help text.\n\n");
         pw.printf("  %s <OPERATION_ID>\n", CMD_IS_SAFE_OPERATION);
         pw.printf("    Checks if the give operation is safe \n\n");
-        pw.printf("  %s <OPERATION_ID> <true|false>\n", CMD_SET_SAFE_OPERATION);
+        pw.printf("  %s <OPERATION_ID> <REASON_ID>\n", CMD_SET_SAFE_OPERATION);
         pw.printf("    Emulates the result of the next call to check if the given operation is safe"
                 + " \n\n");
+        pw.printf("  %s\n", CMD_LIST_OWNERS);
+        pw.printf("    Lists the device / profile owners per user \n\n");
     }
 
     private int runIsSafeOperation(PrintWriter pw) {
         int operation = Integer.parseInt(getNextArgRequired());
-        boolean safe = mService.canExecute(operation);
-        pw.printf("Operation %s is %s\n", DevicePolicyManager.operationToString(operation),
-                safe ? "SAFE" : "UNSAFE");
+        int reason = mService.getUnsafeOperationReason(operation);
+        boolean safe = reason == DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
+        pw.printf("Operation %s is %b. Reason: %s\n",
+                DevicePolicyManager.operationToString(operation), safe,
+                DevicePolicyManager.unsafeOperationReasonToString(reason));
         return 0;
     }
 
     private int runSetSafeOperation(PrintWriter pw) {
         int operation = Integer.parseInt(getNextArgRequired());
-        boolean safe = getNextArg().equals("true");
-        mService.setNextOperationSafety(operation, safe);
+        int reason = Integer.parseInt(getNextArgRequired());
+        mService.setNextOperationSafety(operation, reason);
         pw.printf("Next call to check operation %s will return %s\n",
-                DevicePolicyManager.operationToString(operation), safe ? "SAFE" : "UNSAFE");
+                DevicePolicyManager.operationToString(operation),
+                DevicePolicyManager.unsafeOperationReasonToString(reason));
         return 0;
     }
+
+    private int runListOwners(PrintWriter pw) {
+        List<OwnerDto> owners = mService.listAllOwners();
+        if (owners.isEmpty()) {
+            pw.println("none");
+            return 0;
+        }
+        int size = owners.size();
+        if (size == 1) {
+            pw.println("1 owner:");
+        } else {
+            pw.printf("%d owners:\n", size);
+        }
+
+        for (int i = 0; i < size; i++) {
+            OwnerDto owner = owners.get(i);
+            pw.printf("User %2d: admin=%s", owner.userId, owner.admin.flattenToShortString());
+            if (owner.isDeviceOwner) {
+                pw.print(",DeviceOwner");
+            }
+            if (owner.isProfileOwner) {
+                pw.print(",ProfileOwner");
+            }
+            if (owner.isAffiliated) {
+                pw.print(",Affiliated");
+            }
+            pw.println();
+        }
+
+        return 0;
+    }
+
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
index f7a8261..883f95d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java
@@ -15,9 +15,12 @@
  */
 package com.android.server.devicepolicy;
 
+import static android.app.admin.DevicePolicyManager.UNSAFE_OPERATION_REASON_NONE;
 import static android.app.admin.DevicePolicyManager.operationToString;
+import static android.app.admin.DevicePolicyManager.unsafeOperationReasonToString;
 
 import android.app.admin.DevicePolicyManager.DevicePolicyOperation;
+import android.app.admin.DevicePolicyManager.UnsafeOperationReason;
 import android.app.admin.DevicePolicySafetyChecker;
 import android.util.Slog;
 
@@ -40,31 +43,33 @@
     private final DevicePolicyManagerService mService;
     private final DevicePolicySafetyChecker mRealSafetyChecker;
     private final @DevicePolicyOperation int mOperation;
-    private final boolean mSafe;
+    private final @UnsafeOperationReason int mReason;
 
     OneTimeSafetyChecker(DevicePolicyManagerService service,
-            @DevicePolicyOperation int operation, boolean safe) {
+            @DevicePolicyOperation int operation, @UnsafeOperationReason int reason) {
         mService = Objects.requireNonNull(service);
         mOperation = operation;
-        mSafe = safe;
+        mReason = reason;
         mRealSafetyChecker = service.getDevicePolicySafetyChecker();
         Slog.i(TAG, "Saving real DevicePolicySafetyChecker as " + mRealSafetyChecker);
     }
 
     @Override
-    public boolean isDevicePolicyOperationSafe(@DevicePolicyOperation int operation) {
+    @UnsafeOperationReason
+    public int getUnsafeOperationReason(@DevicePolicyOperation int operation) {
         String name = operationToString(operation);
-        boolean safe = true;
+        int reason = UNSAFE_OPERATION_REASON_NONE;
         if (operation == mOperation) {
-            safe = mSafe;
+            reason = mReason;
         } else {
             Slog.wtf(TAG, "invalid call to isDevicePolicyOperationSafe(): asked for " + name
                     + ", should be " + operationToString(mOperation));
         }
-        Slog.i(TAG, "isDevicePolicyOperationSafe(" + name + "): returning " + safe
+        Slog.i(TAG, "getDevicePolicyOperationSafety(" + name + "): returning "
+                + unsafeOperationReasonToString(reason)
                 + " and restoring DevicePolicySafetyChecker to " + mRealSafetyChecker);
         mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker);
-        return safe;
+        return reason;
     }
 
     @Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 809afe0..1e70d59 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManagerInternal;
 import android.app.admin.SystemUpdateInfo;
@@ -57,6 +58,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.time.LocalDate;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -433,6 +435,23 @@
         }
     }
 
+    List<OwnerDto> listAllOwners() {
+        List<OwnerDto> owners = new ArrayList<>();
+        synchronized (mLock) {
+            if (mDeviceOwner != null) {
+                owners.add(new OwnerDto(mDeviceOwnerUserId, mDeviceOwner.admin,
+                        /* isDeviceOwner= */ true));
+            }
+            for (int i = 0; i < mProfileOwners.size(); i++) {
+                int userId = mProfileOwners.keyAt(i);
+                OwnerInfo info = mProfileOwners.valueAt(i);
+                owners.add(new OwnerDto(userId, info.admin, /* isDeviceOwner= */ false));
+            }
+        }
+        return owners;
+    }
+
+
     SystemUpdatePolicy getSystemUpdatePolicy() {
         synchronized (mLock) {
             return mSystemUpdatePolicy;
@@ -1076,6 +1095,24 @@
         }
     }
 
+    /**
+     * Data-transfer object used by {@link DevicePolicyManagerServiceShellCommand}.
+     */
+    static final class OwnerDto {
+        public final @UserIdInt int userId;
+        public final ComponentName admin;
+        public final boolean isDeviceOwner;
+        public final boolean isProfileOwner;
+        public boolean isAffiliated;
+
+        private OwnerDto(@UserIdInt int userId, ComponentName admin, boolean isDeviceOwner) {
+            this.userId = userId;
+            this.admin = Objects.requireNonNull(admin, "admin must not be null");
+            this.isDeviceOwner = isDeviceOwner;
+            this.isProfileOwner = !isDeviceOwner;
+        }
+    }
+
     public void dump(IndentingPrintWriter pw) {
         boolean needBlank = false;
         if (mDeviceOwner != null) {
diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp
index e978ed4..7534c7c 100644
--- a/services/incremental/Android.bp
+++ b/services/incremental/Android.bp
@@ -51,9 +51,9 @@
     static_libs: [
         "libbase",
         "libext2_uuid",
-        "libdataloader_aidl-unstable-cpp",
-        "libincremental_aidl-unstable-cpp",
-        "libincremental_manager_aidl-unstable-cpp",
+        "libdataloader_aidl-cpp",
+        "libincremental_aidl-cpp",
+        "libincremental_manager_aidl-cpp",
         "libprotobuf-cpp-lite",
         "service.incremental.proto",
         "libutils",
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index d224428..42360d8 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -88,7 +88,6 @@
     }
     sp<ProcessState> ps(ProcessState::self());
     ps->startThreadPool();
-    ps->giveThreadPoolName();
     // sm->addService increments the reference count, and now we're OK with returning the pointer.
     return self.get();
 }
@@ -118,18 +117,10 @@
 
 binder::Status BinderIncrementalService::createStorage(
         const ::std::string& path, const ::android::content::pm::DataLoaderParamsParcel& params,
-        int32_t createMode,
-        const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener,
-        const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams,
-        const ::android::sp<::android::os::incremental::IStorageHealthListener>& healthListener,
-        const ::std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts,
-        int32_t* _aidl_return) {
-    *_aidl_return =
-            mImpl.createStorage(path, const_cast<content::pm::DataLoaderParamsParcel&&>(params),
-                                android::incremental::IncrementalService::CreateOptions(createMode),
-                                statusListener,
-                                const_cast<StorageHealthCheckParams&&>(healthCheckParams),
-                                healthListener, perUidReadTimeouts);
+        int32_t createMode, int32_t* _aidl_return) {
+    *_aidl_return = mImpl.createStorage(path, params,
+                                        android::incremental::IncrementalService::CreateOptions(
+                                                createMode));
     return ok();
 }
 
@@ -144,6 +135,21 @@
     return ok();
 }
 
+binder::Status BinderIncrementalService::startLoading(
+        int32_t storageId, const ::android::content::pm::DataLoaderParamsParcel& params,
+        const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener,
+        const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams,
+        const ::android::sp<IStorageHealthListener>& healthListener,
+        const ::std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts,
+        bool* _aidl_return) {
+    *_aidl_return =
+            mImpl.startLoading(storageId, const_cast<content::pm::DataLoaderParamsParcel&&>(params),
+                               statusListener,
+                               const_cast<StorageHealthCheckParams&&>(healthCheckParams),
+                               healthListener, perUidReadTimeouts);
+    return ok();
+}
+
 binder::Status BinderIncrementalService::makeBindMount(int32_t storageId,
                                                        const std::string& sourcePath,
                                                        const std::string& targetFullPath,
@@ -253,9 +259,16 @@
     return ok();
 }
 
+binder::Status BinderIncrementalService::isFullyLoaded(int32_t storageId, int32_t* _aidl_return) {
+    *_aidl_return = mImpl.getLoadingProgress(storageId, /*stopOnFirstIncomplete=*/true)
+                            .blocksRemainingOrError();
+    return ok();
+}
+
 binder::Status BinderIncrementalService::getLoadingProgress(int32_t storageId,
                                                             float* _aidl_return) {
-    *_aidl_return = mImpl.getLoadingProgress(storageId).getProgress();
+    *_aidl_return =
+            mImpl.getLoadingProgress(storageId, /*stopOnFirstIncomplete=*/false).getProgress();
     return ok();
 }
 
@@ -291,11 +304,6 @@
     return ok();
 }
 
-binder::Status BinderIncrementalService::startLoading(int32_t storageId, bool* _aidl_return) {
-    *_aidl_return = mImpl.startLoading(storageId);
-    return ok();
-}
-
 binder::Status BinderIncrementalService::configureNativeBinaries(
         int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath,
         const std::string& abi, bool extractNativeLibs, bool* _aidl_return) {
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 9a4537a..740c542 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -39,16 +39,18 @@
     void onInvalidStorage(int mountId);
 
     binder::Status openStorage(const std::string& path, int32_t* _aidl_return) final;
-    binder::Status createStorage(
-            const ::std::string& path, const ::android::content::pm::DataLoaderParamsParcel& params,
-            int32_t createMode,
+    binder::Status createStorage(const ::std::string& path,
+                                 const ::android::content::pm::DataLoaderParamsParcel& params,
+                                 int32_t createMode, int32_t* _aidl_return) final;
+    binder::Status createLinkedStorage(const std::string& path, int32_t otherStorageId,
+                                       int32_t createMode, int32_t* _aidl_return) final;
+    binder::Status startLoading(
+            int32_t storageId, const ::android::content::pm::DataLoaderParamsParcel& params,
             const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener,
             const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams,
             const ::android::sp<IStorageHealthListener>& healthListener,
             const ::std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts,
-            int32_t* _aidl_return) final;
-    binder::Status createLinkedStorage(const std::string& path, int32_t otherStorageId,
-                                       int32_t createMode, int32_t* _aidl_return) final;
+            bool* _aidl_return) final;
     binder::Status makeBindMount(int32_t storageId, const std::string& sourcePath,
                                  const std::string& targetFullPath, int32_t bindType,
                                  int32_t* _aidl_return) final;
@@ -71,12 +73,12 @@
     binder::Status unlink(int32_t storageId, const std::string& path, int32_t* _aidl_return) final;
     binder::Status isFileFullyLoaded(int32_t storageId, const std::string& path,
                                      int32_t* _aidl_return) final;
+    binder::Status isFullyLoaded(int32_t storageId, int32_t* _aidl_return) final;
     binder::Status getLoadingProgress(int32_t storageId, float* _aidl_return) final;
     binder::Status getMetadataByPath(int32_t storageId, const std::string& path,
                                      std::vector<uint8_t>* _aidl_return) final;
     binder::Status getMetadataById(int32_t storageId, const std::vector<uint8_t>& id,
                                    std::vector<uint8_t>* _aidl_return) final;
-    binder::Status startLoading(int32_t storageId, bool* _aidl_return) final;
     binder::Status deleteStorage(int32_t storageId) final;
     binder::Status disallowReadLogs(int32_t storageId) final;
     binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index c9c5489..56cb3d1 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -356,7 +356,9 @@
             dprintf(fd, "    storages (%d): {\n", int(mnt.storages.size()));
             for (auto&& [storageId, storage] : mnt.storages) {
                 dprintf(fd, "      [%d] -> [%s] (%d %% loaded) \n", storageId, storage.name.c_str(),
-                        (int)(getLoadingProgressFromPath(mnt, storage.name.c_str()).getProgress() *
+                        (int)(getLoadingProgressFromPath(mnt, storage.name.c_str(),
+                                                         /*stopOnFirstIncomplete=*/false)
+                                      .getProgress() *
                               100));
             }
             dprintf(fd, "    }\n");
@@ -427,10 +429,8 @@
 }
 
 StorageId IncrementalService::createStorage(
-        std::string_view mountPoint, content::pm::DataLoaderParamsParcel&& dataLoaderParams,
-        CreateOptions options, const DataLoaderStatusListener& statusListener,
-        StorageHealthCheckParams&& healthCheckParams, const StorageHealthListener& healthListener,
-        const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) {
+        std::string_view mountPoint, const content::pm::DataLoaderParamsParcel& dataLoaderParams,
+        CreateOptions options) {
     LOG(INFO) << "createStorage: " << mountPoint << " | " << int(options);
     if (!path::isAbsolute(mountPoint)) {
         LOG(ERROR) << "path is not absolute: " << mountPoint;
@@ -538,13 +538,10 @@
         metadata::Mount m;
         m.mutable_storage()->set_id(ifs->mountId);
         m.mutable_loader()->set_type((int)dataLoaderParams.type);
-        m.mutable_loader()->set_allocated_package_name(&dataLoaderParams.packageName);
-        m.mutable_loader()->set_allocated_class_name(&dataLoaderParams.className);
-        m.mutable_loader()->set_allocated_arguments(&dataLoaderParams.arguments);
+        m.mutable_loader()->set_package_name(dataLoaderParams.packageName);
+        m.mutable_loader()->set_class_name(dataLoaderParams.className);
+        m.mutable_loader()->set_arguments(dataLoaderParams.arguments);
         const auto metadata = m.SerializeAsString();
-        m.mutable_loader()->release_arguments();
-        m.mutable_loader()->release_class_name();
-        m.mutable_loader()->release_package_name();
         if (auto err =
                     mIncFs->makeFile(ifs->control,
                                      path::join(ifs->root, constants().mount,
@@ -568,26 +565,9 @@
     // Done here as well, all data structures are in good state.
     secondCleanupOnFailure.release();
 
-    // DataLoader.
-    auto dataLoaderStub = prepareDataLoader(*ifs, std::move(dataLoaderParams), &statusListener,
-                                            std::move(healthCheckParams), &healthListener);
-    CHECK(dataLoaderStub);
-
     mountIt->second = std::move(ifs);
     l.unlock();
 
-    // Per Uid timeouts.
-    if (!perUidReadTimeouts.empty()) {
-        setUidReadTimeouts(mountId, perUidReadTimeouts);
-    }
-
-    if (mSystemReady.load(std::memory_order_relaxed) && !dataLoaderStub->requestCreate()) {
-        // failed to create data loader
-        LOG(ERROR) << "initializeDataLoader() failed";
-        deleteStorage(dataLoaderStub->id());
-        return kInvalidStorageId;
-    }
-
     LOG(INFO) << "created storage " << mountId;
     return mountId;
 }
@@ -634,6 +614,37 @@
     return storageId;
 }
 
+bool IncrementalService::startLoading(StorageId storage,
+                                      content::pm::DataLoaderParamsParcel&& dataLoaderParams,
+                                      const DataLoaderStatusListener& statusListener,
+                                      StorageHealthCheckParams&& healthCheckParams,
+                                      const StorageHealthListener& healthListener,
+                                      const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) {
+    // Per Uid timeouts.
+    if (!perUidReadTimeouts.empty()) {
+        setUidReadTimeouts(storage, perUidReadTimeouts);
+    }
+
+    // Re-initialize DataLoader.
+    std::unique_lock l(mLock);
+    const auto ifs = getIfsLocked(storage);
+    if (!ifs) {
+        return false;
+    }
+    if (ifs->dataLoaderStub) {
+        ifs->dataLoaderStub->cleanupResources();
+        ifs->dataLoaderStub = {};
+    }
+    l.unlock();
+
+    // DataLoader.
+    auto dataLoaderStub = prepareDataLoader(*ifs, std::move(dataLoaderParams), &statusListener,
+                                            std::move(healthCheckParams), &healthListener);
+    CHECK(dataLoaderStub);
+
+    return dataLoaderStub->requestStart();
+}
+
 IncrementalService::BindPathMap::const_iterator IncrementalService::findStorageLocked(
         std::string_view path) const {
     return findParentPath(mBindsByPath, path);
@@ -960,7 +971,12 @@
         LOG(ERROR) << "Invalid paths in link(): " << normOldPath << " | " << normNewPath;
         return -EINVAL;
     }
-    return mIncFs->link(ifsSrc->control, normOldPath, normNewPath);
+    if (auto err = mIncFs->link(ifsSrc->control, normOldPath, normNewPath); err < 0) {
+        PLOG(ERROR) << "Failed to link " << oldPath << "[" << normOldPath << "]"
+                    << " to " << newPath << "[" << normNewPath << "]";
+        return err;
+    }
+    return 0;
 }
 
 int IncrementalService::unlink(StorageId storage, std::string_view path) {
@@ -1065,23 +1081,6 @@
     return mIncFs->getMetadata(ifs->control, node);
 }
 
-bool IncrementalService::startLoading(StorageId storage) const {
-    DataLoaderStubPtr dataLoaderStub;
-    {
-        std::unique_lock l(mLock);
-        const auto& ifs = getIfsLocked(storage);
-        if (!ifs) {
-            return false;
-        }
-        dataLoaderStub = ifs->dataLoaderStub;
-        if (!dataLoaderStub) {
-            return false;
-        }
-    }
-    dataLoaderStub->requestStart();
-    return true;
-}
-
 void IncrementalService::setUidReadTimeouts(
         StorageId storage, const std::vector<PerUidReadTimeouts>& perUidReadTimeouts) {
     using microseconds = std::chrono::microseconds;
@@ -1092,11 +1091,15 @@
         maxPendingTimeUs = std::max(maxPendingTimeUs, microseconds(timeouts.maxPendingTimeUs));
     }
     if (maxPendingTimeUs < Constants::minPerUidTimeout) {
+        LOG(ERROR) << "Skip setting  read timeouts (maxPendingTime < Constants::minPerUidTimeout): "
+                   << duration_cast<milliseconds>(maxPendingTimeUs).count() << "ms < "
+                   << Constants::minPerUidTimeout.count() << "ms";
         return;
     }
 
     const auto ifs = getIfs(storage);
     if (!ifs) {
+        LOG(ERROR) << "Setting read timeouts failed: invalid storage id: " << storage;
         return;
     }
 
@@ -1126,7 +1129,7 @@
     }
 
     // Still loading?
-    const auto progress = getLoadingProgress(storage);
+    const auto progress = getLoadingProgress(storage, /*stopOnFirstIncomplete=*/true);
     if (progress.isError()) {
         // Something is wrong, abort.
         return clearUidReadTimeouts(storage);
@@ -1840,7 +1843,7 @@
 }
 
 IncrementalService::LoadingProgress IncrementalService::getLoadingProgress(
-        StorageId storage) const {
+        StorageId storage, bool stopOnFirstIncomplete) const {
     std::unique_lock l(mLock);
     const auto ifs = getIfsLocked(storage);
     if (!ifs) {
@@ -1853,11 +1856,11 @@
         return {-EINVAL, -EINVAL};
     }
     l.unlock();
-    return getLoadingProgressFromPath(*ifs, storageInfo->second.name);
+    return getLoadingProgressFromPath(*ifs, storageInfo->second.name, stopOnFirstIncomplete);
 }
 
 IncrementalService::LoadingProgress IncrementalService::getLoadingProgressFromPath(
-        const IncFsMount& ifs, std::string_view storagePath) const {
+        const IncFsMount& ifs, std::string_view storagePath, bool stopOnFirstIncomplete) const {
     ssize_t totalBlocks = 0, filledBlocks = 0;
     const auto filePaths = mFs->listFilesRecursive(storagePath);
     for (const auto& filePath : filePaths) {
@@ -1870,6 +1873,9 @@
         }
         totalBlocks += totalBlocksCount;
         filledBlocks += filledBlocksCount;
+        if (stopOnFirstIncomplete && filledBlocks < totalBlocks) {
+            break;
+        }
     }
 
     return {filledBlocks, totalBlocks};
@@ -1877,7 +1883,7 @@
 
 bool IncrementalService::updateLoadingProgress(
         StorageId storage, const StorageLoadingProgressListener& progressListener) {
-    const auto progress = getLoadingProgress(storage);
+    const auto progress = getLoadingProgress(storage, /*stopOnFirstIncomplete=*/false);
     if (progress.isError()) {
         // Failed to get progress from incfs, abort.
         return false;
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 3066121..5d53bac 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -113,6 +113,10 @@
         bool started() const { return totalBlocks > 0; }
         bool fullyLoaded() const { return !isError() && (totalBlocks == filledBlocks); }
 
+        int blocksRemainingOrError() const {
+            return totalBlocks <= 0 ? totalBlocks : totalBlocks - filledBlocks;
+        }
+
         float getProgress() const {
             return totalBlocks < 0
                     ? totalBlocks
@@ -130,15 +134,18 @@
     void onSystemReady();
 
     StorageId createStorage(std::string_view mountPoint,
-                            content::pm::DataLoaderParamsParcel&& dataLoaderParams,
-                            CreateOptions options, const DataLoaderStatusListener& statusListener,
-                            StorageHealthCheckParams&& healthCheckParams,
-                            const StorageHealthListener& healthListener,
-                            const std::vector<PerUidReadTimeouts>& perUidReadTimeouts);
+                            const content::pm::DataLoaderParamsParcel& dataLoaderParams,
+                            CreateOptions options);
     StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage,
                                   CreateOptions options = CreateOptions::Default);
     StorageId openStorage(std::string_view path);
 
+    bool startLoading(StorageId storage, content::pm::DataLoaderParamsParcel&& dataLoaderParams,
+                      const DataLoaderStatusListener& statusListener,
+                      StorageHealthCheckParams&& healthCheckParams,
+                      const StorageHealthListener& healthListener,
+                      const std::vector<PerUidReadTimeouts>& perUidReadTimeouts);
+
     int bind(StorageId storage, std::string_view source, std::string_view target, BindKind kind);
     int unbind(StorageId storage, std::string_view target);
     void deleteStorage(StorageId storage);
@@ -156,7 +163,9 @@
     int unlink(StorageId storage, std::string_view path);
 
     int isFileFullyLoaded(StorageId storage, std::string_view filePath) const;
-    LoadingProgress getLoadingProgress(StorageId storage) const;
+
+    LoadingProgress getLoadingProgress(StorageId storage, bool stopOnFirstIncomplete) const;
+
     bool registerLoadingProgressListener(StorageId storage,
                                          const StorageLoadingProgressListener& progressListener);
     bool unregisterLoadingProgressListener(StorageId storage);
@@ -167,8 +176,6 @@
     RawMetadata getMetadata(StorageId storage, std::string_view path) const;
     RawMetadata getMetadata(StorageId storage, FileId node) const;
 
-    bool startLoading(StorageId storage) const;
-
     bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath,
                                  std::string_view libDirRelativePath, std::string_view abi,
                                  bool extractNativeLibs);
@@ -388,7 +395,8 @@
     binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
 
     int isFileFullyLoadedFromPath(const IncFsMount& ifs, std::string_view filePath) const;
-    LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
+    LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path,
+                                               bool stopOnFirstIncomplete) const;
 
     int setFileContent(const IfsMountPtr& ifs, const incfs::FileId& fileId,
                        std::string_view debugFilePath, std::span<const uint8_t> data) const;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 6fabc58..25d3f77 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -210,7 +210,18 @@
     ErrorCode setUidReadTimeouts(const Control& control,
                                  const std::vector<android::os::incremental::PerUidReadTimeouts>&
                                          perUidReadTimeouts) const final {
-        return -ENOTSUP;
+        std::vector<incfs::UidReadTimeouts> timeouts;
+        timeouts.resize(perUidReadTimeouts.size());
+        for (int i = 0, size = perUidReadTimeouts.size(); i < size; ++i) {
+            auto&& timeout = timeouts[i];
+            const auto& perUidTimeout = perUidReadTimeouts[i];
+            timeout.uid = perUidTimeout.uid;
+            timeout.minTimeUs = perUidTimeout.minTimeUs;
+            timeout.minPendingTimeUs = perUidTimeout.minPendingTimeUs;
+            timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs;
+        }
+
+        return incfs::setUidReadTimeouts(control, timeouts);
     }
 };
 
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index f0deba7..8713f9d 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -678,9 +678,9 @@
     mVold->mountIncFsFails();
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_LT(storageId, 0);
 }
 
@@ -689,9 +689,9 @@
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_LT(storageId, 0);
 }
 
@@ -702,9 +702,9 @@
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_LT(storageId, 0);
 }
 
@@ -716,9 +716,9 @@
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_LT(storageId, 0);
 }
 
@@ -734,24 +734,24 @@
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
-    ASSERT_LT(storageId, 0);
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
 }
 
 TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) {
-    EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
-    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
-    EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(1);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
     mIncrementalService->deleteStorage(storageId);
 }
 
@@ -759,14 +759,15 @@
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
-    EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(2);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {}, {});
     // Simulated crash/other connection breakage.
     mDataLoaderManager->setDataLoaderStatusDestroyed();
 }
@@ -780,12 +781,13 @@
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     mDataLoaderManager->setDataLoaderStatusCreated();
-    ASSERT_TRUE(mIncrementalService->startLoading(storageId));
     mDataLoaderManager->setDataLoaderStatusStarted();
 }
 
@@ -793,16 +795,17 @@
     mDataLoader->initializeCreateOkNoStatus();
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
-    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
+    EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoader, start(_)).Times(1);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_TRUE(mIncrementalService->startLoading(storageId));
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     mDataLoaderManager->setDataLoaderStatusCreated();
 }
 
@@ -815,10 +818,12 @@
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     mDataLoaderManager->setDataLoaderStatusUnavailable();
 }
 
@@ -836,10 +841,12 @@
     EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(1);
     EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(1);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     mDataLoaderManager->setDataLoaderStatusUnavailable();
     ASSERT_NE(nullptr, mLooper->mCallback);
     ASSERT_NE(nullptr, mLooper->mCallbackData);
@@ -890,10 +897,12 @@
             kFirstTimestampUs - std::chrono::duration_cast<MCS>(unhealthyTimeout).count();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, std::move(params), listener, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {},
+                                      std::move(params), listener, {});
 
     // Healthy state, registered for pending reads.
     ASSERT_NE(nullptr, mLooper->mCallback);
@@ -985,10 +994,12 @@
     // Not expecting callback removal.
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     ASSERT_GE(mDataLoader->setStorageParams(true), 0);
 }
 
@@ -1006,10 +1017,12 @@
     // Not expecting callback removal.
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     ASSERT_GE(mDataLoader->setStorageParams(true), 0);
     // Now disable.
     mIncrementalService->disallowReadLogs(storageId);
@@ -1032,10 +1045,12 @@
     // After callback is called, disable read logs and remove callback.
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(1);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     ASSERT_GE(mDataLoader->setStorageParams(true), 0);
     ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
     mAppOpsManager->mStoredCallback->opChanged(0, {});
@@ -1051,10 +1066,12 @@
     EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0);
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     ASSERT_LT(mDataLoader->setStorageParams(true), 0);
 }
 
@@ -1068,10 +1085,12 @@
     EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0);
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     ASSERT_LT(mDataLoader->setStorageParams(true), 0);
 }
 
@@ -1087,18 +1106,20 @@
     EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0);
     EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {},
+                                                  {}, {}));
     ASSERT_LT(mDataLoader->setStorageParams(true), 0);
 }
 
 TEST_F(IncrementalServiceTest, testMakeDirectory) {
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     std::string dir_path("test");
 
     // Expecting incfs to call makeDir on a path like:
@@ -1115,9 +1136,9 @@
 
 TEST_F(IncrementalServiceTest, testMakeDirectories) {
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     auto first = "first"sv;
     auto second = "second"sv;
     auto third = "third"sv;
@@ -1138,9 +1159,9 @@
     mFs->hasNoFile();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
 }
 
@@ -1149,9 +1170,9 @@
     mFs->hasFiles();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
     ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
 }
@@ -1161,9 +1182,9 @@
     mFs->hasFiles();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
     ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
 }
@@ -1173,9 +1194,9 @@
     mFs->hasFiles();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
     ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
 }
@@ -1185,10 +1206,12 @@
     mFs->hasNoFile();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
-    ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId).getProgress());
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_EQ(1,
+              mIncrementalService->getLoadingProgress(storageId, /*stopOnFirstIncomplete=*/false)
+                      .getProgress());
 }
 
 TEST_F(IncrementalServiceTest, testGetLoadingProgressFailsWithFailedRanges) {
@@ -1196,11 +1219,13 @@
     mFs->hasFiles();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
-    ASSERT_EQ(-1, mIncrementalService->getLoadingProgress(storageId).getProgress());
+    ASSERT_EQ(-1,
+              mIncrementalService->getLoadingProgress(storageId, /*stopOnFirstIncomplete=*/false)
+                      .getProgress());
 }
 
 TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithEmptyRanges) {
@@ -1208,11 +1233,13 @@
     mFs->hasFiles();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3);
-    ASSERT_EQ(1, mIncrementalService->getLoadingProgress(storageId).getProgress());
+    ASSERT_EQ(1,
+              mIncrementalService->getLoadingProgress(storageId, /*stopOnFirstIncomplete=*/false)
+                      .getProgress());
 }
 
 TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccess) {
@@ -1220,11 +1247,13 @@
     mFs->hasFiles();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3);
-    ASSERT_EQ(0.5, mIncrementalService->getLoadingProgress(storageId).getProgress());
+    ASSERT_EQ(0.5,
+              mIncrementalService->getLoadingProgress(storageId, /*stopOnFirstIncomplete=*/false)
+                      .getProgress());
 }
 
 TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerSuccess) {
@@ -1232,9 +1261,9 @@
     mFs->hasFiles();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     sp<NiceMock<MockStorageLoadingProgressListener>> listener{
             new NiceMock<MockStorageLoadingProgressListener>};
     NiceMock<MockStorageLoadingProgressListener>* listenerMock = listener.get();
@@ -1257,9 +1286,9 @@
     mFs->hasFiles();
 
     TemporaryDir tempDir;
-    int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                                       IncrementalService::CreateOptions::CreateNew,
-                                                       {}, {}, {}, {});
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     sp<NiceMock<MockStorageLoadingProgressListener>> listener{
             new NiceMock<MockStorageLoadingProgressListener>};
     NiceMock<MockStorageLoadingProgressListener>* listenerMock = listener.get();
@@ -1275,10 +1304,12 @@
 
     TemporaryDir tempDir;
     int storageId =
-            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                               IncrementalService::CreateOptions::CreateNew, {},
-                                               StorageHealthCheckParams{}, listener, {});
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, listener,
+                                      {});
+
     StorageHealthCheckParams newParams;
     newParams.blockedTimeoutMs = 10000;
     newParams.unhealthyTimeoutMs = 20000;
@@ -1378,19 +1409,19 @@
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(1);
     EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(1);
-    EXPECT_CALL(*mDataLoader, start(_)).Times(0);
+    EXPECT_CALL(*mDataLoader, start(_)).Times(1);
     EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
     EXPECT_CALL(*mIncFs, setUidReadTimeouts(_, _)).Times(0);
     EXPECT_CALL(*mTimedQueue, addJob(_, _, _)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
     int storageId =
-            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                               IncrementalService::CreateOptions::CreateNew, {}, {},
-                                               {},
-                                               createPerUidTimeouts(
-                                                       {{0, 1, 2, 3}, {1, 2, 3, 4}, {2, 3, 4, 5}}));
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {},
+                                      createPerUidTimeouts(
+                                              {{0, 1, 2, 3}, {1, 2, 3, 4}, {2, 3, 4, 5}}));
 }
 
 TEST_F(IncrementalServiceTest, testPerUidTimeoutsSuccess) {
@@ -1410,13 +1441,12 @@
 
     TemporaryDir tempDir;
     int storageId =
-            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
-                                               IncrementalService::CreateOptions::CreateNew, {}, {},
-                                               {},
-                                               createPerUidTimeouts({{0, 1, 2, 3},
-                                                                     {1, 2, 3, 4},
-                                                                     {2, 3, 4, 100000000}}));
+            mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
+                                               IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
+    mIncrementalService->startLoading(storageId, std::move(mDataLoaderParcel), {}, {}, {},
+                                      createPerUidTimeouts(
+                                              {{0, 1, 2, 3}, {1, 2, 3, 4}, {2, 3, 4, 100000000}}));
 
     {
         // Timed callback present -> 0 progress.
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ad2bc65..7a4c611 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -97,12 +97,14 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.RuntimeInit;
+import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.ILockSettings;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.appbinding.AppBindingService;
+import com.android.server.apphibernation.AppHibernationService;
 import com.android.server.attention.AttentionManagerService;
 import com.android.server.audio.AudioService;
 import com.android.server.biometrics.AuthService;
@@ -145,6 +147,7 @@
 import com.android.server.om.OverlayManagerService;
 import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
+import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.people.PeopleService;
 import com.android.server.pm.BackgroundDexOptService;
@@ -158,6 +161,7 @@
 import com.android.server.pm.ShortcutService;
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.dex.SystemServerDexLoadReporter;
+import com.android.server.pm.verify.domain.DomainVerificationService;
 import com.android.server.policy.PermissionPolicyService;
 import com.android.server.policy.PhoneWindowManager;
 import com.android.server.policy.role.RoleServicePlatformHelperImpl;
@@ -204,12 +208,15 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Timer;
+import java.util.TreeSet;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 
@@ -236,6 +243,8 @@
             "com.android.server.appwidget.AppWidgetService";
     private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
             "com.android.server.voiceinteraction.VoiceInteractionManagerService";
+    private static final String APP_HIBERNATION_SERVICE_CLASS =
+            "com.android.server.apphibernation.AppHibernationService";
     private static final String PRINT_MANAGER_SERVICE_CLASS =
             "com.android.server.print.PrintManagerService";
     private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
@@ -323,19 +332,23 @@
     private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
             "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
     private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS =
-            "com.android.server.location.timezone.LocationTimeZoneManagerService$Lifecycle";
+            "com.android.server.timezonedetector.location.LocationTimeZoneManagerService$Lifecycle";
     private static final String GNSS_TIME_UPDATE_SERVICE_CLASS =
             "com.android.server.timedetector.GnssTimeUpdateService$Lifecycle";
     private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
             "com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
     private static final String ADB_SERVICE_CLASS =
             "com.android.server.adb.AdbService$Lifecycle";
+    private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
+            "com.android.server.speech.SpeechRecognitionManagerService";
     private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
             "com.android.server.appprediction.AppPredictionManagerService";
     private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
             "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
     private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
             "com.android.server.searchui.SearchUiManagerService";
+    private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
+            "com.android.server.smartspace.SmartspaceManagerService";
     private static final String DEVICE_IDLE_CONTROLLER_CLASS =
             "com.android.server.DeviceIdleController";
     private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
@@ -354,7 +367,12 @@
             "com.android.server.ConnectivityServiceInitializer";
     private static final String IP_CONNECTIVITY_METRICS_CLASS =
             "com.android.server.connectivity.IpConnectivityMetrics";
+    private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
+            "com.android.server.media.MediaCommunicationService";
+
     private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
+    private static final String GAME_MANAGER_SERVICE_CLASS =
+            "com.android.server.app.GameManagerService$Lifecycle";
 
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
@@ -467,6 +485,50 @@
 
     private static native void fdtrackAbort();
 
+    private static final File HEAP_DUMP_PATH = new File("/data/system/heapdump/");
+    private static final int MAX_HEAP_DUMPS = 2;
+
+    /**
+     * Dump system_server's heap.
+     *
+     * For privacy reasons, these aren't automatically pulled into bugreports:
+     * they must be manually pulled by the user.
+     */
+    private static void dumpHprof() {
+        // hprof dumps are rather large, so ensure we don't fill the disk by generating
+        // hundreds of these that will live forever.
+        TreeSet<File> existingTombstones = new TreeSet<>();
+        for (File file : HEAP_DUMP_PATH.listFiles()) {
+            if (!file.isFile()) {
+                continue;
+            }
+            if (!file.getName().startsWith("fdtrack-")) {
+                continue;
+            }
+            existingTombstones.add(file);
+        }
+        if (existingTombstones.size() >= MAX_HEAP_DUMPS) {
+            for (int i = 0; i < MAX_HEAP_DUMPS - 1; ++i) {
+                // Leave the newest `MAX_HEAP_DUMPS - 1` tombstones in place.
+                existingTombstones.pollLast();
+            }
+            for (File file : existingTombstones) {
+                if (!file.delete()) {
+                    Slog.w("System", "Failed to clean up hprof " + file);
+                }
+            }
+        }
+
+        try {
+            String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
+            String filename = "/data/system/heapdump/fdtrack-" + date + ".hprof";
+            Debug.dumpHprofData(filename);
+        } catch (IOException ex) {
+            Slog.e("System", "Failed to dump fdtrack hprof");
+            ex.printStackTrace();
+        }
+    }
+
     /**
      * Spawn a thread that monitors for fd leaks.
      */
@@ -491,11 +553,12 @@
                     enabled = true;
                 } else if (maxFd > abortThreshold) {
                     Slog.i("System", "fdtrack abort threshold reached, dumping and aborting");
+                    dumpHprof();
                     fdtrackAbort();
                 }
 
                 try {
-                    Thread.sleep(checkInterval);
+                    Thread.sleep(checkInterval * 1000);
                 } catch (InterruptedException ex) {
                     continue;
                 }
@@ -1047,11 +1110,18 @@
                     SystemClock.elapsedRealtime());
         }
 
+        t.traceBegin("StartDomainVerificationService");
+        DomainVerificationService domainVerificationService = new DomainVerificationService(
+                mSystemContext, SystemConfig.getInstance(), platformCompat);
+        mSystemServiceManager.startService(domainVerificationService);
+        t.traceEnd();
+
         t.traceBegin("StartPackageManagerService");
         try {
             Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
             mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
-                    mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
+                    domainVerificationService, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF,
+                    mOnlyCore);
         } finally {
             Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
         }
@@ -1196,6 +1266,11 @@
         mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
+        // Tracks native tombstones.
+        t.traceBegin("StartNativeTombstoneManagerService");
+        mSystemServiceManager.startService(NativeTombstoneManagerService.class);
+        t.traceEnd();
+
         // Service to capture bugreports.
         t.traceBegin("StartBugreportManagerService");
         mSystemServiceManager.startService(BugreportManagerService.class);
@@ -1331,6 +1406,13 @@
             mSystemServiceManager.startService(DropBoxManagerService.class);
             t.traceEnd();
 
+            // Grants default permissions and defines roles
+            t.traceBegin("StartRoleManagerService");
+            LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
+                    new RoleServicePlatformHelperImpl(mSystemContext));
+            mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
+            t.traceEnd();
+
             t.traceBegin("StartVibratorManagerService");
             mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
             t.traceEnd();
@@ -1626,12 +1708,21 @@
                         "MusicRecognitionManagerService not defined by OEM or disabled by flag");
             }
 
-
             startContentCaptureService(context, t);
             startAttentionService(context, t);
             startRotationResolverService(context, t);
             startSystemCaptionsManagerService(context, t);
 
+            // System Speech Recognition Service
+            if (deviceHasConfigString(context,
+                    R.string.config_defaultOnDeviceSpeechRecognitionService)) {
+                t.traceBegin("StartSpeechRecognitionManagerService");
+                mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS);
+                t.traceEnd();
+            } else {
+                Slog.d(TAG, "System speech recognition is not defined by OEM");
+            }
+
             // App prediction manager service
             if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) {
                 t.traceBegin("StartAppPredictionService");
@@ -1656,6 +1747,12 @@
             mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
             t.traceEnd();
 
+            // Smartspace manager service
+            // TODO: add deviceHasConfigString(context, R.string.config_defaultSmartspaceService)
+            t.traceBegin("StartSmartspaceService");
+            mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+            t.traceEnd();
+
             t.traceBegin("InitConnectivityModuleConnector");
             try {
                 ConnectivityModuleConnector.getInstance().init(context);
@@ -1691,15 +1788,6 @@
             }
             t.traceEnd();
 
-            t.traceBegin("StartVcnManagementService");
-            try {
-                vcnManagement = VcnManagementService.create(context);
-                ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
-            } catch (Throwable e) {
-                reportWtf("starting VCN Management Service", e);
-            }
-            t.traceEnd();
-
             t.traceBegin("StartFontManagerService");
             mSystemServiceManager.startService(FontManagerService.Lifecycle.class);
             t.traceEnd();
@@ -1801,6 +1889,15 @@
             networkPolicy.bindConnectivityManager(connectivity);
             t.traceEnd();
 
+            t.traceBegin("StartVcnManagementService");
+            try {
+                vcnManagement = VcnManagementService.create(context);
+                ServiceManager.addService(Context.VCN_MANAGEMENT_SERVICE, vcnManagement);
+            } catch (Throwable e) {
+                reportWtf("starting VCN Management Service", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("StartNsdService");
             try {
                 serviceDiscovery = NsdService.create(context);
@@ -2032,13 +2129,6 @@
                 t.traceEnd();
             }
 
-            // Grants default permissions and defines roles
-            t.traceBegin("StartRoleManagerService");
-            LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
-                    new RoleServicePlatformHelperImpl(mSystemContext));
-            mSystemServiceManager.startService(ROLE_SERVICE_CLASS);
-            t.traceEnd();
-
             // We need to always start this service, regardless of whether the
             // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
             // of initializing various settings.  It will internally modify its behavior
@@ -2047,6 +2137,12 @@
             mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
             t.traceEnd();
 
+            if (AppHibernationService.isAppHibernationEnabled()) {
+                t.traceBegin("StartAppHibernationService");
+                mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+                t.traceEnd();
+            }
+
             if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
                 t.traceBegin("StartGestureLauncher");
                 mSystemServiceManager.startService(GestureLauncherService.class);
@@ -2494,6 +2590,10 @@
         }
         t.traceEnd();
 
+        t.traceBegin("GameManagerService");
+        mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS);
+        t.traceEnd();
+
         t.traceBegin("StartBootPhaseDeviceSpecificServicesReady");
         mSystemServiceManager.startBootPhase(t, SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY);
         t.traceEnd();
@@ -2502,6 +2602,10 @@
         mSystemServiceManager.startService(APP_SEARCH_MANAGER_SERVICE_CLASS);
         t.traceEnd();
 
+        t.traceBegin("StartMediaCommunicationService");
+        mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
+        t.traceEnd();
+
         ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
                 START_BLOB_STORE_SERVICE);
 
@@ -2612,15 +2716,6 @@
                 reportWtf("making IpSec Service ready", e);
             }
             t.traceEnd();
-            t.traceBegin("MakeVcnManagementServiceReady");
-            try {
-                if (vcnManagementF != null) {
-                    vcnManagementF.systemReady();
-                }
-            } catch (Throwable e) {
-                reportWtf("making VcnManagementService ready", e);
-            }
-            t.traceEnd();
             t.traceBegin("MakeNetworkStatsServiceReady");
             try {
                 if (networkStatsF != null) {
@@ -2639,6 +2734,15 @@
                 reportWtf("making Connectivity Service ready", e);
             }
             t.traceEnd();
+            t.traceBegin("MakeVcnManagementServiceReady");
+            try {
+                if (vcnManagementF != null) {
+                    vcnManagementF.systemReady();
+                }
+            } catch (Throwable e) {
+                reportWtf("making VcnManagementService ready", e);
+            }
+            t.traceEnd();
             t.traceBegin("MakeNetworkPolicyServiceReady");
             try {
                 if (networkPolicyF != null) {
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 091e688..5453de1 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -45,7 +45,6 @@
 import com.android.server.SystemService;
 import com.android.server.people.data.DataManager;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -156,6 +155,13 @@
     final IBinder mService = new IPeopleManager.Stub() {
 
         @Override
+        public ConversationChannel getConversation(
+                String packageName, int userId, String shortcutId) {
+            enforceSystemRootOrSystemUI(getContext(), "get conversation");
+            return mDataManager.getConversation(packageName, userId, shortcutId);
+        }
+
+        @Override
         public ParceledListSlice<ConversationChannel> getRecentConversations() {
             enforceSystemRootOrSystemUI(getContext(), "get recent conversations");
             return new ParceledListSlice<>(
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 7521415..444f9c6 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -222,33 +222,66 @@
                 mContext.getPackageName(), intentFilter, callingUserId);
     }
 
+    /**
+     * Returns a {@link ConversationChannel} with the associated {@code shortcutId} if existent.
+     * Otherwise, returns null.
+     */
+    @Nullable
+    public ConversationChannel getConversation(String packageName, int userId, String shortcutId) {
+        UserData userData = getUnlockedUserData(userId);
+        if (userData != null) {
+            PackageData packageData = userData.getPackageData(packageName);
+            // App may have been uninstalled.
+            if (packageData != null) {
+                return getConversationChannel(packageData, shortcutId);
+            }
+        }
+        return null;
+    }
+
+    @Nullable
+    private ConversationChannel getConversationChannel(PackageData packageData, String shortcutId) {
+        ConversationInfo conversationInfo = packageData.getConversationInfo(shortcutId);
+        if (conversationInfo == null) {
+            return null;
+        }
+        int userId = packageData.getUserId();
+        String packageName = packageData.getPackageName();
+        ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
+        if (shortcutInfo == null) {
+            return null;
+        }
+        int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+        NotificationChannel parentChannel =
+                mNotificationManagerInternal.getNotificationChannel(packageName, uid,
+                        conversationInfo.getParentNotificationChannelId());
+        NotificationChannelGroup parentChannelGroup = null;
+        if (parentChannel != null) {
+            parentChannelGroup =
+                    mNotificationManagerInternal.getNotificationChannelGroup(packageName,
+                            uid, parentChannel.getId());
+        }
+        return new ConversationChannel(shortcutInfo, uid, parentChannel,
+                parentChannelGroup,
+                conversationInfo.getLastEventTimestamp(),
+                hasActiveNotifications(packageName, userId, shortcutId), false,
+                getStatuses(conversationInfo));
+    }
+
     /** Returns the cached non-customized recent conversations. */
     public List<ConversationChannel> getRecentConversations(@UserIdInt int callingUserId) {
         List<ConversationChannel> conversationChannels = new ArrayList<>();
         forPackagesInProfile(callingUserId, packageData -> {
-            String packageName = packageData.getPackageName();
-            int userId = packageData.getUserId();
             packageData.forAllConversations(conversationInfo -> {
                 if (!isCachedRecentConversation(conversationInfo)) {
                     return;
                 }
                 String shortcutId = conversationInfo.getShortcutId();
-                ShortcutInfo shortcutInfo = getShortcut(packageName, userId, shortcutId);
-                int uid = mPackageManagerInternal.getPackageUid(packageName, 0, userId);
-                NotificationChannel parentChannel =
-                        mNotificationManagerInternal.getNotificationChannel(packageName, uid,
-                                conversationInfo.getParentNotificationChannelId());
-                if (shortcutInfo == null || parentChannel == null) {
+                ConversationChannel channel = getConversationChannel(packageData, shortcutId);
+                if (channel == null || channel.getParentNotificationChannel() == null) {
                     return;
                 }
-                NotificationChannelGroup parentChannelGroup =
-                        mNotificationManagerInternal.getNotificationChannelGroup(packageName,
-                                uid, parentChannel.getId());
-                conversationChannels.add(
-                        new ConversationChannel(shortcutInfo, uid, parentChannel,
-                                parentChannelGroup,
-                                conversationInfo.getLastEventTimestamp(),
-                                hasActiveNotifications(packageName, userId, shortcutId)));
+                conversationChannels.add(channel);
             });
         });
         return conversationChannels;
@@ -372,6 +405,10 @@
             String conversationId) {
         ConversationStore cs = getConversationStoreOrThrow(packageName, userId);
         ConversationInfo conversationInfo = getConversationInfoOrThrow(cs, conversationId);
+        return getStatuses(conversationInfo);
+    }
+
+    private @NonNull List<ConversationStatus> getStatuses(ConversationInfo conversationInfo) {
         Collection<ConversationStatus> statuses = conversationInfo.getStatuses();
         if (statuses != null) {
             final ArrayList<ConversationStatus> list = new ArrayList<>(statuses.size());
diff --git a/services/smartspace/Android.bp b/services/smartspace/Android.bp
new file mode 100644
index 0000000..fcf780d
--- /dev/null
+++ b/services/smartspace/Android.bp
@@ -0,0 +1,13 @@
+filegroup {
+    name: "services.smartspace-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.smartspace",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.smartspace-sources"],
+    libs: ["services.core"],
+}
diff --git a/services/smartspace/OWNERS b/services/smartspace/OWNERS
new file mode 100644
index 0000000..19ef9d7
--- /dev/null
+++ b/services/smartspace/OWNERS
@@ -0,0 +1,2 @@
+srazdan@google.com
+alexmang@google.com
\ No newline at end of file
diff --git a/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
new file mode 100644
index 0000000..3b5a5a5
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/RemoteSmartspaceService.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.smartspace.ISmartspaceService;
+import android.text.format.DateUtils;
+
+import com.android.internal.infra.AbstractMultiplePendingRequestsRemoteService;
+
+
+/**
+ * Proxy to the {@link android.service.smartspace.SmartspaceService} implementation in another
+ * process.
+ */
+public class RemoteSmartspaceService extends
+        AbstractMultiplePendingRequestsRemoteService<RemoteSmartspaceService,
+                ISmartspaceService> {
+
+    private static final String TAG = "RemoteSmartspaceService";
+
+    private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.SECOND_IN_MILLIS;
+
+    private final RemoteSmartspaceServiceCallbacks mCallback;
+
+    public RemoteSmartspaceService(Context context, String serviceInterface,
+            ComponentName componentName, int userId,
+            RemoteSmartspaceServiceCallbacks callback, boolean bindInstantServiceAllowed,
+            boolean verbose) {
+        super(context, serviceInterface, componentName, userId, callback,
+                context.getMainThreadHandler(),
+                bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
+                verbose, /* initialCapacity= */ 1);
+        mCallback = callback;
+    }
+
+    @Override
+    protected ISmartspaceService getServiceInterface(IBinder service) {
+        return ISmartspaceService.Stub.asInterface(service);
+    }
+
+    @Override
+    protected long getTimeoutIdleBindMillis() {
+        return PERMANENT_BOUND_TIMEOUT_MS;
+    }
+
+    @Override
+    protected long getRemoteRequestMillis() {
+        return TIMEOUT_REMOTE_REQUEST_MILLIS;
+    }
+
+    /**
+     * Schedules a request to bind to the remote service.
+     */
+    public void reconnect() {
+        super.scheduleBind();
+    }
+
+    /**
+     * Schedule async request on remote service.
+     */
+    public void scheduleOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+        scheduleAsyncRequest(request);
+    }
+
+    /**
+     * Execute async request on remote service immediately instead of sending it to Handler queue.
+     */
+    public void executeOnResolvedService(@NonNull AsyncRequest<ISmartspaceService> request) {
+        executeAsyncRequest(request);
+    }
+
+    /**
+     * Failure callback
+     */
+    public interface RemoteSmartspaceServiceCallbacks
+            extends VultureCallback<RemoteSmartspaceService> {
+
+        /**
+         * Notifies a the failure or timeout of a remote call.
+         */
+        void onFailureOrTimeout(boolean timedOut);
+
+        /**
+         * Notifies change in connected state of the remote service.
+         */
+        void onConnectedStateChanged(boolean connected);
+    }
+
+    @Override // from AbstractRemoteService
+    protected void handleOnConnectedStateChanged(boolean connected) {
+        if (mCallback != null) {
+            mCallback.onConnectedStateChanged(connected);
+        }
+    }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
new file mode 100644
index 0000000..169b85e
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerService.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import static android.Manifest.permission.MANAGE_SMARTSPACE;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
+import static android.content.Context.SMARTSPACE_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.ISmartspaceManager;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.infra.AbstractMasterSystemService;
+import com.android.server.infra.FrameworkResourcesServiceNameResolver;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.FileDescriptor;
+import java.util.function.Consumer;
+
+/**
+ * A service used to return smartspace targets given a query.
+ */
+public class SmartspaceManagerService extends
+        AbstractMasterSystemService<SmartspaceManagerService, SmartspacePerUserService> {
+
+    private static final String TAG = SmartspaceManagerService.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final int MAX_TEMP_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+    public SmartspaceManagerService(Context context) {
+        super(context, new FrameworkResourcesServiceNameResolver(context,
+                        com.android.internal.R.string.config_defaultSmartspaceService), null,
+                PACKAGE_UPDATE_POLICY_NO_REFRESH | PACKAGE_RESTART_POLICY_NO_REFRESH);
+        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+    }
+
+    @Override
+    protected SmartspacePerUserService newServiceLocked(int resolvedUserId, boolean disabled) {
+        return new SmartspacePerUserService(this, mLock, resolvedUserId);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(SMARTSPACE_SERVICE, new SmartspaceManagerStub());
+    }
+
+    @Override
+    protected void enforceCallingPermissionForManagement() {
+        getContext().enforceCallingPermission(MANAGE_SMARTSPACE, TAG);
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected void onServicePackageUpdatedLocked(@UserIdInt int userId) {
+        final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+        if (service != null) {
+            service.onPackageUpdatedLocked();
+        }
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected void onServicePackageRestartedLocked(@UserIdInt int userId) {
+        final SmartspacePerUserService service = peekServiceForUserLocked(userId);
+        if (service != null) {
+            service.onPackageRestartedLocked();
+        }
+    }
+
+    @Override
+    protected int getMaximumTemporaryServiceDurationMs() {
+        return MAX_TEMP_SERVICE_DURATION_MS;
+    }
+
+    private class SmartspaceManagerStub extends ISmartspaceManager.Stub {
+
+        @Override
+        public void createSmartspaceSession(@NonNull SmartspaceConfig smartspaceConfig,
+                @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+            runForUserLocked("createSmartspaceSession", sessionId, (service) ->
+                    service.onCreateSmartspaceSessionLocked(smartspaceConfig, sessionId, token));
+        }
+
+        @Override
+        public void notifySmartspaceEvent(SmartspaceSessionId sessionId,
+                SmartspaceTargetEvent event) {
+            runForUserLocked("notifySmartspaceEvent", sessionId,
+                    (service) -> service.notifySmartspaceEventLocked(sessionId, event));
+        }
+
+        @Override
+        public void requestSmartspaceUpdate(SmartspaceSessionId sessionId) {
+            runForUserLocked("requestSmartspaceUpdate", sessionId,
+                    (service) -> service.requestSmartspaceUpdateLocked(sessionId));
+        }
+
+        @Override
+        public void registerSmartspaceUpdates(@NonNull SmartspaceSessionId sessionId,
+                @NonNull ISmartspaceCallback callback) {
+            runForUserLocked("registerSmartspaceUpdates", sessionId,
+                    (service) -> service.registerSmartspaceUpdatesLocked(sessionId, callback));
+        }
+
+        @Override
+        public void unregisterSmartspaceUpdates(SmartspaceSessionId sessionId,
+                ISmartspaceCallback callback) {
+            runForUserLocked("unregisterSmartspaceUpdates", sessionId,
+                    (service) -> service.unregisterSmartspaceUpdatesLocked(sessionId, callback));
+        }
+
+        @Override
+        public void destroySmartspaceSession(@NonNull SmartspaceSessionId sessionId) {
+            runForUserLocked("destroySmartspaceSession", sessionId,
+                    (service) -> service.onDestroyLocked(sessionId));
+        }
+
+        public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+                @Nullable FileDescriptor err,
+                @NonNull String[] args, @Nullable ShellCallback callback,
+                @NonNull ResultReceiver resultReceiver) {
+            new SmartspaceManagerServiceShellCommand(SmartspaceManagerService.this)
+                    .exec(this, in, out, err, args, callback, resultReceiver);
+        }
+
+        private void runForUserLocked(@NonNull final String func,
+                @NonNull final SmartspaceSessionId sessionId,
+                @NonNull final Consumer<SmartspacePerUserService> c) {
+            ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class);
+            final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                    sessionId.getUserId(), false, ALLOW_NON_FULL, null, null);
+
+            if (DEBUG) {
+                Slog.d(TAG, "runForUserLocked:" + func + " from pid=" + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid());
+            }
+            if (!(mServiceNameResolver.isTemporary(userId)
+                    || mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid()))) {
+
+                String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid();
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    final SmartspacePerUserService service = getServiceForUserLocked(userId);
+                    c.accept(service);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
new file mode 100644
index 0000000..4143418e
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspaceManagerServiceShellCommand.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * The shell command implementation for the SmartspaceManagerService.
+ */
+public class SmartspaceManagerServiceShellCommand extends ShellCommand {
+
+    private static final String TAG =
+            SmartspaceManagerServiceShellCommand.class.getSimpleName();
+
+    private final SmartspaceManagerService mService;
+
+    public SmartspaceManagerServiceShellCommand(@NonNull SmartspaceManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch (cmd) {
+            case "set": {
+                final String what = getNextArgRequired();
+                switch (what) {
+                    case "temporary-service": {
+                        final int userId = Integer.parseInt(getNextArgRequired());
+                        String serviceName = getNextArg();
+                        if (serviceName == null) {
+                            mService.resetTemporaryService(userId);
+                            pw.println("SmartspaceService temporarily reset. ");
+                            return 0;
+                        }
+                        final int duration = Integer.parseInt(getNextArgRequired());
+                        mService.setTemporaryService(userId, serviceName, duration);
+                        pw.println("SmartspaceService temporarily set to " + serviceName
+                                + " for " + duration + "ms");
+                        break;
+                    }
+                }
+            }
+            break;
+            default:
+                return handleDefaultCommands(cmd);
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        try (PrintWriter pw = getOutPrintWriter()) {
+            pw.println("SmartspaceManagerService commands:");
+            pw.println("  help");
+            pw.println("    Prints this help text.");
+            pw.println("");
+            pw.println("  set temporary-service USER_ID [COMPONENT_NAME DURATION]");
+            pw.println("    Temporarily (for DURATION ms) changes the service implemtation.");
+            pw.println("    To reset, call with just the USER_ID argument.");
+            pw.println("");
+        }
+    }
+}
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
new file mode 100644
index 0000000..db43468
--- /dev/null
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2021 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.smartspace;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppGlobals;
+import android.app.smartspace.ISmartspaceCallback;
+import android.app.smartspace.SmartspaceConfig;
+import android.app.smartspace.SmartspaceSessionId;
+import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.service.smartspace.ISmartspaceService;
+import android.service.smartspace.SmartspaceService;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+/**
+ * Per-user instance of {@link SmartspaceManagerService}.
+ */
+public class SmartspacePerUserService extends
+        AbstractPerUserSystemService<SmartspacePerUserService, SmartspaceManagerService>
+        implements RemoteSmartspaceService.RemoteSmartspaceServiceCallbacks {
+
+    private static final String TAG = SmartspacePerUserService.class.getSimpleName();
+    @GuardedBy("mLock")
+    private final ArrayMap<SmartspaceSessionId, SmartspaceSessionInfo> mSessionInfos =
+            new ArrayMap<>();
+    @Nullable
+    @GuardedBy("mLock")
+    private RemoteSmartspaceService mRemoteService;
+    /**
+     * When {@code true}, remote service died but service state is kept so it's restored after
+     * the system re-binds to it.
+     */
+    @GuardedBy("mLock")
+    private boolean mZombie;
+
+    protected SmartspacePerUserService(SmartspaceManagerService master,
+            Object lock, int userId) {
+        super(master, lock, userId);
+    }
+
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws NameNotFoundException {
+
+        ServiceInfo si;
+        try {
+            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException e) {
+            throw new NameNotFoundException("Could not get service for " + serviceComponent);
+        }
+        // TODO(b/177858728): must check that either the service is from a system component,
+        // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+        // OEMs are implementing the real service and also verify the proper permissions
+        return si;
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        if (enabledChanged) {
+            if (!isEnabledLocked()) {
+                // Clear the remote service for the next call
+                updateRemoteServiceLocked();
+            }
+        }
+        return enabledChanged;
+    }
+
+    /**
+     * Notifies the service of a new smartspace session.
+     */
+    @GuardedBy("mLock")
+    public void onCreateSmartspaceSessionLocked(@NonNull SmartspaceConfig smartspaceConfig,
+            @NonNull SmartspaceSessionId sessionId, @NonNull IBinder token) {
+        final boolean serviceExists = resolveService(sessionId,
+                s -> s.onCreateSmartspaceSession(smartspaceConfig, sessionId));
+
+        if (serviceExists && !mSessionInfos.containsKey(sessionId)) {
+            final SmartspaceSessionInfo sessionInfo = new SmartspaceSessionInfo(
+                    sessionId, smartspaceConfig, token, () -> {
+                synchronized (mLock) {
+                    onDestroyLocked(sessionId);
+                }
+            });
+            if (sessionInfo.linkToDeath()) {
+                mSessionInfos.put(sessionId, sessionInfo);
+            } else {
+                // destroy the session if calling process is already dead
+                onDestroyLocked(sessionId);
+            }
+        }
+    }
+
+    /**
+     * Records an smartspace event to the service.
+     */
+    @GuardedBy("mLock")
+    public void notifySmartspaceEventLocked(@NonNull SmartspaceSessionId sessionId,
+            @NonNull SmartspaceTargetEvent event) {
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, s -> s.notifySmartspaceEvent(sessionId, event));
+    }
+
+    /**
+     * Requests the service to return smartspace results of an input query.
+     */
+    @GuardedBy("mLock")
+    public void requestSmartspaceUpdateLocked(@NonNull SmartspaceSessionId sessionId) {
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId,
+                s -> s.requestSmartspaceUpdate(sessionId));
+    }
+
+    /**
+     * Registers a callback for continuous updates of predicted apps or shortcuts.
+     */
+    @GuardedBy("mLock")
+    public void registerSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+            @NonNull ISmartspaceCallback callback) {
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId,
+                s -> s.registerSmartspaceUpdates(sessionId, callback));
+        if (serviceExists) {
+            sessionInfo.addCallbackLocked(callback);
+        }
+    }
+
+    /**
+     * Unregisters a callback for continuous updates of predicted apps or shortcuts.
+     */
+    @GuardedBy("mLock")
+    public void unregisterSmartspaceUpdatesLocked(@NonNull SmartspaceSessionId sessionId,
+            @NonNull ISmartspaceCallback callback) {
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId,
+                s -> s.unregisterSmartspaceUpdates(sessionId, callback));
+        if (serviceExists) {
+            sessionInfo.removeCallbackLocked(callback);
+        }
+    }
+
+    /**
+     * Notifies the service of the end of an existing smartspace session.
+     */
+    @GuardedBy("mLock")
+    public void onDestroyLocked(@NonNull SmartspaceSessionId sessionId) {
+        if (isDebug()) {
+            Slog.d(TAG, "onDestroyLocked(): sessionId=" + sessionId);
+        }
+        final SmartspaceSessionInfo sessionInfo = mSessionInfos.remove(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, s -> s.onDestroySmartspaceSession(sessionId));
+        sessionInfo.destroy();
+    }
+
+    @Override
+    public void onFailureOrTimeout(boolean timedOut) {
+        if (isDebug()) {
+            Slog.d(TAG, "onFailureOrTimeout(): timed out=" + timedOut);
+        }
+        // Do nothing, we are just proxying to the smartspace ui service
+    }
+
+    @Override
+    public void onConnectedStateChanged(boolean connected) {
+        if (isDebug()) {
+            Slog.d(TAG, "onConnectedStateChanged(): connected=" + connected);
+        }
+        if (connected) {
+            synchronized (mLock) {
+                if (mZombie) {
+                    // Validation check - shouldn't happen
+                    if (mRemoteService == null) {
+                        Slog.w(TAG, "Cannot resurrect sessions because remote service is null");
+                        return;
+                    }
+                    mZombie = false;
+                    resurrectSessionsLocked();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onServiceDied(RemoteSmartspaceService service) {
+        if (isDebug()) {
+            Slog.w(TAG, "onServiceDied(): service=" + service);
+        }
+        synchronized (mLock) {
+            mZombie = true;
+        }
+        updateRemoteServiceLocked();
+    }
+
+    @GuardedBy("mLock")
+    private void updateRemoteServiceLocked() {
+        if (mRemoteService != null) {
+            mRemoteService.destroy();
+            mRemoteService = null;
+        }
+    }
+
+    void onPackageUpdatedLocked() {
+        if (isDebug()) {
+            Slog.v(TAG, "onPackageUpdatedLocked()");
+        }
+        destroyAndRebindRemoteService();
+    }
+
+    void onPackageRestartedLocked() {
+        if (isDebug()) {
+            Slog.v(TAG, "onPackageRestartedLocked()");
+        }
+        destroyAndRebindRemoteService();
+    }
+
+    private void destroyAndRebindRemoteService() {
+        if (mRemoteService == null) {
+            return;
+        }
+
+        if (isDebug()) {
+            Slog.d(TAG, "Destroying the old remote service.");
+        }
+        mRemoteService.destroy();
+        mRemoteService = null;
+
+        synchronized (mLock) {
+            mZombie = true;
+        }
+        mRemoteService = getRemoteServiceLocked();
+        if (mRemoteService != null) {
+            if (isDebug()) {
+                Slog.d(TAG, "Rebinding to the new remote service.");
+            }
+            mRemoteService.reconnect();
+        }
+    }
+
+    /**
+     * Called after the remote service connected, it's used to restore state from a 'zombie'
+     * service (i.e., after it died).
+     */
+    private void resurrectSessionsLocked() {
+        final int numSessions = mSessionInfos.size();
+        if (isDebug()) {
+            Slog.d(TAG, "Resurrecting remote service (" + mRemoteService + ") on "
+                    + numSessions + " sessions.");
+        }
+
+        for (SmartspaceSessionInfo sessionInfo : mSessionInfos.values()) {
+            sessionInfo.resurrectSessionLocked(this, sessionInfo.mToken);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    protected boolean resolveService(
+            @NonNull final SmartspaceSessionId sessionId,
+            @NonNull final AbstractRemoteService.AsyncRequest<ISmartspaceService> cb) {
+
+        final RemoteSmartspaceService service = getRemoteServiceLocked();
+        if (service != null) {
+            service.executeOnResolvedService(cb);
+        }
+        return service != null;
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSmartspaceService getRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            final String serviceName = getComponentNameLocked();
+            if (serviceName == null) {
+                if (mMaster.verbose) {
+                    Slog.v(TAG, "getRemoteServiceLocked(): not set");
+                }
+                return null;
+            }
+            ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+
+            mRemoteService = new RemoteSmartspaceService(getContext(),
+                    SmartspaceService.SERVICE_INTERFACE, serviceComponent, mUserId, this,
+                    mMaster.isBindInstantServiceAllowed(), mMaster.verbose);
+        }
+
+        return mRemoteService;
+    }
+
+    private static final class SmartspaceSessionInfo {
+        private static final boolean DEBUG = false;  // Do not submit with true
+        @NonNull
+        final IBinder mToken;
+        @NonNull
+        final IBinder.DeathRecipient mDeathRecipient;
+        @NonNull
+        private final SmartspaceSessionId mSessionId;
+        @NonNull
+        private final SmartspaceConfig mSmartspaceConfig;
+        private final RemoteCallbackList<ISmartspaceCallback> mCallbacks =
+                new RemoteCallbackList<ISmartspaceCallback>() {
+                    @Override
+                    public void onCallbackDied(ISmartspaceCallback callback) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Binder died for session Id=" + mSessionId
+                                    + " and callback=" + callback.asBinder());
+                        }
+                        if (mCallbacks.getRegisteredCallbackCount() == 0) {
+                            destroy();
+                        }
+                    }
+                };
+
+        SmartspaceSessionInfo(
+                @NonNull final SmartspaceSessionId id,
+                @NonNull final SmartspaceConfig context,
+                @NonNull final IBinder token,
+                @NonNull final IBinder.DeathRecipient deathRecipient) {
+            if (DEBUG) {
+                Slog.d(TAG, "Creating SmartspaceSessionInfo for session Id=" + id);
+            }
+            mSessionId = id;
+            mSmartspaceConfig = context;
+            mToken = token;
+            mDeathRecipient = deathRecipient;
+        }
+
+        void addCallbackLocked(ISmartspaceCallback callback) {
+            if (DEBUG) {
+                Slog.d(TAG, "Storing callback for session Id=" + mSessionId
+                        + " and callback=" + callback.asBinder());
+            }
+            mCallbacks.register(callback);
+        }
+
+        void removeCallbackLocked(ISmartspaceCallback callback) {
+            if (DEBUG) {
+                Slog.d(TAG, "Removing callback for session Id=" + mSessionId
+                        + " and callback=" + callback.asBinder());
+            }
+            mCallbacks.unregister(callback);
+        }
+
+        boolean linkToDeath() {
+            try {
+                mToken.linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Caller is dead before session can be started, sessionId: "
+                            + mSessionId);
+                }
+                return false;
+            }
+            return true;
+        }
+
+        void destroy() {
+            if (DEBUG) {
+                Slog.d(TAG, "Removing all callbacks for session Id=" + mSessionId
+                        + " and " + mCallbacks.getRegisteredCallbackCount() + " callbacks.");
+            }
+            if (mToken != null) {
+                mToken.unlinkToDeath(mDeathRecipient, 0);
+            }
+            mCallbacks.kill();
+        }
+
+        void resurrectSessionLocked(SmartspacePerUserService service, IBinder token) {
+            int callbackCount = mCallbacks.getRegisteredCallbackCount();
+            if (DEBUG) {
+                Slog.d(TAG, "Resurrecting remote service (" + service.getRemoteServiceLocked()
+                        + ") for session Id=" + mSessionId + " and "
+                        + callbackCount + " callbacks.");
+            }
+            service.onCreateSmartspaceSessionLocked(mSmartspaceConfig, mSessionId, token);
+        }
+    }
+}
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 21c863d..6c5c1d4 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -54,6 +54,7 @@
 import org.mockito.Mockito.verify
 import org.testng.Assert.assertThrows
 import java.io.File
+import java.util.UUID
 
 @RunWith(Parameterized::class)
 class PackageManagerComponentLabelIconOverrideTest {
@@ -262,8 +263,13 @@
                     .apply(block)
                     .hideAsFinal()
 
-    private fun makePkgSetting(pkgName: String) = spy(PackageSetting(pkgName, null, File("/test"),
-            null, null, null, null, 0, 0, 0, 0, null, null, null)) {
+    private fun makePkgSetting(pkgName: String) = spy(
+        PackageSetting(
+            pkgName, null, File("/test"),
+            null, null, null, null, 0, 0, 0, 0, null, null, null,
+            UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
+        )
+    ) {
         this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
     }
 
diff --git a/apex/permission/tests/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
similarity index 60%
rename from apex/permission/tests/Android.bp
rename to services/tests/PackageManagerServiceTests/unit/Android.bp
index 271e328..4aa8abc 100644
--- a/apex/permission/tests/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -13,25 +13,17 @@
 // limitations under the License.
 
 android_test {
-    name: "PermissionApexTests",
-    sdk_version: "test_current",
-    srcs: [
-        "java/**/*.kt",
-    ],
+    name: "PackageManagerServiceUnitTests",
+    srcs: ["src/**/*.kt"],
     static_libs: [
-        "service-permission.impl",
         "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "androidx.test.ext.truth",
-        "mockito-target-extended-minus-junit4",
+        "androidx.test.runner",
+        "junit",
+        "services.core",
+        "servicestests-utils",
+        "testng",
+        "truth-prebuilt",
     ],
-    jni_libs: [
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
-    ],
-    compile_multilib: "both",
-    test_suites: [
-        "general-tests",
-        "mts",
-    ],
+    platform_apis: true,
+    test_suites: ["device-tests"],
 }
diff --git a/apex/permission/tests/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
similarity index 64%
rename from apex/permission/tests/AndroidManifest.xml
rename to services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
index 57ee641..2ef7a1f 100644
--- a/apex/permission/tests/AndroidManifest.xml
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -16,17 +15,13 @@
   ~ limitations under the License.
   -->
 
-<manifest
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.permission.test">
-
-    <!-- The application has to be debuggable for static mocking to work. -->
-    <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-    </application>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test">
 
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.permission.test"
-        android:label="Permission APEX Tests" />
+        android:targetPackage="com.android.server.pm.test"
+        />
+
 </manifest>
+
diff --git a/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml
new file mode 100644
index 0000000..78dd1c5
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+
+<configuration description="Test module config for PackageManagerServiceUnitTests">
+    <option name="test-tag" value="PackageManagerServiceUnitTests" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="PackageManagerServiceUnitTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.server.pm.test" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
new file mode 100644
index 0000000..e99b071
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2020 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.pm.test.verify.domain
+
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Build
+import android.os.PatternMatcher
+import android.util.ArraySet
+import com.android.server.SystemConfig
+import com.android.server.compat.PlatformCompat
+import com.android.server.pm.verify.domain.DomainVerificationCollector
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+
+class DomainVerificationCollectorTest {
+
+    companion object {
+        private const val TEST_PKG_NAME = "com.test.pkg"
+    }
+
+    private val platformCompat: PlatformCompat = mockThrowOnUnmocked {
+        whenever(isChangeEnabled(eq(DomainVerificationCollector.RESTRICT_DOMAINS), any())) {
+            (arguments[1] as ApplicationInfo).targetSdkVersion >= Build.VERSION_CODES.S
+        }
+    }
+
+    @Test
+    fun verifyV1() {
+        val pkg = mockPkg(useV2 = false, autoVerify = true)
+        val collector = mockCollector()
+        assertThat(collector.collectAllWebDomains(pkg))
+                .containsExactly("example1.com", "example2.com", "example3.com")
+        assertThat(collector.collectAutoVerifyDomains(pkg))
+                .containsExactly("example1.com", "example2.com", "example3.com", "example4.com")
+    }
+
+    @Test
+    fun verifyV1NoAutoVerify() {
+        val pkg = mockPkg(useV2 = false, autoVerify = false)
+        val collector = mockCollector()
+        assertThat(collector.collectAllWebDomains(pkg))
+                .containsExactly("example1.com", "example2.com", "example3.com")
+        assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+    }
+
+    @Test
+    fun verifyV1ForceAutoVerify() {
+        val pkg = mockPkg(useV2 = false, autoVerify = false)
+        val collector = mockCollector(linkedApps = setOf(TEST_PKG_NAME))
+        assertThat(collector.collectAllWebDomains(pkg))
+                .containsExactly("example1.com", "example2.com", "example3.com")
+        assertThat(collector.collectAutoVerifyDomains(pkg))
+                .containsExactly("example1.com", "example2.com", "example3.com", "example4.com")
+    }
+
+    @Test
+    fun verifyV1NoValidIntentFilter() {
+        val pkg = mockThrowOnUnmocked<AndroidPackage> {
+            whenever(packageName) { TEST_PKG_NAME }
+            whenever(targetSdkVersion) { Build.VERSION_CODES.R }
+
+            val activityList = listOf(
+                    ParsedActivity().apply {
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    addAction(Intent.ACTION_VIEW)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("http")
+                                    addDataScheme("https")
+                                    addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+                                    addDataAuthority("example1.com", null)
+                                }
+                        )
+                    },
+                    ParsedActivity().apply {
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(true)
+                                    addAction(Intent.ACTION_VIEW)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("http")
+                                    addDataScheme("https")
+
+                                    // The presence of a non-web-scheme as the only autoVerify
+                                    // intent-filter, when non-forced, means that v1 will not pick
+                                    // up the package for verification.
+                                    addDataScheme("nonWebScheme")
+                                    addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+                                    addDataAuthority("example2.com", null)
+                                }
+                        )
+                    },
+            )
+
+            whenever(activities) { activityList }
+        }
+
+        val collector = mockCollector()
+        assertThat(collector.collectAllWebDomains(pkg))
+                .containsExactly("example1.com", "example2.com")
+        assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+    }
+
+    @Test
+    fun verifyV2() {
+        val pkg = mockPkg(useV2 = true, autoVerify = true)
+        val collector = mockCollector()
+
+        assertThat(collector.collectAllWebDomains(pkg))
+                .containsExactly("example1.com", "example2.com", "example3.com")
+        assertThat(collector.collectAutoVerifyDomains(pkg))
+                .containsExactly("example1.com", "example3.com")
+    }
+
+    @Test
+    fun verifyV2NoAutoVerify() {
+        val pkg = mockPkg(useV2 = true, autoVerify = false)
+        val collector = mockCollector()
+
+        assertThat(collector.collectAllWebDomains(pkg))
+                .containsExactly("example1.com", "example2.com", "example3.com")
+        assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+    }
+
+    @Test
+    fun verifyV2ForceAutoVerifyIgnored() {
+        val pkg = mockPkg(useV2 = true, autoVerify = false)
+        val collector = mockCollector(linkedApps = setOf(TEST_PKG_NAME))
+
+        assertThat(collector.collectAllWebDomains(pkg))
+                .containsExactly("example1.com", "example2.com", "example3.com")
+        assertThat(collector.collectAutoVerifyDomains(pkg)).isEmpty()
+    }
+
+    private fun mockCollector(linkedApps: Set<String> = emptySet()): DomainVerificationCollector {
+        val systemConfig = mockThrowOnUnmocked<SystemConfig> {
+            whenever(this.linkedApps) { ArraySet(linkedApps) }
+        }
+
+        return DomainVerificationCollector(platformCompat, systemConfig)
+    }
+
+    private fun mockPkg(useV2: Boolean, autoVerify: Boolean): AndroidPackage {
+        // Translate equivalent of the following manifest declaration. This string isn't actually
+        // parsed, but it's a far easier to read representation of the test data.
+        // language=XML
+        """
+            <xml>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <action android:name="android.intent.action.VIEW"/>
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="http"/>
+                    <data android:scheme="https"/>
+                    <data android:path="/sub"/>
+                    <data android:host="example1.com"/>
+                </intent-filter>
+                <intent-filter>
+                    <action android:name="android.intent.action.VIEW"/>
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="http"/>
+                    <data android:path="/sub2"/>
+                    <data android:host="example2.com"/>
+                </intent-filter>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <action android:name="android.intent.action.VIEW"/>
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="https"/>
+                    <data android:path="/sub3"/>
+                    <data android:host="example3.com"/>
+                </intent-filter>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <action android:name="android.intent.action.VIEW"/>
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <data android:scheme="https"/>
+                    <data android:path="/sub4"/>
+                    <data android:host="example4.com"/>
+                </intent-filter>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <action android:name="android.intent.action.VIEW"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="https"/>
+                    <data android:path="/sub5"/>
+                    <data android:host="example5.com"/>
+                </intent-filter>
+                <intent-filter android:autoVerify="$autoVerify">
+                    <category android:name="android.intent.category.BROWSABLE"/>
+                    <category android:name="android.intent.category.DEFAULT"/>
+                    <data android:scheme="https"/>
+                    <data android:path="/sub5"/>
+                    <data android:host="example5.com"/>
+                </intent-filter>
+            </xml>
+        """.trimIndent()
+
+        return mockThrowOnUnmocked<AndroidPackage> {
+            whenever(packageName) { TEST_PKG_NAME }
+            whenever(targetSdkVersion) {
+                if (useV2) Build.VERSION_CODES.S else Build.VERSION_CODES.R
+            }
+
+            // The intents are split into separate Activities to test that multiple are collected
+            val activityList = listOf(
+                    ParsedActivity().apply {
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addAction(Intent.ACTION_VIEW)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("http")
+                                    addDataScheme("https")
+                                    addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+                                    addDataAuthority("example1.com", null)
+                                }
+                        )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    addAction(Intent.ACTION_VIEW)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("http")
+                                    addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL)
+                                    addDataAuthority("example2.com", null)
+                                }
+                        )
+                    },
+                    ParsedActivity().apply {
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addAction(Intent.ACTION_VIEW)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("https")
+                                    addDataPath("/sub3", PatternMatcher.PATTERN_LITERAL)
+                                    addDataAuthority("example3.com", null)
+                                }
+                        )
+                    },
+                    ParsedActivity().apply {
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addAction(Intent.ACTION_VIEW)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addDataScheme("https")
+                                    addDataPath("/sub4", PatternMatcher.PATTERN_LITERAL)
+                                    addDataAuthority("example4.com", null)
+                                }
+                        )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addAction(Intent.ACTION_VIEW)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("https")
+                                    addDataPath("/sub5", PatternMatcher.PATTERN_LITERAL)
+                                    addDataAuthority("example5.com", null)
+                                }
+                        )
+                        addIntent(
+                                ParsedIntentInfo().apply {
+                                    setAutoVerify(autoVerify)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("https")
+                                    addDataPath("/sub6", PatternMatcher.PATTERN_LITERAL)
+                                    addDataAuthority("example6.com", null)
+                                }
+                        )
+                    },
+            )
+
+            whenever(activities) { activityList }
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
new file mode 100644
index 0000000..deb3147
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 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.pm.test.verify.domain
+
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationUserSelection
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.UserHandle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.UUID
+
+@RunWith(Parameterized::class)
+class DomainVerificationCoreApiTest {
+
+    companion object {
+        private val IS_EQUAL_TO: (value: Any, other: Any) -> Unit = { value, other ->
+            assertThat(value).isEqualTo(other)
+        }
+        private val IS_MAP_EQUAL_TO: (value: Map<*, *>, other: Map<*, *>) -> Unit = { value,
+                                                                                      other ->
+            assertThat(value).containsExactlyEntriesIn(other)
+        }
+
+        @JvmStatic
+        @Parameterized.Parameters
+        fun parameters() = arrayOf(
+            Parameter(
+                initial = {
+                    DomainVerificationRequest(
+                        setOf(
+                            "com.test.pkg.one",
+                            "com.test.pkg.two"
+                        )
+                    )
+                },
+                unparcel = { DomainVerificationRequest.CREATOR.createFromParcel(it) },
+                assertion = { first, second ->
+                    assertAll<DomainVerificationRequest, Set<String>>(first, second,
+                        { it.packageNames }, { it.component1() }) { value, other ->
+                        assertThat(value).containsExactlyElementsIn(other)
+                    }
+                }
+            ),
+            Parameter(
+                initial = {
+                    DomainVerificationInfo(
+                        UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
+                        "com.test.pkg",
+                        mapOf(
+                            "example.com" to 0,
+                            "example.org" to 1,
+                            "example.new" to 1000
+                        )
+                    )
+                },
+                unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) },
+                assertion = { first, second ->
+                    assertAll<DomainVerificationInfo, UUID>(first, second,
+                        { it.identifier }, { it.component1() }, IS_EQUAL_TO
+                    )
+                    assertAll<DomainVerificationInfo, String>(first, second,
+                        { it.packageName }, { it.component2() }, IS_EQUAL_TO
+                    )
+                    assertAll<DomainVerificationInfo, Map<String, Int?>>(first, second,
+                        { it.hostToStateMap }, { it.component3() }, IS_MAP_EQUAL_TO
+                    )
+                }
+            ),
+            Parameter(
+                initial = {
+                    DomainVerificationUserSelection(
+                        UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
+                        "com.test.pkg",
+                        UserHandle.of(10),
+                        true,
+                        mapOf(
+                            "example.com" to true,
+                            "example.org" to false,
+                            "example.new" to true
+                        )
+                    )
+                },
+                unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) },
+                assertion = { first, second ->
+                    assertAll<DomainVerificationUserSelection, UUID>(first, second,
+                        { it.identifier }, { it.component1() }, IS_EQUAL_TO
+                    )
+                    assertAll<DomainVerificationUserSelection, String>(first, second,
+                        { it.packageName }, { it.component2() }, IS_EQUAL_TO
+                    )
+                    assertAll<DomainVerificationUserSelection, UserHandle>(first, second,
+                        { it.user }, { it.component3() }, IS_EQUAL_TO
+                    )
+                    assertAll<DomainVerificationUserSelection, Boolean>(
+                        first, second, { it.isLinkHandlingAllowed },
+                        { it.component4() }, IS_EQUAL_TO
+                    )
+                    assertAll<DomainVerificationUserSelection, Map<String, Boolean>>(
+                        first, second, { it.hostToUserSelectionMap },
+                        { it.component5() }, IS_MAP_EQUAL_TO
+                    )
+                }
+            )
+        )
+
+        class Parameter<T : Parcelable>(
+            val initial: () -> T,
+            val unparcel: (Parcel) -> T,
+            private val assertion: (first: T, second: T) -> Unit
+        ) {
+            @Suppress("UNCHECKED_CAST")
+            fun assert(first: Any, second: Any) = assertion(first as T, second as T)
+        }
+
+        private fun <T> assertAll(vararg values: T, block: (value: T, other: T) -> Unit) {
+            values.indices.drop(1).forEach {
+                block(values[0], values[it])
+            }
+        }
+
+        private fun <T, V : Any> assertAll(
+            first: T,
+            second: T,
+            fieldValue: (T) -> V,
+            componentValue: (T) -> V,
+            assertion: (value: V, other: V) -> Unit
+        ) {
+            val values = arrayOf<Any>(fieldValue(first), fieldValue(second),
+                    componentValue(first), componentValue(second))
+            values.indices.drop(1).forEach {
+                @Suppress("UNCHECKED_CAST")
+                assertion(values[0] as V, values[it] as V)
+            }
+        }
+    }
+
+    @Parameterized.Parameter(0)
+    lateinit var parameter: Parameter<*>
+
+    @Test
+    fun parcel() {
+        val parcel = Parcel.obtain()
+        val initial = parameter.initial()
+        initial.writeToParcel(parcel, 0)
+        parcel.setDataPosition(0)
+
+        val newInitial = parameter.initial()
+        val unparceled = parameter.unparcel(parcel)
+        parameter.assert(newInitial, unparceled)
+
+        assertAll(initial, newInitial, unparceled) { value: Any, other: Any ->
+            assertThat(value).isEqualTo(other)
+        }
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
new file mode 100644
index 0000000..d863194
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2021 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.pm.test.verify.domain
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageUserState
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.parsing.component.ParsedActivity
+import android.content.pm.parsing.component.ParsedIntentInfo
+import android.os.Build
+import android.os.Process
+import android.util.ArraySet
+import android.util.Singleton
+import android.util.SparseArray
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.pm.PackageSetting
+import com.android.server.pm.verify.domain.DomainVerificationEnforcer
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.pm.verify.domain.DomainVerificationService
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.spyThrowOnUnmocked
+import com.android.server.testutils.whenever
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.testng.Assert.assertThrows
+import java.io.File
+import java.util.UUID
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+
+private typealias Enforcer = DomainVerificationEnforcer
+
+@RunWith(Parameterized::class)
+class DomainVerificationEnforcerTest {
+
+    val context: Context = InstrumentationRegistry.getInstrumentation().context
+
+    companion object {
+        private val INTERNAL_UIDS = listOf(Process.ROOT_UID, Process.SHELL_UID, Process.SYSTEM_UID)
+        private const val VERIFIER_UID = Process.FIRST_APPLICATION_UID + 1
+        private const val NON_VERIFIER_UID = Process.FIRST_APPLICATION_UID + 2
+
+        private const val TEST_PKG = "com.test"
+
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun parameters(): Array<Any> {
+            val makeEnforcer: (Context) -> DomainVerificationEnforcer = {
+                DomainVerificationEnforcer(it)
+            }
+
+            val mockPkg = mockThrowOnUnmocked<AndroidPackage> {
+                whenever(packageName) { TEST_PKG }
+                whenever(targetSdkVersion) { Build.VERSION_CODES.S }
+                whenever(activities) {
+                    listOf(
+                        ParsedActivity().apply {
+                            addIntent(
+                                ParsedIntentInfo().apply {
+                                    autoVerify = true
+                                    addAction(Intent.ACTION_VIEW)
+                                    addCategory(Intent.CATEGORY_BROWSABLE)
+                                    addCategory(Intent.CATEGORY_DEFAULT)
+                                    addDataScheme("https")
+                                    addDataAuthority("example.com", null)
+                                }
+                            )
+                        }
+                    )
+                }
+            }
+
+            val uuid = UUID.randomUUID()
+
+            // TODO: PackageSetting field encapsulation to move to whenever(name)
+            val mockPkgSetting = spyThrowOnUnmocked(
+                PackageSetting(
+                    TEST_PKG,
+                    TEST_PKG,
+                    File("/test"),
+                    null,
+                    null,
+                    null,
+                    null,
+                    1,
+                    0,
+                    0,
+                    0,
+                    null,
+                    null,
+                    null,
+                    uuid
+                )
+            ) {
+                whenever(getPkg()) { mockPkg }
+                whenever(domainSetId) { uuid }
+                whenever(userState) {
+                    SparseArray<PackageUserState>().apply {
+                        this[0] = PackageUserState()
+                    }
+                }
+            }
+
+            val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, DomainVerificationService> =
+                {
+                    val callingUidInt = AtomicInteger(-1)
+                    val callingUserIdInt = AtomicInteger(-1)
+                    Triple(
+                        callingUidInt, callingUserIdInt, DomainVerificationService(
+                            it,
+                            mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
+                            mockThrowOnUnmocked {
+                                whenever(
+                                    isChangeEnabled(
+                                        anyLong(),
+                                        any()
+                                    )
+                                ) { true }
+                            }).apply {
+                                setConnection(mockThrowOnUnmocked {
+                                    whenever(callingUid) { callingUidInt.get() }
+                                    whenever(callingUserId) { callingUserIdInt.get() }
+                                    whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting }
+                                    whenever(getPackageLocked(TEST_PKG)) { mockPkg }
+                                    whenever(schedule(anyInt(), any()))
+                                    whenever(scheduleWriteSettings())
+                                })
+                        }
+                    )
+                }
+
+            fun enforcer(
+                type: Type,
+                name: String,
+                block: DomainVerificationEnforcer.(
+                    callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
+                ) -> Unit
+            ) = Params(
+                type,
+                makeEnforcer,
+                name
+            ) { enforcer, callingUid, callingUserId, userId, proxy ->
+                enforcer.block(callingUid, callingUserId, userId, proxy)
+            }
+
+            fun service(
+                type: Type,
+                name: String,
+                block: DomainVerificationService.(
+                    callingUid: Int, callingUserId: Int, userId: Int
+                ) -> Unit
+            ) = Params(
+                type,
+                makeService,
+                name
+            ) { uidAndUserIdAndService, callingUid, callingUserId, userId, proxy ->
+                val (callingUidInt, callingUserIdInt, service) = uidAndUserIdAndService
+                callingUidInt.set(callingUid)
+                callingUserIdInt.set(callingUserId)
+                service.setProxy(proxy)
+                service.addPackage(mockPkgSetting)
+                service.block(callingUid, callingUserId, userId)
+            }
+
+            return arrayOf(
+                enforcer(Type.INTERNAL, "internal") { callingUid, _, _, _ ->
+                    assertInternal(callingUid)
+                },
+                enforcer(Type.QUERENT, "approvedQuerent") { callingUid, _, _, proxy ->
+                    assertApprovedQuerent(callingUid, proxy)
+                },
+                enforcer(Type.VERIFIER, "approvedVerifier") { callingUid, _, _, proxy ->
+                    assertApprovedVerifier(callingUid, proxy)
+                },
+                enforcer(
+                    Type.SELECTOR,
+                    "approvedUserSelector"
+                ) { callingUid, callingUserId, userId, _ ->
+                    assertApprovedUserSelector(callingUid, callingUserId, userId)
+                },
+
+                service(Type.INTERNAL, "setStatusInternalPackageName") { _, _, _ ->
+                    setDomainVerificationStatusInternal(
+                        TEST_PKG,
+                        DomainVerificationManager.STATE_SUCCESS,
+                        ArraySet(setOf("example.com"))
+                    )
+                },
+                service(Type.INTERNAL, "setUserSelectionInternal") { _, _, userId ->
+                    setDomainVerificationUserSelectionInternal(
+                        userId,
+                        TEST_PKG,
+                        false,
+                        ArraySet(setOf("example.com"))
+                    )
+                },
+                service(Type.INTERNAL, "verifyPackages") { _, _, _ ->
+                    verifyPackages(listOf(TEST_PKG), true)
+                },
+                service(Type.INTERNAL, "clearState") { _, _, _ ->
+                    clearDomainVerificationState(listOf(TEST_PKG))
+                },
+                service(Type.INTERNAL, "clearUserSelections") { _, _, userId ->
+                    clearUserSelections(listOf(TEST_PKG), userId)
+                },
+                service(Type.VERIFIER, "getPackageNames") { _, _, _ ->
+                    validVerificationPackageNames
+                },
+                service(Type.QUERENT, "getInfo") { _, _, _ ->
+                    getDomainVerificationInfo(TEST_PKG)
+                },
+                service(Type.VERIFIER, "setStatus") { _, _, _ ->
+                    setDomainVerificationStatus(
+                        uuid,
+                        setOf("example.com"),
+                        DomainVerificationManager.STATE_SUCCESS
+                    )
+                },
+                service(Type.VERIFIER, "setStatusInternalUid") { callingUid, _, _ ->
+                    setDomainVerificationStatusInternal(
+                        callingUid,
+                        uuid,
+                        setOf("example.com"),
+                        DomainVerificationManager.STATE_SUCCESS
+                    )
+                },
+                service(Type.SELECTOR, "setLinkHandlingAllowed") { _, _, _ ->
+                    setDomainVerificationLinkHandlingAllowed(TEST_PKG, true)
+                },
+                service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { _, _, userId ->
+                    setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, userId)
+                },
+                service(Type.SELECTOR, "getUserSelection") { _, _, _ ->
+                    getDomainVerificationUserSelection(TEST_PKG)
+                },
+                service(Type.SELECTOR_USER, "getUserSelectionUserId") { _, _, userId ->
+                    getDomainVerificationUserSelection(TEST_PKG, userId)
+                },
+                service(Type.SELECTOR, "setUserSelection") { _, _, _ ->
+                    setDomainVerificationUserSelection(uuid, setOf("example.com"), true)
+                },
+                service(Type.SELECTOR_USER, "setUserSelectionUserId") { _, _, userId ->
+                    setDomainVerificationUserSelection(uuid, setOf("example.com"), true, userId)
+                },
+            )
+        }
+
+        data class Params<T : Any>(
+            val type: Type,
+            val construct: (context: Context) -> T,
+            val name: String,
+            private val method: (
+                T, callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy
+            ) -> Unit
+        ) {
+            override fun toString() = "${type}_$name"
+
+            fun runMethod(
+                target: Any,
+                callingUid: Int,
+                callingUserId: Int,
+                userId: Int,
+                proxy: DomainVerificationProxy
+            ) {
+                @Suppress("UNCHECKED_CAST")
+                method(target as T, callingUid, callingUserId, userId, proxy)
+            }
+        }
+    }
+
+    @Parameterized.Parameter(0)
+    lateinit var params: Params<*>
+
+    private val proxy: DomainVerificationProxy = mockThrowOnUnmocked {
+        whenever(isCallerVerifier(VERIFIER_UID)) { true }
+        whenever(isCallerVerifier(NON_VERIFIER_UID)) { false }
+        whenever(sendBroadcastForPackages(any()))
+    }
+
+    @Test
+    fun verify() {
+        when (params.type) {
+            Type.INTERNAL -> internal()
+            Type.QUERENT -> approvedQuerent()
+            Type.VERIFIER -> approvedVerifier()
+            Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false)
+            Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
+        }.run { /*exhaust*/ }
+    }
+
+    fun internal() {
+        val context: Context = mockThrowOnUnmocked()
+        val target = params.construct(context)
+
+        INTERNAL_UIDS.forEach { runMethod(target, it) }
+        assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
+        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+    }
+
+    fun approvedQuerent() {
+        val allowUserSelection = AtomicBoolean(false)
+        val context: Context = mockThrowOnUnmocked {
+            whenever(
+                enforcePermission(
+                    eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
+                    anyInt(), anyInt(), anyString()
+                )
+            ) {
+                if (!allowUserSelection.get()) {
+                    throw SecurityException()
+                }
+            }
+        }
+        val target = params.construct(context)
+
+        INTERNAL_UIDS.forEach { runMethod(target, it) }
+
+        verifyNoMoreInteractions(context)
+
+        runMethod(target, VERIFIER_UID)
+        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+
+        allowUserSelection.set(true)
+
+        runMethod(target, NON_VERIFIER_UID)
+    }
+
+    fun approvedVerifier() {
+        val shouldThrow = AtomicBoolean(false)
+        val context: Context = mockThrowOnUnmocked {
+            whenever(
+                enforcePermission(
+                    eq(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT),
+                    anyInt(), anyInt(), anyString()
+                )
+            ) {
+                if (shouldThrow.get()) {
+                    throw SecurityException()
+                }
+            }
+        }
+        val target = params.construct(context)
+
+        INTERNAL_UIDS.forEach { runMethod(target, it) }
+
+        verifyNoMoreInteractions(context)
+
+        runMethod(target, VERIFIER_UID)
+        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+
+        shouldThrow.set(true)
+
+        assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) }
+        assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) }
+    }
+
+    fun approvedUserSelector(verifyCrossUser: Boolean) {
+        val allowUserSelection = AtomicBoolean(true)
+        val allowInteractAcrossUsers = AtomicBoolean(true)
+        val context: Context = mockThrowOnUnmocked {
+            whenever(
+                enforcePermission(
+                    eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION),
+                    anyInt(), anyInt(), anyString()
+                )
+            ) {
+                if (!allowUserSelection.get()) {
+                    throw SecurityException()
+                }
+            }
+            whenever(
+                enforcePermission(
+                    eq(android.Manifest.permission.INTERACT_ACROSS_USERS),
+                    anyInt(), anyInt(), anyString()
+                )
+            ) {
+                if (!allowInteractAcrossUsers.get()) {
+                    throw SecurityException()
+                }
+            }
+        }
+        val target = params.construct(context)
+
+        fun runEachTestCaseWrapped(
+            callingUserId: Int,
+            targetUserId: Int,
+            block: (testCase: () -> Unit) -> Unit = { it.invoke() }
+        ) {
+            block { runMethod(target, VERIFIER_UID, callingUserId, targetUserId) }
+            block { runMethod(target, NON_VERIFIER_UID, callingUserId, targetUserId) }
+        }
+
+        val callingUserId = 0
+        val notCallingUserId = 1
+
+        runEachTestCaseWrapped(callingUserId, callingUserId)
+        if (verifyCrossUser) {
+            runEachTestCaseWrapped(callingUserId, notCallingUserId)
+        }
+
+        allowInteractAcrossUsers.set(false)
+
+        runEachTestCaseWrapped(callingUserId, callingUserId)
+
+        if (verifyCrossUser) {
+            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
+                assertThrows(SecurityException::class.java, it)
+            }
+        }
+
+        allowUserSelection.set(false)
+
+        runEachTestCaseWrapped(callingUserId, callingUserId) {
+            assertThrows(SecurityException::class.java, it)
+        }
+        if (verifyCrossUser) {
+            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
+                assertThrows(SecurityException::class.java, it)
+            }
+        }
+
+        allowInteractAcrossUsers.set(true)
+
+        runEachTestCaseWrapped(callingUserId, callingUserId) {
+            assertThrows(SecurityException::class.java, it)
+        }
+        if (verifyCrossUser) {
+            runEachTestCaseWrapped(callingUserId, notCallingUserId) {
+                assertThrows(SecurityException::class.java, it)
+            }
+        }
+    }
+
+    private fun runMethod(target: Any, callingUid: Int, callingUserId: Int = 0, userId: Int = 0) {
+        params.runMethod(target, callingUid, callingUserId, userId, proxy)
+    }
+
+    enum class Type {
+        // System/shell only
+        INTERNAL,
+
+        // INTERNAL || domain verification agent || user setting permission holder
+        QUERENT,
+
+        // INTERNAL || domain verification agent
+        VERIFIER,
+
+        // Holding the user setting permission
+        SELECTOR,
+
+        // Holding the user setting permission, but targeting cross user
+        SELECTOR_USER
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
new file mode 100644
index 0000000..9a3bd99
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationLegacySettingsTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 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.pm.test.verify.domain
+
+import android.content.pm.IntentFilterVerificationInfo
+import android.content.pm.PackageManager
+import android.util.ArraySet
+import com.android.server.pm.verify.domain.DomainVerificationLegacySettings
+import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.readXml
+import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.writeXml
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+
+class DomainVerificationLegacySettingsTest {
+
+    @Rule
+    @JvmField
+    val tempFolder = TemporaryFolder()
+
+    @Test
+    fun writeAndReadBackNormal() {
+        val settings = DomainVerificationLegacySettings().apply {
+            add(
+                "com.test.one",
+                IntentFilterVerificationInfo(
+                    "com.test.one",
+                    ArraySet(setOf("example1.com", "example2.com"))
+                ).apply {
+                    status = PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK
+                }
+            )
+            add(
+                "com.test.one",
+                0, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+            )
+            add(
+                "com.test.one",
+                10, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER
+            )
+
+            add(
+                "com.test.two",
+                IntentFilterVerificationInfo(
+                    "com.test.two",
+                    ArraySet(setOf("example3.com"))
+                )
+            )
+
+            add(
+                "com.test.three",
+                11, PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+            )
+        }
+
+
+        val file = tempFolder.newFile().writeXml(settings::writeSettings)
+        val newSettings = file.readXml {
+            DomainVerificationLegacySettings().apply {
+                readSettings(it)
+            }
+        }
+
+        val xml = file.readText()
+
+        // Legacy migrated settings doesn't bother writing the legacy verification info
+        assertWithMessage(xml).that(newSettings.remove("com.test.one")).isNull()
+        assertWithMessage(xml).that(newSettings.getUserState("com.test.one", 0))
+            .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+        assertWithMessage(xml).that(newSettings.getUserState("com.test.one", 10))
+            .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)
+
+        val firstUserStates = newSettings.getUserStates("com.test.one")
+        assertWithMessage(xml).that(firstUserStates).isNotNull()
+        assertWithMessage(xml).that(firstUserStates!!.size()).isEqualTo(2)
+        assertWithMessage(xml).that(firstUserStates[0])
+            .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+        assertWithMessage(xml).that(firstUserStates[10])
+            .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER)
+
+        assertWithMessage(xml).that(newSettings.remove("com.test.two")).isNull()
+        assertWithMessage(xml).that(newSettings.getUserStates("com.test.two")).isNull()
+
+        assertWithMessage(xml).that(newSettings.remove("com.test.three")).isNull()
+        assertWithMessage(xml).that(newSettings.getUserState("com.test.three", 11))
+            .isEqualTo(PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS)
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
new file mode 100644
index 0000000..a76d8ce
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.pm.test.verify.domain
+
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationUserSelection
+import com.android.server.pm.verify.domain.DomainVerificationPersistence
+
+operator fun <F> android.util.Pair<F, *>.component1() = first
+operator fun <S> android.util.Pair<*, S>.component2() = second
+
+operator fun DomainVerificationRequest.component1() = packageNames
+
+operator fun DomainVerificationInfo.component1() = identifier
+operator fun DomainVerificationInfo.component2() = packageName
+operator fun DomainVerificationInfo.component3() = hostToStateMap
+
+operator fun DomainVerificationUserSelection.component1() = identifier
+operator fun DomainVerificationUserSelection.component2() = packageName
+operator fun DomainVerificationUserSelection.component3() = user
+operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed
+operator fun DomainVerificationUserSelection.component5() = hostToUserSelectionMap
+
+operator fun DomainVerificationPersistence.ReadResult.component1() = active
+operator fun DomainVerificationPersistence.ReadResult.component2() = restored
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
new file mode 100644
index 0000000..a76152c
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPersistenceTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2020 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.pm.test.verify.domain
+
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.util.ArrayMap
+import android.util.TypedXmlPullParser
+import android.util.TypedXmlSerializer
+import android.util.Xml
+import com.android.server.pm.verify.domain.DomainVerificationPersistence
+import com.android.server.pm.verify.domain.models.DomainVerificationPkgState
+import com.android.server.pm.verify.domain.models.DomainVerificationStateMap
+import com.android.server.pm.verify.domain.models.DomainVerificationUserState
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import java.io.File
+import java.nio.charset.StandardCharsets
+import java.util.UUID
+
+class DomainVerificationPersistenceTest {
+
+    companion object {
+        private val PKG_PREFIX = DomainVerificationPersistenceTest::class.java.`package`!!.name
+
+        internal fun File.writeXml(block: (serializer: TypedXmlSerializer) -> Unit) = apply {
+            outputStream().use {
+                // Explicitly use string based XML so it can printed in the test failure output
+                Xml.newFastSerializer()
+                    .apply {
+                        setOutput(it, StandardCharsets.UTF_8.name())
+                        startDocument(null, true)
+                        setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+                    }
+                    .apply(block)
+                    .endDocument()
+            }
+        }
+
+        internal fun <T> File.readXml(block: (parser: TypedXmlPullParser) -> T) =
+            inputStream().use {
+                block(Xml.resolvePullParser(it))
+            }
+    }
+
+    @Rule
+    @JvmField
+    val tempFolder = TemporaryFolder()
+
+    @Test
+    fun writeAndReadBackNormal() {
+        val attached = DomainVerificationStateMap<DomainVerificationPkgState>().apply {
+            mockPkgState(0).let { put(it.packageName, it.id, it) }
+            mockPkgState(1).let { put(it.packageName, it.id, it) }
+        }
+        val pending = ArrayMap<String, DomainVerificationPkgState>().apply {
+            mockPkgState(2).let { put(it.packageName, it) }
+            mockPkgState(3).let { put(it.packageName, it) }
+        }
+        val restored = ArrayMap<String, DomainVerificationPkgState>().apply {
+            mockPkgState(4).let { put(it.packageName, it) }
+            mockPkgState(5).let { put(it.packageName, it) }
+        }
+
+        val file = tempFolder.newFile().writeXml {
+            DomainVerificationPersistence.writeToXml(it, attached, pending, restored)
+        }
+
+        val xml = file.readText()
+
+        val (readActive, readRestored) = file.readXml {
+            DomainVerificationPersistence.readFromXml(it)
+        }
+
+        assertWithMessage(xml).that(readActive.values)
+            .containsExactlyElementsIn(attached.values() + pending.values)
+        assertWithMessage(xml).that(readRestored.values).containsExactlyElementsIn(restored.values)
+    }
+
+    @Test
+    fun readMalformed() {
+        val stateZero = mockEmptyPkgState(0).apply {
+            stateMap["example.com"] = DomainVerificationManager.STATE_SUCCESS
+            stateMap["example.org"] = DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED
+
+            // A domain without a written state falls back to default
+            stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE
+
+            userSelectionStates[1] = DomainVerificationUserState(1).apply {
+                addHosts(setOf("example-user1.com", "example-user1.org"))
+                isDisallowLinkHandling = false
+            }
+        }
+        val stateOne = mockEmptyPkgState(1).apply {
+            // It's valid to have a user selection without any autoVerify domains
+            userSelectionStates[1] = DomainVerificationUserState(1).apply {
+                addHosts(setOf("example-user1.com", "example-user1.org"))
+                isDisallowLinkHandling = true
+            }
+        }
+
+        // Also valid to have neither autoVerify domains nor any active user states
+        val stateTwo = mockEmptyPkgState(2, hasAutoVerifyDomains = false)
+
+        // language=XML
+        val xml = """
+            <?xml?>
+            <domain-verifications>
+                <active>
+                    <package-state
+                        packageName="${stateZero.packageName}"
+                        id="${stateZero.id}"
+                        >
+                        <state>
+                            <domain name="duplicate-takes-last.com" state="1"/>
+                        </state>
+                    </package-state>
+                    <package-state
+                        packageName="${stateZero.packageName}"
+                        id="${stateZero.id}"
+                        hasAutoVerifyDomains="true"
+                        >
+                        <state>
+                            <domain name="example.com" state="${DomainVerificationManager.STATE_SUCCESS}"/>
+                            <domain name="example.org" state="${DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
+                            <not-domain name="not-domain.com" state="1"/>
+                            <domain name="missing-state.com"/>
+                        </state>
+                        <user-states>
+                            <user-state userId="1" disallowLinkHandling="false">
+                                <enabled-hosts>
+                                    <host name="example-user1.com"/>
+                                    <not-host name="not-host.com"/>
+                                    <host/>
+                                </enabled-hosts>
+                                <enabled-hosts>
+                                    <host name="example-user1.org"/>
+                                </enabled-hosts>
+                                <enabled-hosts/>
+                            </user-state>
+                            <user-state>
+                                <enabled-hosts>
+                                    <host name="no-user-id.com"/>
+                                </enabled-hosts>
+                            </user-state>
+                        </user-states>
+                    </package-state>
+                </active>
+                <not-active/>
+                <restored>
+                    <package-state
+                        packageName="${stateOne.packageName}"
+                        id="${stateOne.id}"
+                        hasAutoVerifyDomains="true"
+                        >
+                        <state/>
+                        <user-states>
+                            <user-state userId="1" disallowLinkHandling="true">
+                                <enabled-hosts>
+                                    <host name="example-user1.com"/>
+                                    <host name="example-user1.org"/>
+                                </enabled-hosts>
+                            </user-state>
+                        </user-states>
+                    </package-state>
+                    <package-state packageName="${stateTwo.packageName}"/>
+                    <package-state id="${stateTwo.id}"/>
+                    <package-state
+                        packageName="${stateTwo.packageName}"
+                        id="${stateTwo.id}"
+                        hasAutoVerifyDomains="false"
+                        >
+                        <state/>
+                        <user-states/>
+                    </package-state>
+                </restore>
+                <not-restored/>
+            </domain-verifications>
+        """.trimIndent()
+
+        val (active, restored) = DomainVerificationPersistence
+            .readFromXml(Xml.resolvePullParser(xml.byteInputStream()))
+
+        assertThat(active.values).containsExactly(stateZero)
+        assertThat(restored.values).containsExactly(stateOne, stateTwo)
+    }
+
+    private fun mockEmptyPkgState(
+        id: Int,
+        hasAutoVerifyDomains: Boolean = true
+    ): DomainVerificationPkgState {
+        val pkgName = pkgName(id)
+        val domainSetId = UUID(0L, id.toLong())
+        return DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains)
+    }
+
+    private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply {
+        stateMap["$packageName.com"] = id
+        userSelectionStates[id] = DomainVerificationUserState(id).apply {
+            addHosts(setOf("$packageName-user.com"))
+            isDisallowLinkHandling = true
+        }
+    }
+
+    private fun pkgName(id: Int) = "${PKG_PREFIX}.pkg$id"
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
new file mode 100644
index 0000000..db541f6
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationProxyTest.kt
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2021 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.pm.test.verify.domain
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationRequest
+import android.content.pm.verify.domain.DomainVerificationInfo
+import android.content.pm.verify.domain.DomainVerificationState
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.ArraySet
+import com.android.server.DeviceIdleInternal
+import com.android.server.pm.verify.domain.DomainVerificationCollector
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1
+import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.UUID
+
+@Suppress("DEPRECATION")
+class DomainVerificationProxyTest {
+
+    companion object {
+        private const val TEST_PKG_NAME_ONE = "com.test.pkg.one"
+        private const val TEST_PKG_NAME_TWO = "com.test.pkg.two"
+        private const val TEST_PKG_NAME_TARGET_ONE = "com.test.target.one"
+        private const val TEST_PKG_NAME_TARGET_TWO = "com.test.target.two"
+        private const val TEST_CALLING_UID_ACCEPT = 40
+        private const val TEST_CALLING_UID_REJECT = 41
+        private val TEST_UUID_ONE = UUID.fromString("f7fbb7dd-7b5f-4609-a95e-c6c7765fb9cd")
+        private val TEST_UUID_TWO = UUID.fromString("4a09b361-a967-43ac-9d18-07a385dff740")
+    }
+
+    private val componentOne = ComponentName(TEST_PKG_NAME_ONE, ".ReceiverOne")
+    private val componentTwo = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverTwo")
+    private val componentThree = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverThree")
+
+    private lateinit var context: Context
+    private lateinit var manager: DomainVerificationManagerInternal
+    private lateinit var collector: DomainVerificationCollector
+
+    // Must be declared as field to support generics
+    @Captor
+    lateinit var hostCaptor: ArgumentCaptor<Set<String>>
+
+    @Before
+    fun setUpMocks() {
+        MockitoAnnotations.initMocks(this)
+        context = mockThrowOnUnmocked {
+            whenever(sendBroadcastAsUser(any(), any(), any(), any<Bundle>()))
+            whenever(
+                enforceCallingOrSelfPermission(
+                    eq(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT),
+                    anyString()
+                )
+            )
+        }
+        manager = mockThrowOnUnmocked {
+            whenever(getDomainVerificationInfoId(any())) {
+                when (val pkgName = arguments[0] as String) {
+                    TEST_PKG_NAME_TARGET_ONE -> TEST_UUID_ONE
+                    TEST_PKG_NAME_TARGET_TWO -> TEST_UUID_TWO
+                    else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+                }
+            }
+            whenever(getDomainVerificationInfo(anyString())) {
+                when (val pkgName = arguments[0] as String) {
+                    TEST_PKG_NAME_TARGET_ONE -> DomainVerificationInfo(
+                        TEST_UUID_ONE, pkgName, mapOf(
+                            "example1.com" to DomainVerificationManager.STATE_NO_RESPONSE,
+                            "example2.com" to DomainVerificationManager.STATE_NO_RESPONSE
+                        )
+                    )
+                    TEST_PKG_NAME_TARGET_TWO -> DomainVerificationInfo(
+                        TEST_UUID_TWO, pkgName, mapOf(
+                            "example3.com" to DomainVerificationManager.STATE_NO_RESPONSE,
+                            "example4.com" to DomainVerificationManager.STATE_NO_RESPONSE
+                        )
+                    )
+                    else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+                }
+            }
+            whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt()))
+        }
+        collector = mockThrowOnUnmocked {
+            whenever(collectAutoVerifyDomains(any())) {
+                when (val pkgName = (arguments[0] as AndroidPackage).packageName) {
+                    TEST_PKG_NAME_TARGET_ONE -> ArraySet(setOf("example1.com", "example2.com"))
+                    TEST_PKG_NAME_TARGET_TWO -> ArraySet(setOf("example3.com", "example4.com"))
+                    else -> throw IllegalArgumentException("Unexpected package name $pkgName")
+                }
+            }
+        }
+    }
+
+    @Test
+    fun isCallerVerifierV1() {
+        val connection = mockConnection()
+        val proxyV1 = DomainVerificationProxy.makeProxy<Connection>(
+            componentOne, null, context,
+            manager, collector, connection
+        )
+
+        assertThat(proxyV1.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+        verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)
+        verifyNoMoreInteractions(connection)
+        clearInvocations(connection)
+
+        assertThat(proxyV1.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+        verify(connection).isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_ONE)
+        verifyNoMoreInteractions(connection)
+    }
+
+    @Test
+    fun isCallerVerifierV2() {
+        val connection = mockConnection()
+        val proxyV2 = DomainVerificationProxy.makeProxy<Connection>(
+            null, componentTwo, context,
+            manager, collector, connection
+        )
+
+        assertThat(proxyV2.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+        verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)
+        verifyNoMoreInteractions(connection)
+        clearInvocations(connection)
+
+        assertThat(proxyV2.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+        verify(connection).isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_TWO)
+        verifyNoMoreInteractions(connection)
+    }
+
+    @Test
+    fun isCallerVerifierBoth() {
+        val connection = mockConnection()
+        val proxyBoth = DomainVerificationProxy.makeProxy<Connection>(
+            componentTwo, componentThree,
+            context, manager, collector, connection
+        )
+
+        // The combined proxy should only ever call v2 when it succeeds
+        assertThat(proxyBoth.isCallerVerifier(TEST_CALLING_UID_ACCEPT)).isTrue()
+        verify(connection).isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)
+        verifyNoMoreInteractions(connection)
+        clearInvocations(connection)
+
+        val callingUidCaptor = ArgumentCaptor.forClass(Int::class.java)
+
+        // But will call both when v2 fails
+        assertThat(proxyBoth.isCallerVerifier(TEST_CALLING_UID_REJECT)).isFalse()
+        verify(connection, times(2))
+            .isCallerPackage(callingUidCaptor.capture(), eq(TEST_PKG_NAME_TWO))
+        verifyNoMoreInteractions(connection)
+
+        assertThat(callingUidCaptor.allValues.toSet()).containsExactly(TEST_CALLING_UID_REJECT)
+    }
+
+    @Test
+    fun differentPackagesResolvesOnlyV2() {
+        assertThat(DomainVerificationProxy.makeProxy<Connection>(
+            componentOne, componentTwo,
+            context, manager, collector, mockConnection()
+        )).isInstanceOf(DomainVerificationProxyV2::class.java)
+    }
+
+    private fun prepareProxyV1(): ProxyV1Setup {
+        val messages = mutableListOf<Pair<Int, Any?>>()
+        val connection = mockConnection {
+            whenever(schedule(anyInt(), any())) {
+                messages.add((arguments[0] as Int) to arguments[1])
+            }
+        }
+
+        val proxy = DomainVerificationProxy.makeProxy<Connection>(
+            componentOne,
+            null,
+            context,
+            manager,
+            collector,
+            connection
+        )
+        return ProxyV1Setup(messages, connection, proxy)
+    }
+
+    @Test
+    fun sendBroadcastForPackagesV1() {
+        val (messages, _, proxy) = prepareProxyV1()
+
+        proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+        messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+        verify(context, times(2)).sendBroadcastAsUser(
+            intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+        )
+        verifyNoMoreInteractions(context)
+
+        val intents = intentCaptor.allValues
+        assertThat(intents).hasSize(2)
+        intents.forEach {
+            assertThat(it.action).isEqualTo(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
+            assertThat(it.getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME))
+                .isEqualTo(IntentFilter.SCHEME_HTTPS)
+            assertThat(it.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1))
+                .isNotEqualTo(-1)
+        }
+
+        intents[0].apply {
+            assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME))
+                .isEqualTo(TEST_PKG_NAME_TARGET_ONE)
+            assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS))
+                .isEqualTo("example1.com example2.com")
+        }
+
+        intents[1].apply {
+            assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME))
+                .isEqualTo(TEST_PKG_NAME_TARGET_TWO)
+            assertThat(getStringExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS))
+                .isEqualTo("example3.com example4.com")
+        }
+    }
+
+    private fun prepareProxyOnIntentFilterVerifiedV1(): Pair<ProxyV1Setup, Pair<Int, Int>> {
+        val (messages, connection, proxy) = prepareProxyV1()
+
+        proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+        messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+        messages.clear()
+
+        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+        verify(context, times(2)).sendBroadcastAsUser(
+            intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+        )
+
+        val verificationIds = intentCaptor.allValues.map {
+            it.getIntExtra(PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID, -1)
+        }
+
+        assertThat(verificationIds).doesNotContain(-1)
+
+        return ProxyV1Setup(messages, connection, proxy) to
+                (verificationIds[0] to verificationIds[1])
+    }
+
+    @Test
+    fun proxyOnIntentFilterVerifiedFullSuccessV1() {
+        val setup = prepareProxyOnIntentFilterVerifiedV1()
+        val (messages, connection, proxy) = setup.first
+        val (idOne, idTwo) = setup.second
+
+        DomainVerificationProxyV1.queueLegacyVerifyResult(
+            context,
+            connection,
+            idOne,
+            PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+            emptyList(),
+            TEST_CALLING_UID_ACCEPT
+        )
+
+        DomainVerificationProxyV1.queueLegacyVerifyResult(
+            context,
+            connection,
+            idTwo,
+            PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+            emptyList(),
+            TEST_CALLING_UID_ACCEPT
+        )
+
+        assertThat(messages).hasSize(2)
+        messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+        val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+
+        @Suppress("UNCHECKED_CAST")
+        verify(manager, times(2)).setDomainVerificationStatusInternal(
+            eq(TEST_CALLING_UID_ACCEPT),
+            idCaptor.capture(),
+            hostCaptor.capture(),
+            eq(DomainVerificationManager.STATE_SUCCESS)
+        )
+
+        assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
+
+        assertThat(hostCaptor.allValues.toSet()).containsExactly(
+            setOf("example1.com", "example2.com"),
+            setOf("example3.com", "example4.com")
+        )
+    }
+
+    @Test
+    fun proxyOnIntentFilterVerifiedPartialSuccessV1() {
+        val setup = prepareProxyOnIntentFilterVerifiedV1()
+        val (messages, connection, proxy) = setup.first
+        val (idOne, idTwo) = setup.second
+
+        DomainVerificationProxyV1.queueLegacyVerifyResult(
+            context,
+            connection,
+            idOne,
+            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+            listOf("example1.com"),
+            TEST_CALLING_UID_ACCEPT
+        )
+
+        DomainVerificationProxyV1.queueLegacyVerifyResult(
+            context,
+            connection,
+            idTwo,
+            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+            listOf("example3.com"),
+            TEST_CALLING_UID_ACCEPT
+        )
+
+        messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+        val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+        val stateCaptor = ArgumentCaptor.forClass(Int::class.java)
+
+        @Suppress("UNCHECKED_CAST")
+        verify(manager, times(4)).setDomainVerificationStatusInternal(
+            eq(TEST_CALLING_UID_ACCEPT),
+            idCaptor.capture(),
+            hostCaptor.capture(),
+            stateCaptor.capture()
+        )
+
+        assertThat(idCaptor.allValues)
+            .containsExactly(TEST_UUID_ONE, TEST_UUID_ONE, TEST_UUID_TWO, TEST_UUID_TWO)
+
+        val hostToStates: Map<Set<*>, Int> = hostCaptor.allValues.zip(stateCaptor.allValues).toMap()
+        assertThat(hostToStates).isEqualTo(mapOf(
+            setOf("example1.com") to DomainVerificationState.STATE_LEGACY_FAILURE,
+            setOf("example2.com") to DomainVerificationState.STATE_SUCCESS,
+            setOf("example3.com") to DomainVerificationState.STATE_LEGACY_FAILURE,
+            setOf("example4.com") to DomainVerificationState.STATE_SUCCESS,
+        ))
+    }
+
+    @Test
+    fun proxyOnIntentFilterVerifiedFailureV1() {
+        val setup = prepareProxyOnIntentFilterVerifiedV1()
+        val (messages, connection, proxy) = setup.first
+        val (idOne, idTwo) = setup.second
+
+        DomainVerificationProxyV1.queueLegacyVerifyResult(
+            context,
+            connection,
+            idOne,
+            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+            listOf("example1.com", "example2.com"),
+            TEST_CALLING_UID_ACCEPT
+        )
+
+        DomainVerificationProxyV1.queueLegacyVerifyResult(
+            context,
+            connection,
+            idTwo,
+            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+            listOf("example3.com", "example4.com"),
+            TEST_CALLING_UID_ACCEPT
+        )
+
+        messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+        val idCaptor = ArgumentCaptor.forClass(UUID::class.java)
+
+        @Suppress("UNCHECKED_CAST")
+        verify(manager, times(2)).setDomainVerificationStatusInternal(
+            eq(TEST_CALLING_UID_ACCEPT),
+            idCaptor.capture(),
+            hostCaptor.capture(),
+            eq(DomainVerificationState.STATE_LEGACY_FAILURE)
+        )
+
+        assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)
+
+        assertThat(hostCaptor.allValues.toSet()).containsExactly(
+            setOf("example1.com", "example2.com"),
+            setOf("example3.com", "example4.com")
+        )
+    }
+
+    @Test
+    fun sendBroadcastForPackagesV2() {
+        val componentTwo = ComponentName(TEST_PKG_NAME_TWO, ".ReceiverOne")
+        val messages = mutableListOf<Pair<Int, Any?>>()
+
+        val connection = mockConnection {
+            whenever(schedule(anyInt(), any())) {
+                messages.add((arguments[0] as Int) to arguments[1])
+            }
+        }
+
+        val proxy = DomainVerificationProxy.makeProxy<Connection>(
+            null,
+            componentTwo,
+            context,
+            manager,
+            collector,
+            connection
+        )
+
+        proxy.sendBroadcastForPackages(setOf(TEST_PKG_NAME_TARGET_ONE, TEST_PKG_NAME_TARGET_TWO))
+
+        messages.forEach { (code, value) -> proxy.runMessage(code, value) }
+
+        val intentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+
+        verify(context).sendBroadcastAsUser(
+            intentCaptor.capture(), eq(UserHandle.SYSTEM), isNull(), any<Bundle>()
+        )
+        verifyNoMoreInteractions(context)
+
+        val intents = intentCaptor.allValues
+        assertThat(intents).hasSize(1)
+        intents.single().apply {
+            assertThat(this.action).isEqualTo(Intent.ACTION_DOMAINS_NEED_VERIFICATION)
+            val request: DomainVerificationRequest? =
+                getParcelableExtra(DomainVerificationManager.EXTRA_VERIFICATION_REQUEST)
+            assertThat(request?.packageNames).containsExactly(
+                TEST_PKG_NAME_TARGET_ONE,
+                TEST_PKG_NAME_TARGET_TWO
+            )
+        }
+    }
+
+    private fun mockConnection(block: Connection.() -> Unit = {}) =
+        mockThrowOnUnmocked<Connection> {
+            whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_ONE)) { true }
+            whenever(isCallerPackage(TEST_CALLING_UID_ACCEPT, TEST_PKG_NAME_TWO)) { true }
+            whenever(isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_ONE)) { false }
+            whenever(isCallerPackage(TEST_CALLING_UID_REJECT, TEST_PKG_NAME_TWO)) { false }
+            whenever(getPackage(anyString())) { mockPkg(arguments[0] as String) }
+            whenever(powerSaveTempWhitelistAppDuration) { 1000 }
+            whenever(deviceIdleInternal) {
+                mockThrowOnUnmocked<DeviceIdleInternal> {
+                    whenever(
+                        addPowerSaveTempWhitelistApp(
+                            anyInt(), anyString(), anyLong(), anyInt(),
+                            anyBoolean(), anyString()
+                        )
+                    )
+                }
+            }
+            block()
+        }
+
+    private fun mockPkg(pkgName: String): AndroidPackage {
+        return mockThrowOnUnmocked { whenever(packageName) { pkgName } }
+    }
+
+    private data class ProxyV1Setup(
+        val messages: MutableList<Pair<Int, Any?>>,
+        val connection: Connection,
+        val proxy: DomainVerificationProxy
+    )
+
+    interface Connection : DomainVerificationProxyV1.Connection,
+        DomainVerificationProxyV2.Connection
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index a691a8d..607fb47 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -271,8 +271,7 @@
         verify(mMockIActivityManager).registerUidObserver(
                 uidObserverArgumentCaptor.capture(),
                 eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
-                        | ActivityManager.UID_OBSERVER_ACTIVE
-                        | ActivityManager.UID_OBSERVER_PROCSTATE),
+                        | ActivityManager.UID_OBSERVER_ACTIVE),
                 eq(ActivityManager.PROCESS_STATE_UNKNOWN),
                 isNull());
         verify(mMockIAppOpsService).startWatchingMode(
@@ -650,11 +649,6 @@
         assertFalse(instance.isUidActiveSynced(UID_2));
         assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
-
         mIUidObserver.onUidStateChanged(UID_2,
                 ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 0,
                 ActivityManager.PROCESS_CAPABILITY_NONE);
@@ -670,11 +664,6 @@
         assertFalse(instance.isUidActiveSynced(UID_2));
         assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertTrue(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
-
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0,
                 ActivityManager.PROCESS_CAPABILITY_NONE);
@@ -686,10 +675,6 @@
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
-        assertTrue(instance.isUidInForeground(UID_1));
-        assertTrue(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         mIUidObserver.onUidGone(UID_1, true);
 
         waitUntilMainHandlerDrain();
@@ -699,10 +684,6 @@
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertTrue(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         mIUidObserver.onUidIdle(UID_2, true);
 
         waitUntilMainHandlerDrain();
@@ -712,10 +693,6 @@
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0,
                 ActivityManager.PROCESS_CAPABILITY_NONE);
@@ -727,10 +704,6 @@
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
-        assertTrue(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0,
                 ActivityManager.PROCESS_CAPABILITY_NONE);
@@ -746,10 +719,6 @@
         assertFalse(instance.isUidActiveSynced(UID_2));
         assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
 
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
         // The result from AMI.isUidActive() only affects isUidActiveSynced().
         when(mMockIActivityManagerInternal.isUidActive(anyInt())).thenReturn(true);
 
@@ -760,11 +729,6 @@
         assertTrue(instance.isUidActiveSynced(UID_1));
         assertTrue(instance.isUidActiveSynced(UID_2));
         assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
-
-        assertFalse(instance.isUidInForeground(UID_1));
-        assertFalse(instance.isUidInForeground(UID_2));
-        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
-
     }
 
     @Test
@@ -1480,7 +1444,6 @@
         callStart(instance);
 
         instance.mActiveUids.put(UID_1, true);
-        instance.mForegroundUids.put(UID_2, true);
         instance.mRunAnyRestrictedPackages.add(Pair.create(UID_1, PACKAGE_1));
         instance.mExemptedBucketPackages.add(UserHandle.getUserId(UID_2), PACKAGE_2);
 
@@ -1493,7 +1456,6 @@
         mReceiver.onReceive(mMockContext, packageRemoved);
 
         assertEquals(1, instance.mActiveUids.size());
-        assertEquals(1, instance.mForegroundUids.size());
         assertEquals(1, instance.mRunAnyRestrictedPackages.size());
         assertEquals(1, instance.mExemptedBucketPackages.size());
 
@@ -1506,7 +1468,6 @@
         mReceiver.onReceive(mMockContext, packageRemoved);
 
         assertEquals(1, instance.mActiveUids.size());
-        assertEquals(1, instance.mForegroundUids.size());
         assertEquals(1, instance.mRunAnyRestrictedPackages.size());
         assertEquals(1, instance.mExemptedBucketPackages.size());
 
@@ -1518,7 +1479,6 @@
         mReceiver.onReceive(mMockContext, packageRemoved);
 
         assertEquals(0, instance.mActiveUids.size());
-        assertEquals(1, instance.mForegroundUids.size());
         assertEquals(0, instance.mRunAnyRestrictedPackages.size());
         assertEquals(1, instance.mExemptedBucketPackages.size());
 
@@ -1530,7 +1490,6 @@
         mReceiver.onReceive(mMockContext, packageRemoved);
 
         assertEquals(0, instance.mActiveUids.size());
-        assertEquals(0, instance.mForegroundUids.size());
         assertEquals(0, instance.mRunAnyRestrictedPackages.size());
         assertEquals(0, instance.mExemptedBucketPackages.size());
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 7a970a1..1254df9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -44,14 +44,15 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
-import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
+import static com.android.server.alarm.AlarmManagerService.Constants.ALLOW_WHILE_IDLE_WINDOW;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;
 import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
 import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK;
 import static com.android.server.alarm.AlarmManagerService.TIME_CHANGED_MASK;
@@ -409,6 +410,12 @@
         return mockPi;
     }
 
+    private void setDeviceConfigInt(String key, int val) {
+        mDeviceConfigKeys.add(key);
+        doReturn(val).when(mDeviceConfigProperties).getInt(eq(key), anyInt());
+        mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
+    }
+
     private void setDeviceConfigLong(String key, long val) {
         mDeviceConfigKeys.add(key);
         doReturn(val).when(mDeviceConfigProperties).getLong(eq(key), anyLong());
@@ -430,10 +437,12 @@
         setDeviceConfigLong(KEY_MIN_INTERVAL, 0);
         mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]);
         mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]);
-        doReturn(8).when(mDeviceConfigProperties)
+        doReturn(50).when(mDeviceConfigProperties)
                 .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]), anyInt());
-        doReturn(5).when(mDeviceConfigProperties)
+        doReturn(35).when(mDeviceConfigProperties)
                 .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]), anyInt());
+        doReturn(20).when(mDeviceConfigProperties)
+                .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[FREQUENT_INDEX]), anyInt());
 
         mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
     }
@@ -496,15 +505,13 @@
         setDeviceConfigLong(KEY_MIN_FUTURITY, 5);
         setDeviceConfigLong(KEY_MIN_INTERVAL, 10);
         setDeviceConfigLong(KEY_MAX_INTERVAL, 15);
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, 20);
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, 25);
+        setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20);
         setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30);
         setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35);
         assertEquals(5, mService.mConstants.MIN_FUTURITY);
         assertEquals(10, mService.mConstants.MIN_INTERVAL);
         assertEquals(15, mService.mConstants.MAX_INTERVAL);
-        assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_SHORT_TIME);
-        assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_LONG_TIME);
+        assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA);
         assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION);
         assertEquals(35, mService.mConstants.LISTENER_TIMEOUT);
     }
@@ -1301,62 +1308,54 @@
     public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception {
         doReturn(0).when(mService).fuzzForDuration(anyLong());
 
-        final long awiDelayForTest = 23;
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, awiDelayForTest);
-
-        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1000,
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + ALLOW_WHILE_IDLE_WINDOW + 1000,
                 getNewMockPendingIntent());
         assertNotNull(mService.mPendingIdleUntil);
 
-        final long seedTrigger = mNowElapsedTest + 3;
-        final int numAlarms = 10;
-        final PendingIntent[] pis = new PendingIntent[numAlarms];
-        for (int i = 0; i < numAlarms; i++) {
-            pis[i] = getNewMockPendingIntent();
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, seedTrigger + i * i, pis[i], false);
-        }
-
-        long lastAwiDispatch = -1;
-        int i = 0;
-        while (i < numAlarms) {
-            final long nextDispatch = (lastAwiDispatch >= 0) ? (lastAwiDispatch + awiDelayForTest)
-                    : (seedTrigger + i * i);
-            assertEquals("Wrong allow-while-idle dispatch", nextDispatch, mTestTimer.getElapsed());
-
-            mNowElapsedTest = nextDispatch;
+        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false);
+            mNowElapsedTest = mTestTimer.getElapsed();
             mTestTimer.expire();
-
-            while (i < numAlarms && (seedTrigger + i * i) <= nextDispatch) {
-                verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(),
-                        any(Handler.class), isNull(), any());
-                i++;
-            }
-            Log.d(TAG, "Dispatched alarms upto " + i + " at " + nextDispatch);
-            lastAwiDispatch = nextDispatch;
         }
+        // This one should get deferred on set.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
+                getNewMockPendingIntent(), false);
+        final long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
+        assertEquals("Incorrect trigger when no quota left", expectedNextTrigger,
+                mTestTimer.getElapsed());
+
+        // Bring the idle until alarm back.
+        setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger - 50,
+                getNewMockPendingIntent());
+        assertEquals(expectedNextTrigger - 50, mService.mPendingIdleUntil.getWhenElapsed());
+        assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed());
     }
 
     @Test
-    public void allowWhileIdleUnrestrictedInIdle() throws Exception {
+    public void allowWhileIdleUnrestricted() throws Exception {
         doReturn(0).when(mService).fuzzForDuration(anyLong());
 
-        final long awiDelayForTest = 127;
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, awiDelayForTest);
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, 0);
-
+        // Both battery saver and doze are on.
         setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1000,
                 getNewMockPendingIntent());
         assertNotNull(mService.mPendingIdleUntil);
 
-        final long seedTrigger = mNowElapsedTest + 3;
-        for (int i = 1; i <= 5; i++) {
-            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, seedTrigger + i * i,
+        when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE)).thenReturn(true);
+
+        final int numAlarms = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA + 100;
+        final long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < numAlarms; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
                     getNewMockPendingIntent(), true);
         }
-        for (int i = 1; i <= 5; i++) {
-            final long nextTrigger = mTestTimer.getElapsed();
-            assertEquals("Wrong trigger for alarm " + i, seedTrigger + i * i, nextTrigger);
-            mNowElapsedTest = nextTrigger;
+        // All of them should fire as expected.
+        for (int i = 0; i < numAlarms; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            assertEquals("Incorrect trigger at i=" + i, firstTrigger + i, mNowElapsedTest);
             mTestTimer.expire();
         }
     }
@@ -1427,9 +1426,10 @@
         verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture());
         final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue();
 
-        final PendingIntent alarmPi = getNewMockPendingIntent();
         when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(true);
+
+        final PendingIntent alarmPi = getNewMockPendingIntent();
         setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 7, alarmPi);
         assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed());
 
@@ -1446,61 +1446,64 @@
 
     @Test
     public void allowWhileIdleAlarmsInBatterySaver() throws Exception {
-        final ArgumentCaptor<AppStateTrackerImpl.Listener> listenerArgumentCaptor =
-                ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class);
-        verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture());
-        final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue();
-
-        final long longDelay = 23;
-        final long shortDelay = 7;
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, longDelay);
-        setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, shortDelay);
-
         when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID,
                 TEST_CALLING_PACKAGE)).thenReturn(true);
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
+        when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true);
+
+        final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA;
+        long firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false);
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        // This one should get deferred on set.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
                 getNewMockPendingIntent(), false);
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2,
+        long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
+        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
+                mTestTimer.getElapsed());
+
+        // Refresh the state
+        mService.removeLocked(TEST_CALLING_UID);
+        mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
+
+        firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false);
+        }
+        // This one should get deferred after the latest alarm expires.
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota,
                 getNewMockPendingIntent(), false);
+        for (int i = 0; i < quota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW;
+        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
+                mTestTimer.getElapsed());
 
-        assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed());
+        // Refresh the state
+        mService.removeLocked(TEST_CALLING_UID);
+        mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER);
 
-        mNowElapsedTest += 1;
-        mTestTimer.expire();
-
-        assertEquals(mNowElapsedTest + longDelay, mTestTimer.getElapsed());
-        listener.onUidForeground(TEST_CALLING_UID, true);
-        // The next alarm should be deferred by shortDelay.
-        assertEquals(mNowElapsedTest + shortDelay, mTestTimer.getElapsed());
-
-        mNowElapsedTest = mTestTimer.getElapsed();
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
+        firstTrigger = mNowElapsedTest + 10;
+        for (int i = 0; i < quota; i++) {
+            setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
+                    getNewMockPendingIntent(), false);
+        }
+        // This delivery time maintains the quota invariant. Should not be deferred.
+        expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW + 5;
+        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger,
                 getNewMockPendingIntent(), false);
-
-        when(mAppStateTracker.isUidInForeground(TEST_CALLING_UID)).thenReturn(true);
-        mTestTimer.expire();
-        // The next alarm should be deferred by shortDelay again.
-        assertEquals(mNowElapsedTest + shortDelay, mTestTimer.getElapsed());
-
-        mNowElapsedTest = mTestTimer.getElapsed();
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1,
-                getNewMockPendingIntent(), true);
-        when(mAppStateTracker.isUidInForeground(TEST_CALLING_UID)).thenReturn(false);
-        mTestTimer.expire();
-        final long lastAwiDispatch = mNowElapsedTest;
-        // Unrestricted, so should not be changed.
-        assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed());
-
-        mNowElapsedTest = mTestTimer.getElapsed();
-        // AWI_unrestricted should not affect normal AWI bookkeeping.
-        // The next alarm is after the short delay but before the long delay.
-        setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, lastAwiDispatch + shortDelay + 1,
-                getNewMockPendingIntent(), false);
-        mTestTimer.expire();
-        assertEquals(lastAwiDispatch + longDelay, mTestTimer.getElapsed());
-
-        listener.onUidForeground(TEST_CALLING_UID, true);
-        assertEquals(lastAwiDispatch + shortDelay + 1, mTestTimer.getElapsed());
+        for (int i = 0; i < quota; i++) {
+            mNowElapsedTest = mTestTimer.getElapsed();
+            mTestTimer.expire();
+        }
+        assertEquals("Incorrect trigger when no quota available", expectedNextTrigger,
+                mTestTimer.getElapsed());
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
index b85da94..961fc18 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AppChildProcessTest.java
@@ -118,8 +118,12 @@
     @After
     public void tearDown() {
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        mMockitoSession.finishMocking();
-        mHandlerThread.quit();
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+        }
     }
 
     @Test
@@ -228,8 +232,8 @@
         ai.packageName = packageName;
         ai.uid = uid;
         ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
-        app.pid = pid;
-        mAms.mPidsSelfLocked.doAddInternal(app);
+        app.setPid(pid);
+        mAms.mPidsSelfLocked.doAddInternal(app.getPid(), app);
         mPhantomInjector.addToProcess(uid, pid, pid);
     }
 
@@ -298,7 +302,7 @@
         }
 
         void addToProcess(int uid, int pid, int newPid) {
-            final String path = PhantomProcessList.getCgroupFilePath(uid, pid);
+            final String path = mPhantomProcessList.getCgroupFilePath(uid, pid);
             StringBuffer sb = mPathToData.get(path);
             if (sb == null) {
                 sb = new StringBuffer();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index e9a50b3..22b2f7e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -164,24 +164,24 @@
     }
 
     private void updateExitInfo(ProcessRecord app) {
-        ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecordLocked(app);
+        ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecord(app);
         mAppExitInfoTracker.handleNoteProcessDiedLocked(raw);
-        mAppExitInfoTracker.recycleRawRecordLocked(raw);
+        mAppExitInfoTracker.recycleRawRecord(raw);
     }
 
     private void noteAppKill(ProcessRecord app, int reason, int subReason, String msg) {
-        ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecordLocked(app);
+        ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecord(app);
         raw.setReason(reason);
         raw.setSubReason(subReason);
         raw.setDescription(msg);
         mAppExitInfoTracker.handleNoteAppKillLocked(raw);
-        mAppExitInfoTracker.recycleRawRecordLocked(raw);
+        mAppExitInfoTracker.recycleRawRecord(raw);
     }
 
     @Test
     public void testApplicationExitInfo() throws Exception {
         mAppExitInfoTracker.clearProcessExitInfo(true);
-        mAppExitInfoTracker.mAppExitInfoLoaded = true;
+        mAppExitInfoTracker.mAppExitInfoLoaded.set(true);
         mAppExitInfoTracker.mProcExitStoreDir = new File(mContext.getFilesDir(),
                 AppExitInfoTracker.APP_EXIT_STORE_DIR);
         assertTrue(FileUtils.createDir(mAppExitInfoTracker.mProcExitStoreDir));
@@ -798,7 +798,7 @@
         final int traceEnd = 8192;
         createRandomFile(traceFile, traceSize);
         assertEquals(traceSize, traceFile.length());
-        mAppExitInfoTracker.handleLogAnrTrace(app.pid, app.uid, app.getPackageList(),
+        mAppExitInfoTracker.handleLogAnrTrace(app.getPid(), app.uid, app.getPackageList(),
                 traceFile, traceStart, traceEnd);
 
         noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
@@ -991,19 +991,19 @@
         ApplicationInfo ai = new ApplicationInfo();
         ai.packageName = packageName;
         ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
-        app.pid = pid;
+        app.setPid(pid);
         app.info.uid = packageUid;
         if (definingUid != null) {
             final String dummyPackageName = "com.android.test";
             final String dummyClassName = ".Foo";
-            app.hostingRecord = HostingRecord.byAppZygote(new ComponentName(
-                    dummyPackageName, dummyClassName), "", definingUid);
+            app.setHostingRecord(HostingRecord.byAppZygote(new ComponentName(
+                    dummyPackageName, dummyClassName), "", definingUid));
         }
-        app.connectionGroup = connectionGroup;
-        app.setProcState = procState;
-        app.lastMemInfo = spy(new Debug.MemoryInfo());
-        app.lastPss = pss;
-        app.mLastRss = rss;
+        app.mServices.setConnectionGroup(connectionGroup);
+        app.mState.setSetProcState(procState);
+        app.mProfile.setLastMemInfo(spy(new Debug.MemoryInfo()));
+        app.mProfile.setLastPss(pss);
+        app.mProfile.setLastRss(rss);
         return app;
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index 3710396..022fadc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -84,7 +84,7 @@
     private int mNextPackageName = 1;
 
     private TestExecutor mExecutor = new TestExecutor();
-    private CacheOomRanker mCacheOomRanker = new CacheOomRanker();
+    private CacheOomRanker mCacheOomRanker;
 
     @Before
     public void setUp() {
@@ -107,6 +107,7 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
 
+        mCacheOomRanker = new CacheOomRanker(mAms);
         mCacheOomRanker.init(mExecutor);
     }
 
@@ -169,7 +170,7 @@
                 /* lruWeight= */1.0f);
 
         ProcessList list = new ProcessList();
-        ArrayList<ProcessRecord> processList = list.mLruProcesses;
+        ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
         ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
                 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
         processList.add(lastUsed40MinutesAgo);
@@ -190,7 +191,7 @@
                 Duration.ofMinutes(30).toMillis(), 1024L, 20);
         processList.add(lastUsed30MinutesAgo);
 
-        mCacheOomRanker.reRankLruCachedApps(list);
+        mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
 
         // First 5 ordered by least recently used first, then last processes position unchanged.
         assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo,
@@ -206,7 +207,7 @@
                 /* lruWeight= */ 0.0f);
 
         ProcessList list = new ProcessList();
-        ArrayList<ProcessRecord> processList = list.mLruProcesses;
+        ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
         ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
                 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
         processList.add(rss10k);
@@ -230,7 +231,7 @@
                 Duration.ofMinutes(30).toMillis(), 16 * 1024L, 20);
         processList.add(rss16k);
 
-        mCacheOomRanker.reRankLruCachedApps(list);
+        mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
 
         // First 6 ordered by largest pss, then last processes position unchanged.
         assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
@@ -245,8 +246,8 @@
                 /* lruWeight= */ 0.0f);
 
         ProcessList list = new ProcessList();
-        list.mLruProcessServiceStart = 1;
-        ArrayList<ProcessRecord> processList = list.mLruProcesses;
+        list.setLruProcessServiceStartLSP(1);
+        ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
         ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
                 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
         processList.add(used1000);
@@ -267,7 +268,7 @@
                 Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
         processList.add(used200);
 
-        mCacheOomRanker.reRankLruCachedApps(list);
+        mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
 
         // First 4 ordered by uses, then last processes position unchanged.
         assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500,
@@ -282,7 +283,7 @@
                 /* lruWeight= */ 0.3f);
 
         ProcessList list = new ProcessList();
-        ArrayList<ProcessRecord> processList = list.mLruProcesses;
+        ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
         ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
                 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
         processList.add(unknownAdj1);
@@ -303,7 +304,7 @@
         processList.add(systemAdj);
 
         // 6 Processes but only 3 in eligible for cache so no re-ranking.
-        mCacheOomRanker.reRankLruCachedApps(list);
+        mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
 
         // All positions unchanged.
         assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3,
@@ -318,8 +319,8 @@
                 /* lruWeight= */ 0.0f);
 
         ProcessList list = new ProcessList();
-        list.mLruProcessServiceStart = 4;
-        ArrayList<ProcessRecord> processList = list.mLruProcesses;
+        list.setLruProcessServiceStartLSP(4);
+        ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
         ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
                 Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
         processList.add(used1000);
@@ -339,7 +340,7 @@
                 Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
         processList.add(used200);
 
-        mCacheOomRanker.reRankLruCachedApps(list);
+        mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
 
         // All positions unchanged.
         assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
@@ -377,17 +378,17 @@
         ApplicationInfo ai = new ApplicationInfo();
         ai.packageName = "a.package.name" + mNextPackageName++;
         ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++);
-        app.pid = mNextPid++;
+        app.setPid(mNextPid++);
         app.info.uid = mNextPackageUid++;
         // Exact value does not mater, it can be any state for which compaction is allowed.
-        app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-        app.setAdj = setAdj;
-        app.lastActivityTime = lastActivityTime;
-        app.mLastRss = lastRss;
-        app.setCached(false);
+        app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+        app.mState.setSetAdj(setAdj);
+        app.setLastActivityTime(lastActivityTime);
+        app.mProfile.setLastRss(lastRss);
+        app.mState.setCached(false);
         for (int i = 0; i < returnedToCacheCount; ++i) {
-            app.setCached(false);
-            app.setCached(true);
+            app.mState.setCached(false);
+            app.mState.setCached(true);
         }
         return app;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 96a44a4..d860326 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -134,11 +134,11 @@
         ApplicationInfo ai = new ApplicationInfo();
         ai.packageName = packageName;
         ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
-        app.pid = pid;
+        app.setPid(pid);
         app.info.uid = packageUid;
         // Exact value does not mater, it can be any state for which compaction is allowed.
-        app.setProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-        app.setAdj = 905;
+        app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+        app.mState.setSetAdj(905);
         return app;
     }
 
@@ -875,7 +875,8 @@
         mProcessDependencies.setRss(rssBefore2);
         mProcessDependencies.setRssAfterCompaction(rssAfter2);
         // This is to avoid throttle of compacting too soon.
-        processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000;
+        processRecord.mOptRecord.setLastCompactTime(
+                processRecord.mOptRecord.getLastCompactTime() - 10_000);
         // WHEN we try to run compaction.
         mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
         waitForHandler();
@@ -890,7 +891,8 @@
         mProcessDependencies.setRss(rssBefore3);
         mProcessDependencies.setRssAfterCompaction(rssAfter3);
         // This is to avoid throttle of compacting too soon.
-        processRecord.lastCompactTime = processRecord.lastCompactTime - 10_000;
+        processRecord.mOptRecord.setLastCompactTime(
+                processRecord.mOptRecord.getLastCompactTime() - 10_000);
         // WHEN we try to run compaction
         mCachedAppOptimizerUnderTest.compactAppFull(processRecord);
         waitForHandler();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 8011ec2..27825a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -103,6 +103,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Test class for {@link OomAdjuster}.
@@ -159,8 +160,9 @@
                 sContext.getMainThreadHandler());
         setFieldValue(ActivityManagerService.class, sService, "mContext",
                 sContext);
-        ProcessList pr = new ProcessList();
-        pr.init(sService, new ActiveUids(sService, false), null);
+        ProcessList pr = spy(new ProcessList());
+        pr.mService = sService;
+        AppProfiler profiler = mock(AppProfiler.class);
         setFieldValue(ActivityManagerService.class, sService, "mProcessList",
                 pr);
         setFieldValue(ActivityManagerService.class, sService, "mHandler",
@@ -173,13 +175,16 @@
                 mock(OomAdjProfiler.class));
         setFieldValue(ActivityManagerService.class, sService, "mUserController",
                 mock(UserController.class));
-        setFieldValue(ActivityManagerService.class, sService, "mAppProfiler",
-                mock(AppProfiler.class));
-        doReturn(new ActivityManagerService.ProcessChangeItem()).when(sService)
+        setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
+        setFieldValue(ActivityManagerService.class, sService, "mProcLock",
+                new ActivityManagerProcLock());
+        setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
+        doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
                 .enqueueProcessChangeItemLocked(anyInt(), anyInt());
         sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList,
                 mock(ActiveUids.class));
         sService.mOomAdjuster.mAdjSeq = 10000;
+        sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
     }
 
     @AfterClass
@@ -204,11 +209,11 @@
     public void testUpdateOomAdj_DoOne_Persistent_TopUi_Sleeping() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.maxAdj = PERSISTENT_PROC_ADJ;
-        app.setHasTopUi(true);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_ASLEEP;
+        app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        app.mState.setHasTopUi(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERSISTENT_PROC_ADJ,
                 SCHED_GROUP_RESTRICTED);
@@ -219,9 +224,9 @@
     public void testUpdateOomAdj_DoOne_Persistent_TopUi_Awake() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.maxAdj = PERSISTENT_PROC_ADJ;
-        app.setHasTopUi(true);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        app.mState.setHasTopUi(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
@@ -233,11 +238,11 @@
     public void testUpdateOomAdj_DoOne_Persistent_TopApp() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.maxAdj = PERSISTENT_PROC_ADJ;
-        doReturn(app).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        doReturn(app).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doReturn(null).when(sService).getTopAppLocked();
+        doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_PERSISTENT_UI, PERSISTENT_PROC_ADJ,
                 SCHED_GROUP_TOP_APP);
@@ -249,10 +254,10 @@
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
-        doReturn(app).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        doReturn(app).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doReturn(null).when(sService).getTopAppLocked();
+        doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_TOP_APP);
     }
@@ -263,8 +268,8 @@
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
-        app.runningRemoteAnimation = true;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setRunningRemoteAnimation(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
 
@@ -277,7 +282,7 @@
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         doReturn(mock(ActiveInstrumentation.class)).when(app).getActiveInstrumentation();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
         doCallRealMethod().when(app).getActiveInstrumentation();
 
@@ -292,7 +297,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         doReturn(true).when(sService).isReceivingBroadcastLocked(any(ProcessRecord.class),
                 any(ArraySet.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
         doReturn(false).when(sService).isReceivingBroadcastLocked(any(ProcessRecord.class),
                 any(ArraySet.class));
@@ -305,8 +310,8 @@
     public void testUpdateOomAdj_DoOne_ExecutingService() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.executingServices.add(mock(ServiceRecord.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mServices.startExecutingService(mock(ServiceRecord.class));
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_SERVICE, FOREGROUND_APP_ADJ, SCHED_GROUP_BACKGROUND);
@@ -318,12 +323,12 @@
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
         doReturn(PROCESS_STATE_TOP_SLEEPING).when(sService.mAtmInternal).getTopProcessState();
-        doReturn(app).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_ASLEEP;
+        doReturn(app).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_ASLEEP);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doReturn(null).when(sService).getTopAppLocked();
+        doReturn(null).when(sService).getTopApp();
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
         assertProcStates(app, PROCESS_STATE_TOP_SLEEPING, FOREGROUND_APP_ADJ,
                 SCHED_GROUP_BACKGROUND);
@@ -334,9 +339,9 @@
     public void testUpdateOomAdj_DoOne_CachedEmpty() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.setCurRawAdj(CACHED_APP_MIN_ADJ);
-        doReturn(null).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
+        doReturn(null).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
@@ -348,7 +353,6 @@
     public void testUpdateOomAdj_DoOne_VisibleActivities() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
         WindowProcessController wpc = app.getWindowProcessController();
         doReturn(true).when(wpc).hasActivities();
         doAnswer(answer(callback -> {
@@ -363,9 +367,8 @@
             return 0;
         })).when(wpc).computeOomAdjFromActivities(
                 any(WindowProcessController.ComputeOomAdjCallback.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doCallRealMethod().when(app).getWindowProcessController();
 
         assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
     }
@@ -375,15 +378,14 @@
     public void testUpdateOomAdj_DoOne_RecentTasks() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
         WindowProcessController wpc = app.getWindowProcessController();
         doReturn(true).when(wpc).hasRecentTasks();
-        app.lastTopTime = SystemClock.uptimeMillis();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setLastTopTime(SystemClock.uptimeMillis());
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
         doCallRealMethod().when(wpc).hasRecentTasks();
 
-        assertEquals(PROCESS_STATE_CACHED_RECENT, app.setProcState);
+        assertEquals(PROCESS_STATE_CACHED_RECENT, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -391,8 +393,8 @@
     public void testUpdateOomAdj_DoOne_FgServiceLocation() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mServices.setHasForegroundServices(true, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -404,8 +406,8 @@
     public void testUpdateOomAdj_DoOne_FgService() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.setHasForegroundServices(true, 0);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mServices.setHasForegroundServices(true, 0);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -417,8 +419,8 @@
     public void testUpdateOomAdj_DoOne_OverlayUi() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.setHasOverlayUi(true);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setHasOverlayUi(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ,
@@ -430,9 +432,9 @@
     public void testUpdateOomAdj_DoOne_PerceptibleRecent() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.setHasForegroundServices(true, 0);
-        app.lastTopTime = SystemClock.uptimeMillis();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mServices.setHasForegroundServices(true, 0);
+        app.mState.setLastTopTime(SystemClock.uptimeMillis());
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE,
@@ -444,8 +446,8 @@
     public void testUpdateOomAdj_DoOne_Toast() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.forcingToImportant = new Object();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setForcingToImportant(new Object());
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -457,10 +459,9 @@
     public void testUpdateOomAdj_DoOne_HeavyWeight() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
         WindowProcessController wpc = app.getWindowProcessController();
         doReturn(true).when(wpc).isHeavyWeightProcess();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
         doReturn(false).when(wpc).isHeavyWeightProcess();
 
@@ -473,10 +474,9 @@
     public void testUpdateOomAdj_DoOne_HomeApp() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
         WindowProcessController wpc = app.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_HOME, HOME_APP_ADJ, SCHED_GROUP_BACKGROUND);
@@ -487,11 +487,10 @@
     public void testUpdateOomAdj_DoOne_PreviousApp() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
         WindowProcessController wpc = app.getWindowProcessController();
         doReturn(true).when(wpc).isPreviousProcess();
         doReturn(true).when(wpc).hasActivities();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
@@ -506,7 +505,7 @@
         BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
         backupTarget.app = app;
         doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
         doReturn(null).when(sService.mBackupTargets).get(anyInt());
 
@@ -519,11 +518,11 @@
     public void testUpdateOomAdj_DoOne_ClientActivities() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        doReturn(true).when(app).hasClientActivities();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mServices.setHasClientActivities(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.setProcState);
+        assertEquals(PROCESS_STATE_CACHED_ACTIVITY_CLIENT, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -531,11 +530,11 @@
     public void testUpdateOomAdj_DoOne_TreatLikeActivity() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        app.treatLikeActivity = true;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mServices.setTreatLikeActivity(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.setProcState);
+        assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -543,13 +542,13 @@
     public void testUpdateOomAdj_DoOne_ServiceB() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
-        app.serviceb = true;
+        app.mState.setServiceB(true);
         ServiceRecord s = mock(ServiceRecord.class);
         doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
         s.startRequested = true;
         s.lastActivity = SystemClock.uptimeMillis();
-        app.startService(s);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mServices.startService(s);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_B_ADJ, SCHED_GROUP_BACKGROUND);
@@ -560,8 +559,8 @@
     public void testUpdateOomAdj_DoOne_MaxAdj() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
-        app.maxAdj = PERCEPTIBLE_LOW_APP_ADJ;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setMaxAdj(PERCEPTIBLE_LOW_APP_ADJ);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, PERCEPTIBLE_LOW_APP_ADJ,
@@ -573,14 +572,14 @@
     public void testUpdateOomAdj_DoOne_NonCachedToCached() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
-        app.setCached(false);
-        app.setCurRawAdj(SERVICE_ADJ);
-        doReturn(null).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mState.setCached(false);
+        app.mState.setCurRawAdj(SERVICE_ADJ);
+        doReturn(null).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.setAdj);
-        assertTrue(ProcessList.CACHED_APP_MAX_ADJ >= app.setAdj);
+        assertTrue(ProcessList.CACHED_APP_MIN_ADJ <= app.mState.getSetAdj());
+        assertTrue(ProcessList.CACHED_APP_MAX_ADJ >= app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -592,8 +591,8 @@
         doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
         s.startRequested = true;
         s.lastActivity = SystemClock.uptimeMillis();
-        app.startService(s);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mServices.startService(s);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_SERVICE, SERVICE_ADJ, SCHED_GROUP_BACKGROUND);
@@ -609,11 +608,11 @@
         ServiceRecord s = bindService(app, client, null, Context.BIND_WAIVE_PRIORITY,
                 mock(IBinder.class));
         s.startRequested = true;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
-        doReturn(client).when(sService).getTopAppLocked();
+        doReturn(client).when(sService).getTopApp();
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doReturn(null).when(sService).getTopAppLocked();
+        doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_SERVICE, UNKNOWN_ADJ, SCHED_GROUP_BACKGROUND);
     }
@@ -627,10 +626,10 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_WAIVE_PRIORITY
                 | Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.setProcState);
+        assertEquals(PROCESS_STATE_CACHED_ACTIVITY, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -647,11 +646,11 @@
         setFieldValue(ConnectionRecord.class, cr, "activity",
                 mock(ActivityServiceConnectionsHolder.class));
         doReturn(true).when(cr.activity).isActivityVisible();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(FOREGROUND_APP_ADJ, app.setAdj);
-        assertEquals(SCHED_GROUP_TOP_APP_BOUND, app.setSchedGroup);
+        assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+        assertEquals(SCHED_GROUP_TOP_APP_BOUND, app.mState.getSetSchedGroup());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -660,7 +659,7 @@
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         bindService(app, app, null, 0, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, UNKNOWN_ADJ, SCHED_GROUP_BACKGROUND);
@@ -673,12 +672,12 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
-        client.treatLikeActivity = true;
+        client.mServices.setTreatLikeActivity(true);
         bindService(app, client, null, 0, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_CACHED_EMPTY, app.setProcState);
+        assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -686,7 +685,6 @@
     public void testUpdateOomAdj_DoOne_Service_AllowOomManagement() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
-        doReturn(mock(WindowProcessController.class)).when(app).getWindowProcessController();
         WindowProcessController wpc = app.getWindowProcessController();
         doReturn(false).when(wpc).isHomeProcess();
         doReturn(true).when(wpc).isPreviousProcess();
@@ -695,12 +693,12 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_ALLOW_OOM_MANAGEMENT, mock(IBinder.class));
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
-        doReturn(client).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        doReturn(client).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doReturn(null).when(sService).getTopAppLocked();
+        doReturn(null).when(sService).getTopApp();
 
-        assertEquals(PREVIOUS_APP_ADJ, app.setAdj);
+        assertEquals(PREVIOUS_APP_ADJ, app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -711,9 +709,9 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
-        client.maxAdj = PERSISTENT_PROC_ADJ;
-        client.setHasTopUi(true);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        client.mState.setHasTopUi(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
@@ -728,11 +726,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_IMPORTANT, mock(IBinder.class));
-        client.executingServices.add(mock(ServiceRecord.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mServices.startExecutingService(mock(ServiceRecord.class));
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(FOREGROUND_APP_ADJ, app.setAdj);
+        assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -744,10 +742,10 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, 0, mock(IBinder.class));
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
-        doReturn(client).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        doReturn(client).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doReturn(null).when(sService).getTopAppLocked();
+        doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT);
     }
@@ -760,11 +758,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_FOREGROUND_SERVICE, mock(IBinder.class));
-        client.maxAdj = PERSISTENT_PROC_ADJ;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.setProcState);
+        assertEquals(PROCESS_STATE_BOUND_FOREGROUND_SERVICE, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -775,11 +773,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
-        client.maxAdj = PERSISTENT_PROC_ADJ;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.setProcState);
+        assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -790,11 +788,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, 0, mock(IBinder.class));
-        client.setHasForegroundServices(true, 0);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mServices.setHasForegroundServices(true, 0);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.setProcState);
+        assertEquals(PROCESS_STATE_FOREGROUND_SERVICE, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -808,16 +806,16 @@
         BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0);
         backupTarget.app = client;
         doReturn(backupTarget).when(sService.mBackupTargets).get(anyInt());
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
         doReturn(null).when(sService.mBackupTargets).get(anyInt());
 
-        assertEquals(BACKUP_APP_ADJ, app.setAdj);
+        assertEquals(BACKUP_APP_ADJ, app.mState.getSetAdj());
 
-        client.maxAdj = PERSISTENT_PROC_ADJ;
+        client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PERSISTENT_SERVICE_ADJ, app.setAdj);
+        assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -828,11 +826,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
-        client.runningRemoteAnimation = true;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mState.setRunningRemoteAnimation(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.setAdj);
+        assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -843,11 +841,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_NOT_VISIBLE, mock(IBinder.class));
-        client.runningRemoteAnimation = true;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mState.setRunningRemoteAnimation(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PERCEPTIBLE_APP_ADJ, app.setAdj);
+        assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -858,11 +856,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, 0, mock(IBinder.class));
-        client.setHasOverlayUi(true);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mState.setHasOverlayUi(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PERCEPTIBLE_APP_ADJ, app.setAdj);
+        assertEquals(PERCEPTIBLE_APP_ADJ, app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -873,11 +871,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, 0, mock(IBinder.class));
-        client.runningRemoteAnimation = true;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mState.setRunningRemoteAnimation(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(VISIBLE_APP_ADJ, app.setAdj);
+        assertEquals(VISIBLE_APP_ADJ, app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -888,11 +886,11 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindService(app, client, null, Context.BIND_IMPORTANT_BACKGROUND, mock(IBinder.class));
-        client.setHasOverlayUi(true);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mState.setHasOverlayUi(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.setProcState);
+        assertEquals(PROCESS_STATE_IMPORTANT_BACKGROUND, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -914,8 +912,8 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindProvider(app, client, null, null, false);
-        client.treatLikeActivity = true;
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client.mServices.setTreatLikeActivity(true);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, UNKNOWN_ADJ, SCHED_GROUP_BACKGROUND);
@@ -930,10 +928,10 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindProvider(app, client, null, null, false);
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
-        doReturn(client).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        doReturn(client).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doReturn(null).when(sService).getTopAppLocked();
+        doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_BOUND_TOP, FOREGROUND_APP_ADJ, SCHED_GROUP_DEFAULT);
     }
@@ -945,9 +943,9 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
-        client.setHasForegroundServices(true, 0);
+        client.mServices.setHasForegroundServices(true, 0);
         bindProvider(app, client, null, null, false);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -962,7 +960,7 @@
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
         bindProvider(app, client, null, null, true);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, FOREGROUND_APP_ADJ,
@@ -974,8 +972,8 @@
     public void testUpdateOomAdj_DoOne_Provider_Retention() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
-        app.lastProviderTime = SystemClock.uptimeMillis();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        app.mProviders.setLastProviderTime(SystemClock.uptimeMillis());
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
@@ -994,10 +992,10 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
-        doReturn(client2).when(sService).getTopAppLocked();
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        doReturn(client2).when(sService).getTopApp();
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
-        doReturn(null).when(sService).getTopAppLocked();
+        doReturn(null).when(sService).getTopApp();
 
         assertProcStates(app, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
@@ -1014,8 +1012,8 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app, client2, null, 0, mock(IBinder.class));
-        client2.setHasForegroundServices(true, 0);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client2.mServices.setHasForegroundServices(true, 0);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1033,8 +1031,8 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
-        client2.setHasForegroundServices(true, 0);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client2.mServices.setHasForegroundServices(true, 0);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1052,14 +1050,14 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
-        client2.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0);
         bindService(client2, app, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
         lru.add(client);
         lru.add(client2);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, true, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1069,13 +1067,13 @@
         assertProcStates(client2, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
 
-        client2.setHasForegroundServices(false, 0);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client2.mServices.setHasForegroundServices(false, 0);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(client2, true, OomAdjuster.OOM_ADJ_REASON_NONE);
 
-        assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.setProcState);
-        assertEquals(PROCESS_STATE_CACHED_EMPTY, client.setProcState);
-        assertEquals(PROCESS_STATE_CACHED_EMPTY, app.setProcState);
+        assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
+        assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
+        assertEquals(PROCESS_STATE_CACHED_EMPTY, app.mState.getSetProcState());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1090,13 +1088,13 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client2, client, null, 0, mock(IBinder.class));
-        client.setHasForegroundServices(true, 0);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        client.mServices.setHasForegroundServices(true, 0);
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
         lru.add(client);
         lru.add(client2);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, true, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1118,13 +1116,13 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
-        client2.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0);
         bindService(client2, app, null, 0, mock(IBinder.class));
         ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
-        client3.forcingToImportant = new Object();
+        client3.mState.setForcingToImportant(new Object());
         bindService(app, client3, null, 0, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1143,14 +1141,13 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
         bindService(client2, app, null, 0, mock(IBinder.class));
-        doReturn(mock(WindowProcessController.class)).when(client2).getWindowProcessController();
         WindowProcessController wpc = client2.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
-        client3.forcingToImportant = new Object();
+        client3.mState.setForcingToImportant(new Object());
         bindService(app, client3, null, 0, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -1169,16 +1166,15 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
         bindService(client2, app, null, 0, mock(IBinder.class));
-        doReturn(mock(WindowProcessController.class)).when(client2).getWindowProcessController();
         WindowProcessController wpc = client2.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
         ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        client4.forcingToImportant = new Object();
+        client4.mState.setForcingToImportant(new Object());
         bindService(app, client4, null, 0, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_TRANSIENT_BACKGROUND, PERCEPTIBLE_APP_ADJ,
@@ -1197,18 +1193,17 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(client, client2, null, 0, mock(IBinder.class));
         bindService(client2, app, null, 0, mock(IBinder.class));
-        doReturn(mock(WindowProcessController.class)).when(client2).getWindowProcessController();
         WindowProcessController wpc = client2.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
-        client3.forcingToImportant = new Object();
+        client3.mState.setForcingToImportant(new Object());
         bindService(app, client3, null, 0, mock(IBinder.class));
         ProcessRecord client4 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        client4.setHasForegroundServices(true, 0);
+        client4.mServices.setHasForegroundServices(true, 0);
         bindService(app, client4, null, 0, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1222,19 +1217,18 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
-        doReturn(mock(WindowProcessController.class)).when(client).getWindowProcessController();
         WindowProcessController wpc = client.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         bindService(app, client, null, 0, mock(IBinder.class));
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app, client2, null, 0, mock(IBinder.class));
-        client2.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0);
         ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
-        client3.forcingToImportant = new Object();
+        client3.mState.setForcingToImportant(new Object());
         bindService(app, client3, null, 0, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1252,8 +1246,8 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(client, client2, null, null, false);
-        client2.setHasForegroundServices(true, 0);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client2.mServices.setHasForegroundServices(true, 0);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1271,9 +1265,9 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(client, client2, null, null, false);
-        client2.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0);
         bindService(client2, app, null, 0, mock(IBinder.class));
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1291,8 +1285,8 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(client, client2, null, null, false);
-        client2.setHasForegroundServices(true, 0);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        client2.mServices.setHasForegroundServices(true, 0);
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1310,9 +1304,9 @@
         ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(client, client2, null, null, false);
-        client2.setHasForegroundServices(true, 0);
+        client2.mServices.setHasForegroundServices(true, 0);
         bindProvider(client2, app, null, null, false);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
@@ -1324,15 +1318,15 @@
     public void testUpdateOomAdj_DoAll_Unbound() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
-        app.forcingToImportant = new Object();
+        app.mState.setForcingToImportant(new Object());
         ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
-        app2.setHasForegroundServices(true, 0);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        app2.mServices.setHasForegroundServices(true, 0);
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
         lru.add(app2);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
         lru.clear();
 
@@ -1347,16 +1341,16 @@
     public void testUpdateOomAdj_DoAll_BoundFgService() {
         ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
-        app.forcingToImportant = new Object();
+        app.mState.setForcingToImportant(new Object());
         ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
-        app2.setHasForegroundServices(true, 0);
+        app2.mServices.setHasForegroundServices(true, 0);
         bindService(app, app2, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
         lru.add(app2);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
         lru.clear();
 
@@ -1377,14 +1371,14 @@
         ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app2, app3, null, 0, mock(IBinder.class));
-        app3.setHasForegroundServices(true, 0);
+        app3.mServices.setHasForegroundServices(true, 0);
         bindService(app3, app, null, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
         lru.add(app2);
         lru.add(app3);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
         lru.clear();
 
@@ -1394,15 +1388,15 @@
                 SCHED_GROUP_DEFAULT);
         assertProcStates(app3, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
-        assertEquals("service", app.adjType);
-        assertEquals("service", app2.adjType);
-        assertEquals("fg-service", app3.adjType);
+        assertEquals("service", app.mState.getAdjType());
+        assertEquals("service", app2.mState.getAdjType());
+        assertEquals("fg-service", app3.mState.getAdjType());
         assertEquals(false, app.isCached());
         assertEquals(false, app2.isCached());
         assertEquals(false, app3.isCached());
-        assertEquals(false, app.empty);
-        assertEquals(false, app2.empty);
-        assertEquals(false, app3.empty);
+        assertEquals(false, app.mState.isEmpty());
+        assertEquals(false, app2.mState.isEmpty());
+        assertEquals(false, app3.mState.isEmpty());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1417,25 +1411,24 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app2, app3, null, 0, mock(IBinder.class));
         bindService(app3, app, null, 0, mock(IBinder.class));
-        doReturn(mock(WindowProcessController.class)).when(app3).getWindowProcessController();
         WindowProcessController wpc = app3.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
-        app4.setHasOverlayUi(true);
+        app4.mState.setHasOverlayUi(true);
         bindService(app, app4, s, 0, mock(IBinder.class));
         ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        app5.setHasForegroundServices(true, 0);
+        app5.mServices.setHasForegroundServices(true, 0);
         bindService(app, app5, s, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
         lru.add(app2);
         lru.add(app3);
         lru.add(app4);
         lru.add(app5);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
         lru.clear();
 
@@ -1463,25 +1456,24 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app2, app3, null, 0, mock(IBinder.class));
         bindService(app3, app, null, 0, mock(IBinder.class));
-        doReturn(mock(WindowProcessController.class)).when(app3).getWindowProcessController();
         WindowProcessController wpc = app3.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
-        app4.setHasOverlayUi(true);
+        app4.mState.setHasOverlayUi(true);
         bindService(app, app4, s, 0, mock(IBinder.class));
         ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        app5.setHasForegroundServices(true, 0);
+        app5.mServices.setHasForegroundServices(true, 0);
         bindService(app, app5, s, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app5);
         lru.add(app4);
         lru.add(app3);
         lru.add(app2);
         lru.add(app);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
         lru.clear();
 
@@ -1509,25 +1501,24 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindService(app2, app3, null, 0, mock(IBinder.class));
         bindService(app3, app, null, 0, mock(IBinder.class));
-        doReturn(mock(WindowProcessController.class)).when(app3).getWindowProcessController();
         WindowProcessController wpc = app3.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
-        app4.setHasOverlayUi(true);
+        app4.mState.setHasOverlayUi(true);
         bindService(app, app4, s, 0, mock(IBinder.class));
         ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        app5.setHasForegroundServices(true, 0);
+        app5.mServices.setHasForegroundServices(true, 0);
         bindService(app, app5, s, 0, mock(IBinder.class));
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app3);
         lru.add(app4);
         lru.add(app2);
         lru.add(app);
         lru.add(app5);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
         lru.clear();
 
@@ -1555,25 +1546,24 @@
                 MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
         bindProvider(app2, app3, null, null, false);
         bindProvider(app3, app, null, null, false);
-        doReturn(mock(WindowProcessController.class)).when(app3).getWindowProcessController();
         WindowProcessController wpc = app3.getWindowProcessController();
         doReturn(true).when(wpc).isHomeProcess();
         ProcessRecord app4 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
                 MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
-        app4.setHasOverlayUi(true);
+        app4.mState.setHasOverlayUi(true);
         bindProvider(app, app4, cr, null, false);
         ProcessRecord app5 = spy(makeDefaultProcessRecord(MOCKAPP5_PID, MOCKAPP5_UID,
                 MOCKAPP5_PROCESSNAME, MOCKAPP5_PACKAGENAME, false));
-        app5.setHasForegroundServices(true, 0);
+        app5.mServices.setHasForegroundServices(true, 0);
         bindProvider(app, app5, cr, null, false);
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app);
         lru.add(app2);
         lru.add(app3);
         lru.add(app4);
         lru.add(app5);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
         lru.clear();
 
@@ -1609,23 +1599,23 @@
         s.app = app3;
         setFieldValue(ServiceRecord.class, s, "connections",
                 new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
-        app3.startService(s);
+        app3.mServices.startService(s);
         doCallRealMethod().when(s).getConnections();
         s.startRequested = true;
         s.lastActivity = now;
-        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app3);
         lru.add(app2);
         lru.add(app);
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.mNumServiceProcs = 3;
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
         lru.clear();
 
-        assertEquals(SERVICE_B_ADJ, app3.setAdj);
-        assertEquals(SERVICE_ADJ, app2.setAdj);
-        assertEquals(SERVICE_ADJ, app.setAdj);
+        assertEquals(SERVICE_B_ADJ, app3.mState.getSetAdj());
+        assertEquals(SERVICE_ADJ, app2.mState.getSetAdj());
+        assertEquals(SERVICE_ADJ, app.mState.getSetAdj());
     }
 
     @SuppressWarnings("GuardedBy")
@@ -1641,7 +1631,7 @@
         final int cachedAdj2 = cachedAdj1 + ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
         doReturn(userOwner).when(sService.mUserController).getCurrentUserId();
 
-        final ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        final ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         lru.add(app2);
         lru.add(app);
@@ -1661,9 +1651,9 @@
         s.startRequested = true;
         s.lastActivity = now;
 
-        app.setCached(false);
-        app.startService(s);
-        app.hasShownUi = true;
+        app.mState.setCached(false);
+        app.mServices.startService(s);
+        app.mState.setHasShownUi(true);
 
         final ServiceInfo si2 = mock(ServiceInfo.class);
         si2.applicationInfo = mock(ApplicationInfo.class);
@@ -1674,38 +1664,38 @@
         s2.startRequested = true;
         s2.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
 
-        app2.setCached(false);
-        app2.startService(s2);
-        app2.hasShownUi = false;
+        app2.mState.setCached(false);
+        app2.mServices.startService(s2);
+        app2.mState.setHasShownUi(false);
 
-        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
 
-        app.setProcState = PROCESS_STATE_NONEXISTENT;
-        app.adjType = null;
-        app.setAdj = UNKNOWN_ADJ;
-        app.hasShownUi = false;
+        app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
+        app.mState.setAdjType(null);
+        app.mState.setSetAdj(UNKNOWN_ADJ);
+        app.mState.setHasShownUi(false);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
 
-        app.setCached(false);
-        app.setProcState = PROCESS_STATE_NONEXISTENT;
-        app.adjType = null;
-        app.setAdj = UNKNOWN_ADJ;
+        app.mState.setCached(false);
+        app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
+        app.mState.setAdjType(null);
+        app.mState.setSetAdj(UNKNOWN_ADJ);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
 
-        app.stopService(s);
-        app.setProcState = PROCESS_STATE_NONEXISTENT;
-        app.adjType = null;
-        app.setAdj = UNKNOWN_ADJ;
-        app.hasShownUi = true;
+        app.mServices.stopService(s);
+        app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
+        app.mState.setAdjType(null);
+        app.mState.setSetAdj(UNKNOWN_ADJ);
+        app.mState.setHasShownUi(true);
         sService.mConstants.KEEP_WARMING_SERVICES.add(cn);
         sService.mConstants.KEEP_WARMING_SERVICES.add(cn2);
         s = spy(new ServiceRecord(sService, cn, cn, null, 0, null,
@@ -1714,17 +1704,17 @@
         s.startRequested = true;
         s.lastActivity = now;
 
-        app.startService(s);
+        app.mServices.startService(s);
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
 
         assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
         assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
 
-        app.setCached(true);
-        app.setProcState = PROCESS_STATE_NONEXISTENT;
-        app.adjType = null;
-        app.setAdj = UNKNOWN_ADJ;
-        app.hasShownUi = false;
+        app.mState.setCached(true);
+        app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
+        app.mState.setAdjType(null);
+        app.mState.setSetAdj(UNKNOWN_ADJ);
+        app.mState.setHasShownUi(false);
         s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
         sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -1773,48 +1763,55 @@
         ai.longVersionCode = versionCode;
         ai.targetSdkVersion = targetSdkVersion;
         ProcessRecord app = new ProcessRecord(service, ai, processName, uid);
-        app.thread = mock(IApplicationThread.class);
-        app.lastActivityTime = lastActivityTime;
-        app.lastPssTime = lastPssTime;
-        app.nextPssTime = nextPssTime;
-        app.lastPss = lastPss;
-        app.maxAdj = maxAdj;
-        app.setRawAdj = setRawAdj;
-        app.curAdj = curAdj;
-        app.setAdj = setAdj;
-        app.setCurrentSchedulingGroup(curSchedGroup);
-        app.setSchedGroup = setSchedGroup;
-        app.setCurProcState(curProcState);
-        app.setReportedProcState(repProcState);
-        app.setCurRawProcState(curRawProcState);
-        app.setProcState = setProcState;
-        app.connectionGroup = connectionGroup;
-        app.connectionImportance = connectionImportance;
-        app.serviceb = serviceb;
-        app.setHasClientActivities(hasClientActivities);
-        app.setHasForegroundServices(hasForegroundServices, fgServiceTypes);
-        app.setHasClientActivities(hasForegroundActivities);
-        app.repForegroundActivities = repForegroundActivities;
-        app.systemNoUi = systemNoUi;
-        app.hasShownUi = hasShownUi;
-        app.setHasTopUi(hasTopUi);
-        app.setHasOverlayUi(hasOverlayUi);
-        app.runningRemoteAnimation = runningRemoteAnimation;
-        app.hasAboveClient = hasAboveClient;
-        app.treatLikeActivity = treatLikeActivity;
-        app.killedByAm = killedByAm;
-        app.forcingToImportant = forcingToImportant;
-        for (int i = 0; i < numOfCurReceivers; i++) {
-            app.curReceivers.add(mock(BroadcastRecord.class));
-        }
-        app.lastProviderTime = lastProviderTime;
-        app.lastTopTime = lastTopTime;
-        app.setCached(cached);
+        final ProcessStateRecord state = app.mState;
+        final ProcessServiceRecord services = app.mServices;
+        final ProcessReceiverRecord receivers = app.mReceivers;
+        final ProcessProfileRecord profile = app.mProfile;
+        final ProcessProviderRecord providers = app.mProviders;
+        app.makeActive(mock(IApplicationThread.class), sService.mProcessStats);
+        app.setLastActivityTime(lastActivityTime);
+        app.setKilledByAm(killedByAm);
+        app.setIsolatedEntryPoint(isolatedEntryPoint);
+        setFieldValue(ProcessRecord.class, app, "mWindowProcessController",
+                mock(WindowProcessController.class));
+        profile.setLastPssTime(lastPssTime);
+        profile.setNextPssTime(nextPssTime);
+        profile.setLastPss(lastPss);
+        state.setMaxAdj(maxAdj);
+        state.setSetRawAdj(setRawAdj);
+        state.setCurAdj(curAdj);
+        state.setSetAdj(setAdj);
+        state.setCurrentSchedulingGroup(curSchedGroup);
+        state.setSetSchedGroup(setSchedGroup);
+        state.setCurProcState(curProcState);
+        state.setReportedProcState(repProcState);
+        state.setCurRawProcState(curRawProcState);
+        state.setSetProcState(setProcState);
+        state.setServiceB(serviceb);
+        state.setRepForegroundActivities(repForegroundActivities);
+        state.setHasForegroundActivities(hasForegroundActivities);
+        state.setSystemNoUi(systemNoUi);
+        state.setHasShownUi(hasShownUi);
+        state.setHasTopUi(hasTopUi);
+        state.setRunningRemoteAnimation(runningRemoteAnimation);
+        state.setHasOverlayUi(hasOverlayUi);
+        state.setCached(cached);
+        state.setLastTopTime(lastTopTime);
+        state.setForcingToImportant(forcingToImportant);
+        services.setConnectionGroup(connectionGroup);
+        services.setConnectionImportance(connectionImportance);
+        services.setHasClientActivities(hasClientActivities);
+        services.setHasForegroundServices(hasForegroundServices, fgServiceTypes);
+        services.setHasAboveClient(hasAboveClient);
+        services.setTreatLikeActivity(treatLikeActivity);
+        services.setExecServicesFg(execServicesFg);
         for (int i = 0; i < numOfExecutingServices; i++) {
-            app.executingServices.add(mock(ServiceRecord.class));
+            services.startExecutingService(mock(ServiceRecord.class));
         }
-        app.isolatedEntryPoint = isolatedEntryPoint;
-        app.execServicesFg = execServicesFg;
+        for (int i = 0; i < numOfCurReceivers; i++) {
+            receivers.addCurReceiver(mock(BroadcastRecord.class));
+        }
+        providers.setLastProviderTime(lastProviderTime);
         return app;
     }
 
@@ -1825,7 +1822,7 @@
             record.app = service;
             setFieldValue(ServiceRecord.class, record, "connections",
                     new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
-            service.startService(record);
+            service.mServices.startService(record);
             doCallRealMethod().when(record).getConnections();
         }
         AppBindRecord binding = new AppBindRecord(record, null, client);
@@ -1836,7 +1833,7 @@
         doCallRealMethod().when(record).addConnection(any(IBinder.class),
                 any(ConnectionRecord.class));
         record.addConnection(binder, cr);
-        client.connections.add(cr);
+        client.mServices.addConnection(cr);
         binding.connections.add(cr);
         doNothing().when(cr).trackProcState(anyInt(), anyInt(), anyLong());
         return record;
@@ -1846,7 +1843,7 @@
             ContentProviderRecord record, String name, boolean hasExternalProviders) {
         if (record == null) {
             record = mock(ContentProviderRecord.class);
-            publisher.pubProviders.put(name, record);
+            publisher.mProviders.installProvider(name, record);
             record.proc = publisher;
             setFieldValue(ContentProviderRecord.class, record, "connections",
                     new ArrayList<ContentProviderConnection>());
@@ -1855,22 +1852,24 @@
         ContentProviderConnection conn = spy(new ContentProviderConnection(record, client,
                 client.info.packageName));
         record.connections.add(conn);
-        client.conProviders.add(conn);
+        client.mProviders.addProviderConnection(conn);
         return record;
     }
 
     private void assertProcStates(ProcessRecord app, int expectedProcState, int expectedAdj,
             int expectedSchedGroup) {
-        assertEquals(expectedProcState, app.setProcState);
-        assertEquals(expectedAdj, app.setAdj);
-        assertEquals(expectedSchedGroup, app.setSchedGroup);
+        final ProcessStateRecord state = app.mState;
+        assertEquals(expectedProcState, state.getSetProcState());
+        assertEquals(expectedAdj, state.getSetAdj());
+        assertEquals(expectedSchedGroup, state.getSetSchedGroup());
     }
 
     private void assertProcStates(ProcessRecord app, boolean expectedCached,
             int expectedProcState, int expectedAdj, String expectedAdjType) {
-        assertEquals(expectedCached, app.isCached());
-        assertEquals(expectedProcState, app.setProcState);
-        assertEquals(expectedAdj, app.setAdj);
-        assertEquals(expectedAdjType, app.adjType);
+        final ProcessStateRecord state = app.mState;
+        assertEquals(expectedCached, state.isCached());
+        assertEquals(expectedProcState, state.getSetProcState());
+        assertEquals(expectedAdj, state.getSetAdj());
+        assertEquals(expectedAdjType, state.getAdjType());
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 84bfc9b..3b5cc88 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -96,6 +96,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -383,7 +384,7 @@
 
         LocationResult loc = createLocationResult(NAME, mRandom);
         mProvider.setProviderLocation(loc);
-        verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class));
+        verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
     }
 
     @Test
@@ -406,13 +407,13 @@
 
         LocationResult loc = createLocationResult(NAME, mRandom);
         mProvider.setProviderLocation(loc);
-        verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class));
+        verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
 
         mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
         verify(listener, timeout(TIMEOUT_MS).times(1)).onProviderEnabledChanged(NAME, false);
         loc = createLocationResult(NAME, mRandom);
         mProvider.setProviderLocation(loc);
-        verify(listener, times(1)).onLocationChanged(any(LocationResult.class),
+        verify(listener, times(1)).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
 
         mInjector.getSettingsHelper().setLocationEnabled(true, CURRENT_USER);
@@ -422,7 +423,7 @@
         verify(listener, timeout(TIMEOUT_MS).times(2)).onProviderEnabledChanged(NAME, false);
         loc = createLocationResult(NAME, mRandom);
         mProvider.setProviderLocation(loc);
-        verify(listener, times(1)).onLocationChanged(any(LocationResult.class),
+        verify(listener, times(1)).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
 
         mProvider.setAllowed(true);
@@ -430,7 +431,7 @@
 
         loc = createLocationResult(NAME, mRandom);
         mProvider.setProviderLocation(loc);
-        verify(listener).onLocationChanged(eq(loc), nullable(IRemoteCallback.class));
+        verify(listener).onLocationChanged(eq(loc.asList()), nullable(IRemoteCallback.class));
     }
 
     @Test
@@ -447,7 +448,7 @@
 
         LocationResult loc = createLocationResult(NAME, mRandom);
         mProvider.setProviderLocation(loc);
-        verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(eq(loc),
+        verify(listener, timeout(TIMEOUT_MS).times(1)).onLocationChanged(eq(loc.asList()),
                 nullable(IRemoteCallback.class));
     }
 
@@ -462,7 +463,7 @@
         mManager.unregisterLocationRequest(listener);
 
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
-        verify(listener, never()).onLocationChanged(any(LocationResult.class),
+        verify(listener, never()).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
 
         mInjector.getSettingsHelper().setLocationEnabled(false, CURRENT_USER);
@@ -493,7 +494,7 @@
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
         mManager.unregisterLocationRequest(listener);
         blocker.countDown();
-        verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(LocationResult.class),
+        verify(listener, after(TIMEOUT_MS).never()).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
     }
 
@@ -513,7 +514,7 @@
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
 
-        verify(listener, times(5)).onLocationChanged(any(LocationResult.class),
+        verify(listener, times(5)).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
     }
 
@@ -528,7 +529,7 @@
 
         mInjector.getAlarmHelper().incrementAlarmTime(5000);
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
-        verify(listener, never()).onLocationChanged(any(LocationResult.class),
+        verify(listener, never()).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
     }
 
@@ -544,7 +545,7 @@
         Thread.sleep(25);
 
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
-        verify(listener, never()).onLocationChanged(any(LocationResult.class),
+        verify(listener, never()).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
     }
 
@@ -561,7 +562,7 @@
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
 
         verify(listener, times(1)).onLocationChanged(
-                any(LocationResult.class), nullable(IRemoteCallback.class));
+                any(List.class), nullable(IRemoteCallback.class));
     }
 
     @Test
@@ -578,7 +579,7 @@
         mProvider.setProviderLocation(loc);
 
         verify(listener, times(1)).onLocationChanged(
-                any(LocationResult.class), nullable(IRemoteCallback.class));
+                any(List.class), nullable(IRemoteCallback.class));
     }
 
     @Test
@@ -592,7 +593,7 @@
 
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
 
-        verify(listener, never()).onLocationChanged(any(LocationResult.class),
+        verify(listener, never()).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
     }
 
@@ -622,7 +623,7 @@
         verify(mWakeLock, never()).release();
 
         blocker.countDown();
-        verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(LocationResult.class),
+        verify(listener, timeout(TIMEOUT_MS)).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
         verify(mWakeLock).acquire(anyLong());
         verify(mWakeLock, timeout(TIMEOUT_MS)).release();
@@ -640,7 +641,7 @@
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
         verify(listener, times(1))
-                .onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class));
+                .onLocationChanged(any(List.class), nullable(IRemoteCallback.class));
     }
 
     @Test
@@ -657,7 +658,7 @@
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
         mProvider.setProviderLocation(createLocation(NAME, mRandom));
         verify(listener, times(1))
-                .onLocationChanged(any(LocationResult.class), nullable(IRemoteCallback.class));
+                .onLocationChanged(any(List.class), nullable(IRemoteCallback.class));
     }
 
     @Test
@@ -746,7 +747,7 @@
         mProvider.completeFlushes();
 
         InOrder inOrder = inOrder(listener);
-        inOrder.verify(listener).onLocationChanged(eq(loc), any(IRemoteCallback.class));
+        inOrder.verify(listener).onLocationChanged(eq(loc.asList()), any(IRemoteCallback.class));
         inOrder.verify(listener).onFlushComplete(99);
     }
 
@@ -838,7 +839,7 @@
                 .build();
         mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
 
-        verify(listener1).onLocationChanged(any(LocationResult.class),
+        verify(listener1).onLocationChanged(any(List.class),
                 nullable(IRemoteCallback.class));
 
         assertThat(mProvider.getRequest().isActive()).isFalse();
@@ -989,7 +990,7 @@
     private ILocationListener createMockLocationListener() {
         return spy(new ILocationListener.Stub() {
             @Override
-            public void onLocationChanged(LocationResult location,
+            public void onLocationChanged(List<Location> locations,
                     IRemoteCallback onCompleteCallback) {
                 if (onCompleteCallback != null) {
                     try {
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
index 99846c5..07170da 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java
@@ -185,8 +185,8 @@
 
     @Test
     public void testReportLocation() {
-        LocationResult realLocation = LocationResult.create(new Location("real"));
-        LocationResult mockLocation = LocationResult.create(new Location("mock"));
+        LocationResult realLocation = LocationResult.wrap(new Location("real"));
+        LocationResult mockLocation = LocationResult.wrap(new Location("mock"));
 
         mRealProvider.reportLocation(realLocation);
         assertThat(mListener.getNextLocationResult()).isEqualTo(realLocation);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index c522541..6e27b3a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -64,6 +64,8 @@
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
+import com.android.server.testutils.TestHandler
 import com.android.server.testutils.mock
 import com.android.server.testutils.nullable
 import com.android.server.testutils.whenever
@@ -142,7 +144,7 @@
         }
         whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
                 nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
-                nullable(), nullable(), nullable())) {
+                nullable(), nullable(), nullable(), nullable())) {
             val name: String = getArgument(0)
             val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
                     ?: return@whenever null
@@ -183,6 +185,8 @@
         val dexManager: DexManager = mock()
         val installer: Installer = mock()
         val displayMetrics: DisplayMetrics = mock()
+        val domainVerificationManagerInternal: DomainVerificationManagerInternal = mock()
+        val handler = TestHandler(null)
     }
 
     companion object {
@@ -258,6 +262,9 @@
         whenever(mocks.injector.userManagerInternal).thenReturn(mocks.userManagerInternal)
         whenever(mocks.injector.installer).thenReturn(mocks.installer)
         whenever(mocks.injector.displayMetrics).thenReturn(mocks.displayMetrics)
+        whenever(mocks.injector.domainVerificationManagerInternal)
+            .thenReturn(mocks.domainVerificationManagerInternal)
+        whenever(mocks.injector.handler) { mocks.handler }
         wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
         whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
         whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UsageStatsServiceTest.java
index c9fcd02..c5e1ed1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/usage/UsageStatsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/usage/UsageStatsServiceTest.java
@@ -37,6 +37,7 @@
 
 import com.android.server.LocalServices;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -82,6 +83,13 @@
         mService.onStart();
     }
 
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
     @Test
     public void testUsageEventListener() throws Exception {
         TestUsageEventListener listener = new TestUsageEventListener();
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 6daa381..7a0cb8e 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -59,9 +59,9 @@
     },
 
     libs: [
-        "android.hardware.power-java",
+        "android.hardware.power-V1-java",
         "android.hardware.tv.cec-V1.0-java",
-        "android.hardware.vibrator-java",
+        "android.hardware.vibrator-V1-java",
         "android.hidl.manager-V1.0-java",
         "android.test.mock",
         "android.test.base",
@@ -88,7 +88,7 @@
         "libui",
         "libunwindstack",
         "libutils",
-        "netd_aidl_interface-cpp",
+        "netd_aidl_interface-V5-cpp",
     ],
 
     dxflags: ["--multi-dex"],
diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
index 0a35db5..f7b2492 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java
@@ -16,34 +16,61 @@
 
 package com.android.server;
 
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
 import android.hardware.vibrator.IVibrator;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.os.CombinedVibrationEffect;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.IVibratorStateListener;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorInfo;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.vibrator.FakeVibrator;
 import com.android.server.vibrator.FakeVibratorControllerProvider;
 import com.android.server.vibrator.VibratorController;
 
@@ -51,12 +78,16 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Predicate;
 
 /**
  * Tests for {@link VibratorManagerService}.
@@ -67,39 +98,86 @@
 @Presubmit
 public class VibratorManagerServiceTest {
 
+    private static final int TEST_TIMEOUT_MILLIS = 1_000;
     private static final int UID = Process.ROOT_UID;
     private static final String PACKAGE_NAME = "package";
+    private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
+    private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
+            .setBatterySaverEnabled(true).build();
     private static final VibrationAttributes ALARM_ATTRS =
             new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
+    private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
+            new VibrationAttributes.Builder().setUsage(
+                    VibrationAttributes.USAGE_TOUCH).build();
+    private static final VibrationAttributes NOTIFICATION_ATTRS =
+            new VibrationAttributes.Builder().setUsage(
+                    VibrationAttributes.USAGE_NOTIFICATION).build();
+    private static final VibrationAttributes RINGTONE_ATTRS =
+            new VibrationAttributes.Builder().setUsage(
+                    VibrationAttributes.USAGE_RINGTONE).build();
 
     @Rule public MockitoRule rule = MockitoJUnit.rule();
+    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock private PackageManagerInternal mPackageManagerInternalMock;
     @Mock private PowerManagerInternal mPowerManagerInternalMock;
     @Mock private PowerSaveState mPowerSaveStateMock;
+    @Mock private AppOpsManager mAppOpsManagerMock;
+    @Mock private IInputManager mIInputManagerMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
+    private Context mContextSpy;
     private TestLooper mTestLooper;
+    private FakeVibrator mVibrator;
+    private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
 
     @Before
     public void setUp() throws Exception {
         mTestLooper = new TestLooper();
+        mVibrator = new FakeVibrator();
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        InputManager inputManager = InputManager.resetInstance(mIInputManagerMock);
 
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+        when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mVibrator);
+        when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager);
+        when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock);
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
+        when(mPackageManagerInternalMock.getSystemUiServiceComponent())
+                .thenReturn(new ComponentName("", ""));
         when(mPowerManagerInternalMock.getLowPowerState(PowerManager.ServiceType.VIBRATION))
                 .thenReturn(mPowerSaveStateMock);
+        doAnswer(invocation -> {
+            mRegisteredPowerModeListener = invocation.getArgument(0);
+            return null;
+        }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
 
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+
+        addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
         addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+
+        mTestLooper.startAutoDispatch();
     }
 
     @After
     public void tearDown() throws Exception {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
     }
 
     private VibratorManagerService createService() {
         VibratorManagerService service = new VibratorManagerService(
-                InstrumentationRegistry.getContext(),
+                mContextSpy,
                 new VibratorManagerService.Injector() {
                     @Override
                     VibratorManagerService.NativeWrapper getNativeWrapper() {
@@ -172,6 +250,74 @@
     }
 
     @Test
+    public void registerVibratorStateListener_callbacksAreTriggered() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createService();
+        IVibratorStateListener listenerMock = mockVibratorStateListener();
+        service.registerVibratorStateListener(1, listenerMock);
+
+        vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+        // Wait until service knows vibrator is on.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+        // Wait until effect ends.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        InOrder inOrderVerifier = inOrder(listenerMock);
+        // First notification done when listener is registered.
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        inOrderVerifier.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createService();
+        IVibratorStateListener listenerMock = mockVibratorStateListener();
+        service.registerVibratorStateListener(1, listenerMock);
+
+        vibrate(service, VibrationEffect.createOneShot(40, 100), ALARM_ATTRS);
+
+        // Wait until service knows vibrator is on.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.unregisterVibratorStateListener(1, listenerMock);
+
+        // Wait until vibrator is off.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        InOrder inOrderVerifier = inOrder(listenerMock);
+        // First notification done when listener is registered.
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+        inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
+        inOrderVerifier.verify(listenerMock, atLeastOnce()).asBinder(); // unregister
+        inOrderVerifier.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void registerVibratorStateListener_multipleVibratorsAreTriggered() throws Exception {
+        mockVibrators(0, 1, 2);
+        VibratorManagerService service = createService();
+        IVibratorStateListener[] listeners = new IVibratorStateListener[3];
+        for (int i = 0; i < 3; i++) {
+            listeners[i] = mockVibratorStateListener();
+            service.registerVibratorStateListener(i, listeners[i]);
+        }
+
+        vibrate(service, CombinedVibrationEffect.startSynced()
+                .addVibrator(0, VibrationEffect.createOneShot(40, 100))
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine(), ALARM_ATTRS);
+        // Wait until service knows vibrator is on.
+        assertTrue(waitUntil(s -> s.isVibrating(0), service, TEST_TIMEOUT_MILLIS));
+
+        verify(listeners[0]).onVibrating(eq(true));
+        verify(listeners[1]).onVibrating(eq(true));
+        verify(listeners[2], never()).onVibrating(eq(true));
+    }
+
+    @Test
     public void setAlwaysOnEffect_withMono_enablesAlwaysOnEffectToAllVibratorsWithCapability() {
         mockVibrators(1, 2, 3);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_ALWAYS_ON_CONTROL);
@@ -276,22 +422,264 @@
     }
 
     @Test
-    public void vibrate_isUnsupported() {
+    public void vibrate_withRingtone_usesRingtoneSettings() throws Exception {
+        mockVibrators(1);
+        mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
         VibratorManagerService service = createService();
-        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-        assertExpectException(UnsupportedOperationException.class,
-                "Not implemented",
-                () -> service.vibrate(UID, PACKAGE_NAME, effect, ALARM_ATTRS, "reason", service));
+        vibrate(service, VibrationEffect.createOneShot(40, 1), RINGTONE_ATTRS);
+
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+        service = createService();
+        vibrate(service, VibrationEffect.createOneShot(40, 10), RINGTONE_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+        setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+        service = createService();
+        vibrate(service, VibrationEffect.createOneShot(40, 100), RINGTONE_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        assertEquals(2, mVibratorProviders.get(1).getEffects().size());
+        assertEquals(Arrays.asList(10, 100), mVibratorProviders.get(1).getAmplitudes());
     }
 
     @Test
-    public void cancelVibrate_isUnsupported() {
+    public void vibrate_withPowerMode_usesPowerModeState() throws Exception {
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         VibratorManagerService service = createService();
-        CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-        assertExpectException(UnsupportedOperationException.class,
-                "Not implemented", () -> service.cancelVibrate(service));
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+        vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS);
+        vibrate(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+                service, TEST_TIMEOUT_MILLIS));
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+        vibrate(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+                service, TEST_TIMEOUT_MILLIS));
+
+        assertEquals(Arrays.asList(2, 3, 4), fakeVibrator.getAmplitudes());
+    }
+
+    @Test
+    public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() {
+        VibratorManagerService service = createService();
+
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+        AudioAttributes audioAttributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build();
+        VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder(
+                audioAttributes, effect).build();
+
+        vibrate(service, effect, vibrationAttributes);
+
+        verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString());
+    }
+
+    @Test
+    public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() {
+        VibratorManagerService service = createService();
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                new VibrationAttributes.Builder().setUsage(
+                        VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build());
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK),
+                new VibrationAttributes.Builder().setUsage(
+                        VibrationAttributes.USAGE_UNKNOWN).build());
+
+        InOrder inOrderVerifier = inOrder(mAppOpsManagerMock);
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST),
+                anyInt(), anyString());
+        inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE),
+                eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
+    }
+
+    @Test
+    public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1));
+        setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
+        VibratorManagerService service = createService();
+
+        // Prebaked vibration will play fallback waveform on input device.
+        ArgumentCaptor<VibrationEffect> captor = ArgumentCaptor.forClass(VibrationEffect.class);
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+        verify(mIInputManagerMock).vibrate(eq(1), captor.capture(), any());
+        assertTrue(captor.getValue() instanceof VibrationEffect.Waveform);
+
+        VibrationEffect[] effects = new VibrationEffect[]{
+                VibrationEffect.createOneShot(100, 128),
+                VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1),
+                VibrationEffect.startComposition()
+                        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+                        .compose(),
+        };
+
+        for (VibrationEffect effect : effects) {
+            vibrate(service, effect, ALARM_ATTRS);
+            verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any());
+        }
+
+        // VibrationThread will start this vibration async, so wait before checking it never played.
+        assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffects().isEmpty(),
+                service, /* timeout= */ 50));
+    }
+
+    @Test
+    public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS,
+                IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createService();
+        // The native callback will be dispatched manually in this test.
+        mTestLooper.stopAutoDispatchAndIgnoreExceptions();
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async, so wait before triggering callbacks.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        // Trigger callbacks from controller.
+        mTestLooper.moveTimeForward(50);
+        mTestLooper.dispatchAll();
+
+        // VibrationThread needs some time to react to native callbacks and stop the vibrator.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+
+    @Test
+    public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
+        mVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_HIGH);
+        setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_LOW);
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_OFF);
+
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
+                IVibrator.CAP_COMPOSE_EFFECTS);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createService();
+
+        vibrate(service, CombinedVibrationEffect.startSynced()
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .combine(), ALARM_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 1,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, CombinedVibrationEffect.startSequential()
+                .addNext(1, VibrationEffect.createOneShot(20, 100))
+                .combine(), NOTIFICATION_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 2,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+                .compose(), HAPTIC_FEEDBACK_ATTRS);
+        assertTrue(waitUntil(s -> fakeVibrator.getEffects().size() == 3,
+                service, TEST_TIMEOUT_MILLIS));
+
+        vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
+
+        assertEquals(3, fakeVibrator.getEffects().size());
+        assertEquals(1, fakeVibrator.getAmplitudes().size());
+
+        // Alarm vibration is always VIBRATION_INTENSITY_HIGH.
+        VibrationEffect expected = new VibrationEffect.Prebaked(VibrationEffect.EFFECT_CLICK, false,
+                VibrationEffect.EFFECT_STRENGTH_STRONG);
+        assertEquals(expected, fakeVibrator.getEffects().get(0));
+
+        // Notification vibrations will be scaled with SCALE_VERY_HIGH.
+        assertTrue(150 < fakeVibrator.getAmplitudes().get(0));
+
+        // Haptic feedback vibrations will be scaled with SCALE_LOW.
+        VibrationEffect.Composed played =
+                (VibrationEffect.Composed) fakeVibrator.getEffects().get(2);
+        assertTrue(0.5 < played.getPrimitiveEffects().get(0).scale);
+        assertTrue(0.5 > played.getPrimitiveEffects().get(1).scale);
+
+        // Ring vibrations have intensity OFF and are not played.
+    }
+
+    @Test
+    public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
+        mockVibrators(1, 2);
+        VibratorManagerService service = createService();
+        vibrate(service,
+                CombinedVibrationEffect.startSynced()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+        // Haptic feedback cancelled on low power mode.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+    }
+
+    @Test
+    public void vibrate_withSettingsChange_doNotCancelVibration() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createService();
+
+        vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.updateServiceState();
+        // Vibration is not stopped nearly after updating service.
+        assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
+    }
+
+    @Test
+    public void cancelVibrate_stopsVibrating() throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createService();
+
+        service.cancelVibrate(service);
+        assertFalse(service.isVibrating(1));
+
+        vibrate(service, VibrationEffect.createOneShot(10_000, 100), ALARM_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        service.cancelVibrate(service);
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
     }
 
     private void mockVibrators(int... vibratorIds) {
@@ -302,8 +690,56 @@
         when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
     }
 
+    private IVibratorStateListener mockVibratorStateListener() {
+        IVibratorStateListener listenerMock = mock(IVibratorStateListener.class);
+        IBinder binderMock = mock(IBinder.class);
+        when(listenerMock.asBinder()).thenReturn(binderMock);
+        return listenerMock;
+    }
+
+    private InputDevice createInputDeviceWithVibrator(int id) {
+        return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
+                null, /* hasVibrator= */ true, false, false, false, false);
+    }
+
     private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
         LocalServices.removeServiceForTest(clazz);
         LocalServices.addService(clazz, mock);
     }
+
+    private void setRingerMode(int ringerMode) {
+        AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class);
+        audioManager.setRingerModeInternal(ringerMode);
+        assertEquals(ringerMode, audioManager.getRingerModeInternal());
+    }
+
+    private void setUserSetting(String settingName, int value) {
+        Settings.System.putIntForUser(
+                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+    }
+
+    private void setGlobalSetting(String settingName, int value) {
+        Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value);
+    }
+
+    private void vibrate(VibratorManagerService service, VibrationEffect effect,
+            VibrationAttributes attrs) {
+        vibrate(service, CombinedVibrationEffect.createSynced(effect), attrs);
+    }
+
+    private void vibrate(VibratorManagerService service, CombinedVibrationEffect effect,
+            VibrationAttributes attrs) {
+        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+    }
+
+    private boolean waitUntil(Predicate<VibratorManagerService> predicate,
+            VibratorManagerService service, long timeout) throws InterruptedException {
+        long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
+        boolean predicateResult = false;
+        while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) {
+            Thread.sleep(10);
+            predicateResult = predicate.test(service);
+        }
+        return predicateResult;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
index 743848c..2a7905a 100644
--- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java
@@ -582,16 +582,19 @@
         VibratorService service = createService();
 
         service.registerVibratorStateListener(mVibratorStateListenerMock);
-        verify(mVibratorStateListenerMock).onVibrating(false);
 
-        vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS);
+        vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS);
 
         // VibrationThread will start this vibration async, so wait before triggering callbacks.
         Thread.sleep(10);
+        assertTrue(service.isVibrating());
+
         service.unregisterVibratorStateListener(mVibratorStateListenerMock);
         // Trigger callbacks from controller.
-        mTestLooper.moveTimeForward(150);
+        mTestLooper.moveTimeForward(50);
         mTestLooper.dispatchAll();
+        Thread.sleep(20);
+        assertFalse(service.isVibrating());
 
         InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock);
         // First notification done when listener is registered.
@@ -745,7 +748,8 @@
 
     private InputDevice createInputDeviceWithVibrator(int id) {
         return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
-                null, /* hasVibrator= */ true, false, false, false /* hasSensor */);
+                null, /* hasVibrator= */ true, false, false, false /* hasSensor */,
+                false /* hasBattery */);
     }
 
     private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index df8a720..110bb21 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -164,9 +164,7 @@
     @SmallTest
     public void testRegisterSystemActionWithoutPermission() throws Exception {
         doThrow(SecurityException.class).when(mMockSecurityPolicy)
-                .enforceCallerIsRecentsOrHasPermission(
-                        Manifest.permission.MANAGE_ACCESSIBILITY,
-                        AccessibilityManagerService.FUNCTION_REGISTER_SYSTEM_ACTION);
+                .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         try {
             mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
@@ -185,9 +183,7 @@
     @SmallTest
     public void testUnregisterSystemActionWithoutPermission() throws Exception {
         doThrow(SecurityException.class).when(mMockSecurityPolicy)
-                .enforceCallerIsRecentsOrHasPermission(
-                        Manifest.permission.MANAGE_ACCESSIBILITY,
-                        AccessibilityManagerService.FUNCTION_UNREGISTER_SYSTEM_ACTION);
+                .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
 
         try {
             mA11yms.unregisterSystemAction(ACTION_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index cc8ac86..c7e7c78 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -51,9 +51,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityWindowInfo;
 
-import com.android.server.LocalServices;
-import com.android.server.wm.ActivityTaskManagerInternal;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -123,7 +120,6 @@
     @Mock private AccessibilityWindowManager mMockA11yWindowManager;
     @Mock private AppWidgetManagerInternal mMockAppWidgetManager;
     @Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
-    @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
 
     @Before
     public void setUp() {
@@ -132,10 +128,6 @@
         when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
         when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mMockAppOpsManager);
 
-        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
-        LocalServices.addService(
-                ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
-
         mA11ySecurityPolicy = new AccessibilitySecurityPolicy(mMockContext, mMockA11yUserManager);
         mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager);
         mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager);
@@ -570,10 +562,4 @@
                 APP_UID, PACKAGE_NAME);
     }
 
-    @Test
-    public void testEnforceCallerIsRecentsOrHasPermission() {
-        mA11ySecurityPolicy.enforceCallerIsRecentsOrHasPermission(PERMISSION, FUNCTION);
-        verify(mMockActivityTaskManagerInternal).enforceCallerIsRecentsOrHasPermission(
-                PERMISSION, FUNCTION);
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
index 3b4699e..8c21a39 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -134,7 +134,7 @@
 
     private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
             long lastNetworkUpdatedProcStateSeq) {
-        final UidRecord record = new UidRecord(uid);
+        final UidRecord record = new UidRecord(uid, mAms);
         record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
         record.curProcStateSeq = curProcStateSeq;
         record.waitingForNetwork = true;
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index e119d52..f314008 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -273,7 +273,7 @@
     }
 
     private UidRecord addUidRecord(int uid) {
-        final UidRecord uidRec = new UidRecord(uid);
+        final UidRecord uidRec = new UidRecord(uid, mAms);
         uidRec.waitingForNetwork = true;
         uidRec.hasInternetPermission = true;
         mAms.mProcessList.mActiveUids.put(uid, uidRec);
@@ -282,8 +282,9 @@
         info.packageName = "";
 
         final ProcessRecord appRec = new ProcessRecord(mAms, info, TAG, uid);
-        appRec.thread = mock(IApplicationThread.class);
-        mAms.mProcessList.mLruProcesses.add(appRec);
+        final ProcessStatsService tracker = new ProcessStatsService(mAms, mContext.getCacheDir());
+        appRec.makeActive(mock(IApplicationThread.class), tracker);
+        mAms.mProcessList.getLruProcessesLSP().add(appRec);
 
         return uidRec;
     }
@@ -295,23 +296,23 @@
         CustomThread thread = new CustomThread(uidRec.networkStateLock);
         thread.startAndWait("Unexpected state for " + uidRec);
 
-        uidRec.setProcState = prevState;
+        uidRec.setSetProcState(prevState);
         uidRec.setCurProcState(curState);
-        mAms.mProcessList.incrementProcStateSeqAndNotifyAppsLocked(mAms.mProcessList.mActiveUids);
+        mAms.mProcessList.incrementProcStateSeqAndNotifyAppsLOSP(mAms.mProcessList.mActiveUids);
 
         // @SuppressWarnings("GuardedBy")
         assertEquals(expectedGlobalCounter, mAms.mProcessList.mProcStateSeqCounter);
         assertEquals(expectedCurProcStateSeq, uidRec.curProcStateSeq);
 
-        for (int i = mAms.mProcessList.getLruSizeLocked() - 1; i >= 0; --i) {
-            final ProcessRecord app = mAms.mProcessList.mLruProcesses.get(i);
+        for (int i = mAms.mProcessList.getLruSizeLOSP() - 1; i >= 0; --i) {
+            final ProcessRecord app = mAms.mProcessList.getLruProcessesLOSP().get(i);
             // AMS should notify apps only for block states other than NETWORK_STATE_NO_CHANGE.
-            if (app.uid == uidRec.uid && expectedBlockState == NETWORK_STATE_BLOCK) {
-                verify(app.thread).setNetworkBlockSeq(uidRec.curProcStateSeq);
+            if (app.uid == uidRec.getUid() && expectedBlockState == NETWORK_STATE_BLOCK) {
+                verify(app.getThread()).setNetworkBlockSeq(uidRec.curProcStateSeq);
             } else {
-                verifyZeroInteractions(app.thread);
+                verifyZeroInteractions(app.getThread());
             }
-            Mockito.reset(app.thread);
+            Mockito.reset(app.getThread());
         }
 
         if (expectNotify) {
@@ -446,55 +447,56 @@
 
     @Test
     public void testBlockStateForUid() {
-        final UidRecord uidRec = new UidRecord(TEST_UID);
+        final UidRecord uidRec = new UidRecord(TEST_UID, mAms);
         int expectedBlockState;
 
         final String errorTemplate = "Block state should be %s, prevState: %s, curState: %s";
         Function<Integer, String> errorMsg = (blockState) -> {
             return String.format(errorTemplate,
                     valueToString(ActivityManagerService.class, "NETWORK_STATE_", blockState),
-                    valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.setProcState),
+                    valueToString(ActivityManager.class, "PROCESS_STATE_",
+                        uidRec.getSetProcState()),
                     valueToString(ActivityManager.class, "PROCESS_STATE_", uidRec.getCurProcState())
             );
         };
 
         // No change in uid state
-        uidRec.setProcState = PROCESS_STATE_RECEIVER;
+        uidRec.setSetProcState(PROCESS_STATE_RECEIVER);
         uidRec.setCurProcState(PROCESS_STATE_RECEIVER);
         expectedBlockState = NETWORK_STATE_NO_CHANGE;
         assertEquals(errorMsg.apply(expectedBlockState),
                 expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
 
         // Foreground to foreground
-        uidRec.setProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+        uidRec.setSetProcState(PROCESS_STATE_FOREGROUND_SERVICE);
         uidRec.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
         expectedBlockState = NETWORK_STATE_NO_CHANGE;
         assertEquals(errorMsg.apply(expectedBlockState),
                 expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
 
         // Background to background
-        uidRec.setProcState = PROCESS_STATE_CACHED_ACTIVITY;
+        uidRec.setSetProcState(PROCESS_STATE_CACHED_ACTIVITY);
         uidRec.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
         expectedBlockState = NETWORK_STATE_NO_CHANGE;
         assertEquals(errorMsg.apply(expectedBlockState),
                 expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
 
         // Background to background
-        uidRec.setProcState = PROCESS_STATE_NONEXISTENT;
+        uidRec.setSetProcState(PROCESS_STATE_NONEXISTENT);
         uidRec.setCurProcState(PROCESS_STATE_CACHED_ACTIVITY);
         expectedBlockState = NETWORK_STATE_NO_CHANGE;
         assertEquals(errorMsg.apply(expectedBlockState),
                 expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
 
         // Background to foreground
-        uidRec.setProcState = PROCESS_STATE_SERVICE;
+        uidRec.setSetProcState(PROCESS_STATE_SERVICE);
         uidRec.setCurProcState(PROCESS_STATE_FOREGROUND_SERVICE);
         expectedBlockState = NETWORK_STATE_BLOCK;
         assertEquals(errorMsg.apply(expectedBlockState),
                 expectedBlockState, mAms.mProcessList.getBlockStateForUid(uidRec));
 
         // Foreground to background
-        uidRec.setProcState = PROCESS_STATE_TOP;
+        uidRec.setSetProcState(PROCESS_STATE_TOP);
         uidRec.setCurProcState(PROCESS_STATE_LAST_ACTIVITY);
         expectedBlockState = NETWORK_STATE_UNBLOCK;
         assertEquals(errorMsg.apply(expectedBlockState),
@@ -748,13 +750,13 @@
                         item.procState, validateUidRecord.getCurProcState());
                 assertEquals("processState: " + item.procState + " setProcState: "
                         + validateUidRecord.getCurProcState() + " should have been equal",
-                        item.procState, validateUidRecord.setProcState);
+                        item.procState, validateUidRecord.getSetProcState());
                 if (item.change == UidRecord.CHANGE_IDLE) {
                     assertTrue("UidRecord.idle should be updated to true for CHANGE_IDLE",
-                            validateUidRecord.idle);
+                            validateUidRecord.isIdle());
                 } else if (item.change == UidRecord.CHANGE_ACTIVE) {
                     assertFalse("UidRecord.idle should be updated to false for CHANGE_ACTIVE",
-                            validateUidRecord.idle);
+                            validateUidRecord.isIdle());
                 }
             }
         }
@@ -779,7 +781,7 @@
 
     @Test
     public void testEnqueueUidChangeLocked_procStateSeqUpdated() {
-        final UidRecord uidRecord = new UidRecord(TEST_UID);
+        final UidRecord uidRecord = new UidRecord(TEST_UID, mAms);
         uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
 
         // Verify with no pending changes for TEST_UID.
@@ -823,9 +825,9 @@
     @MediumTest
     @Test
     public void testEnqueueUidChangeLocked_dispatchUidsChanged() {
-        final UidRecord uidRecord = new UidRecord(TEST_UID);
+        final UidRecord uidRecord = new UidRecord(TEST_UID, mAms);
         final int expectedProcState = PROCESS_STATE_SERVICE;
-        uidRecord.setProcState = expectedProcState;
+        uidRecord.setSetProcState(expectedProcState);
         uidRecord.curProcStateSeq = TEST_PROC_STATE_SEQ1;
 
         // Test with no pending uid records.
@@ -895,7 +897,7 @@
     private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
             long lastDispatchedProcStateSeq, long lastNetworkUpdatedProcStateSeq,
             final long procStateSeqToWait, boolean expectWait) throws Exception {
-        final UidRecord record = new UidRecord(Process.myUid());
+        final UidRecord record = new UidRecord(Process.myUid(), mAms);
         record.curProcStateSeq = curProcStateSeq;
         record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
         record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index 52c824a..432f6d6 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -33,6 +33,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -48,7 +50,26 @@
 
     @Before
     public void setUp() {
-        runWithDexmakerShareClassLoader(() -> mAnrApp = mock(ProcessRecord.class));
+        runWithDexmakerShareClassLoader(() -> {
+            mAnrApp = mock(ProcessRecord.class);
+            final ActivityManagerService service = mock(ActivityManagerService.class);
+            final ProcessErrorStateRecord errorState = mock(ProcessErrorStateRecord.class);
+            setFieldValue(ProcessErrorStateRecord.class, errorState, "mProcLock",
+                    new ActivityManagerProcLock());
+            setFieldValue(ProcessRecord.class, mAnrApp, "mErrorState", errorState);
+        });
+    }
+
+    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            Field mfield = Field.class.getDeclaredField("accessFlags");
+            mfield.setAccessible(true);
+            mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+            field.set(obj, val);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+        }
     }
 
     @Test
@@ -62,7 +83,7 @@
         mAnrHelper.appNotResponding(mAnrApp, activityShortComponentName, appInfo,
                 parentShortComponentName, parentProcess, aboveSystem, annotation);
 
-        verify(mAnrApp, timeout(TimeUnit.SECONDS.toMillis(5))).appNotResponding(
+        verify(mAnrApp.mErrorState, timeout(TimeUnit.SECONDS.toMillis(5))).appNotResponding(
                 eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
                 eq(parentProcess), eq(aboveSystem), eq(annotation), eq(false) /* onlyDumpSelf */);
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
new file mode 100644
index 0000000..fdf5095
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2021 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.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.hardware.power.stats.Channel;
+import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerResult;
+import android.hardware.power.stats.EnergyConsumerType;
+import android.hardware.power.stats.EnergyMeasurement;
+import android.hardware.power.stats.PowerEntity;
+import android.hardware.power.stats.StateResidencyResult;
+import android.power.PowerStatsInternal;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.power.MeasuredEnergyArray;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Tests for {@link BatteryExternalStatsWorker}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:BatteryExternalStatsWorkerTest
+ */
+public class BatteryExternalStatsWorkerTest {
+    private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
+    private TestBatteryStatsImpl mBatteryStatsImpl;
+    private TestPowerStatsInternal mPowerStatsInternal;
+
+    @Before
+    public void setUp() {
+        final Context context = InstrumentationRegistry.getContext();
+
+        mBatteryStatsImpl = new TestBatteryStatsImpl();
+        mPowerStatsInternal = new TestPowerStatsInternal();
+        mBatteryExternalStatsWorker = new BatteryExternalStatsWorker(new TestInjector(context),
+                mBatteryStatsImpl);
+    }
+
+    @Test
+    public void getEnergyConsumptionData() {
+        SparseLongArray expectSubsystems = new SparseLongArray();
+        // Add some energy consumers used by BatteryExternalStatsWorker.
+        final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
+                "display");
+        mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
+        expectSubsystems.put(MeasuredEnergyArray.SUBSYSTEM_DISPLAY, 12345);
+
+        // Add an arbitrary energy consumer unused by BatteryExternalStatsWorker.
+        // Must be changed if '154' ever becomes an EnergyConsumerType used by BESW.
+        final int someId = mPowerStatsInternal.addEnergyConsumer((byte) 154, 0, "some_consumer");
+        mPowerStatsInternal.incrementEnergyConsumption(someId, 34567);
+
+        // Inform BESW that PowerStatsInternal is ready to query
+        mBatteryExternalStatsWorker.systemServicesReady();
+
+        MeasuredEnergyArray energies = mBatteryExternalStatsWorker.getEnergyConsumptionData();
+
+        assertEquals(expectSubsystems.size(), energies.size());
+        final int size = expectSubsystems.size();
+
+        for (int i = 0; i < size; i++) {
+            int subsystem = expectSubsystems.keyAt(i);
+            // find the subsystem in the returned MeasuredEnergyArray
+            int subsystemIndex = -1;
+            for (int j = 0; j < size; j++) {
+                if (subsystem == energies.getSubsystem(i)) {
+                    subsystemIndex = i;
+                    break;
+                }
+            }
+            assertNotEquals("Subsystem " + subsystem + " not found in MeasuredEnergyArray", -1,
+                    subsystemIndex);
+            assertEquals(expectSubsystems.valueAt(i), energies.getEnergy(subsystemIndex));
+        }
+    }
+
+    public class TestInjector extends BatteryExternalStatsWorker.Injector {
+        public TestInjector(Context context) {
+            super(context);
+        }
+
+        public <T> T getSystemService(Class<T> serviceClass) {
+            return null;
+        }
+
+        public <T> T getLocalService(Class<T> serviceClass) {
+            if (serviceClass == PowerStatsInternal.class) {
+                return (T) mPowerStatsInternal;
+            }
+            return null;
+        }
+    }
+
+    public class TestBatteryStatsImpl extends BatteryStatsImpl {
+    }
+
+    public class TestPowerStatsInternal extends PowerStatsInternal {
+        private final SparseArray<EnergyConsumer> mEnergyConsumers = new SparseArray<>();
+        private final SparseArray<EnergyConsumerResult> mEnergyConsumerResults =
+                new SparseArray<>();
+        private final int mTimeSinceBoot = 0;
+
+        @Override
+        public EnergyConsumer[] getEnergyConsumerInfo() {
+            final int size = mEnergyConsumers.size();
+            final EnergyConsumer[] consumers = new EnergyConsumer[size];
+            for (int i = 0; i < size; i++) {
+                consumers[i] = mEnergyConsumers.valueAt(i);
+            }
+            return consumers;
+        }
+
+        @Override
+        public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
+                int[] energyConsumerIds) {
+            final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture();
+            final int size = mEnergyConsumerResults.size();
+            final EnergyConsumerResult[] results = new EnergyConsumerResult[size];
+            for (int i = 0; i < size; i++) {
+                results[i] = mEnergyConsumerResults.valueAt(i);
+            }
+            future.complete(results);
+            return future;
+        }
+
+        @Override
+        public PowerEntity[] getPowerEntityInfo() {
+            return new PowerEntity[0];
+        }
+
+        @Override
+        public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
+                int[] powerEntityIds) {
+            return new CompletableFuture<>();
+        }
+
+        @Override
+        public Channel[] getEnergyMeterInfo() {
+            return new Channel[0];
+        }
+
+        @Override
+        public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
+                int[] channelIds) {
+            return new CompletableFuture<>();
+        }
+
+        /**
+         * Util method to add a new EnergyConsumer for testing
+         *
+         * @return the EnergyConsumer id of the new EnergyConsumer
+         */
+        public int addEnergyConsumer(@EnergyConsumerType byte type, int ordinal, String name) {
+            final EnergyConsumer consumer = new EnergyConsumer();
+            final int id = getNextAvailableId();
+            consumer.id = id;
+            consumer.type = type;
+            consumer.ordinal = ordinal;
+            consumer.name = name;
+            mEnergyConsumers.put(id, consumer);
+
+            final EnergyConsumerResult result = new EnergyConsumerResult();
+            result.id = id;
+            result.timestampMs = mTimeSinceBoot;
+            result.energyUWs = 0;
+            mEnergyConsumerResults.put(id, result);
+            return id;
+        }
+
+        public void incrementEnergyConsumption(int id, long energyUWs) {
+            EnergyConsumerResult result = mEnergyConsumerResults.get(id, null);
+            assertNotNull(result);
+            result.energyUWs += energyUWs;
+        }
+
+        private int getNextAvailableId() {
+            final int size = mEnergyConsumers.size();
+            // Just return the first index that does not match the key (aka the EnergyConsumer id)
+            for (int i = size - 1; i >= 0; i--) {
+                if (mEnergyConsumers.keyAt(i) == i) return i + 1;
+            }
+            // Otherwise return the lowest id
+            return 0;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index 4f58c87..638b1b4 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -40,6 +40,9 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
 /**
  * Test class for {@link OomAdjuster}.
  *
@@ -74,8 +77,13 @@
             sService.mActivityTaskManager.initialize(null, null, sContext.getMainLooper());
             sService.mAtmInternal = sService.mActivityTaskManager.getAtmInternal();
 
+            setFieldValue(ActivityManagerService.class, sService, "mProcLock",
+                    new ActivityManagerProcLock());
             sService.mConstants = new ActivityManagerConstants(sContext, sService,
                     sContext.getMainThreadHandler());
+            final AppProfiler profiler = mock(AppProfiler.class);
+            setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
+            setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
             sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, null);
             LocalServices.addService(UsageStatsManagerInternal.class,
                     mock(UsageStatsManagerInternal.class));
@@ -83,6 +91,18 @@
         });
     }
 
+    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            Field mfield = Field.class.getDeclaredField("accessFlags");
+            mfield.setAccessible(true);
+            mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+            field.set(obj, val);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+        }
+    }
+
     @AfterClass
     public static void tearDownOnce() {
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -106,7 +126,7 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStatePersistentUI() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, elapsedTime);
@@ -115,7 +135,7 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateTop() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, elapsedTime);
@@ -124,8 +144,8 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateTop_PreviousInteraction() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
-        mProcessRecord.reportedInteraction = true;
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
+        mProcessRecord.mState.setReportedInteraction(true);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, ZERO);
@@ -134,8 +154,8 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateTop_PastUsageInterval() {
         final long elapsedTime = 3 * USAGE_STATS_INTERACTION;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
-        mProcessRecord.reportedInteraction = true;
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_TOP);
+        mProcessRecord.mState.setReportedInteraction(true);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, elapsedTime);
@@ -144,7 +164,7 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateBoundTop() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_TOP);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_TOP);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, elapsedTime);
@@ -153,7 +173,7 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateFGS() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(elapsedTime, false, ZERO);
@@ -163,8 +183,8 @@
     public void testMaybeUpdateUsageStats_ProcStateFGS_ShortInteraction() {
         final long elapsedTime = ZERO;
         final long fgInteractionTime = 1000L;
-        mProcessRecord.setFgInteractionTime(fgInteractionTime);
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mProcessRecord.mState.setFgInteractionTime(fgInteractionTime);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(fgInteractionTime, false, ZERO);
@@ -174,8 +194,8 @@
     public void testMaybeUpdateUsageStats_ProcStateFGS_LongInteraction() {
         final long elapsedTime = 2 * SERVICE_USAGE_INTERACTION;
         final long fgInteractionTime = 1000L;
-        mProcessRecord.setFgInteractionTime(fgInteractionTime);
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mProcessRecord.mState.setFgInteractionTime(fgInteractionTime);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(fgInteractionTime, true, elapsedTime);
@@ -185,9 +205,9 @@
     public void testMaybeUpdateUsageStats_ProcStateFGS_PreviousLongInteraction() {
         final long elapsedTime = 2 * SERVICE_USAGE_INTERACTION;
         final long fgInteractionTime = 1000L;
-        mProcessRecord.setFgInteractionTime(fgInteractionTime);
-        mProcessRecord.reportedInteraction = true;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mProcessRecord.mState.setFgInteractionTime(fgInteractionTime);
+        mProcessRecord.mState.setReportedInteraction(true);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(fgInteractionTime, true, ZERO);
@@ -196,7 +216,7 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateFGSLocation() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(elapsedTime, false, ZERO);
@@ -205,7 +225,8 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateBFGS() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+        mProcessRecord.mState.setCurProcState(
+                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, elapsedTime);
@@ -214,7 +235,7 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateImportantFG() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, elapsedTime);
@@ -223,8 +244,8 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateImportantFG_PreviousInteraction() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
-        mProcessRecord.reportedInteraction = true;
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+        mProcessRecord.mState.setReportedInteraction(true);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, ZERO);
@@ -233,8 +254,8 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateImportantFG_PastUsageInterval() {
         final long elapsedTime = 3 * USAGE_STATS_INTERACTION;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
-        mProcessRecord.reportedInteraction = true;
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
+        mProcessRecord.mState.setReportedInteraction(true);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, true, elapsedTime);
@@ -243,7 +264,7 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateImportantBG() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, false, ZERO);
@@ -252,7 +273,7 @@
     @Test
     public void testMaybeUpdateUsageStats_ProcStateService() {
         final long elapsedTime = ZERO;
-        mProcessRecord.setCurProcState(ActivityManager.PROCESS_STATE_SERVICE);
+        mProcessRecord.mState.setCurProcState(ActivityManager.PROCESS_STATE_SERVICE);
         sService.mOomAdjuster.maybeUpdateUsageStats(mProcessRecord, elapsedTime);
 
         assertProcessRecordState(ZERO, false, ZERO);
@@ -261,10 +282,10 @@
     private void assertProcessRecordState(long fgInteractionTime, boolean reportedInteraction,
             long interactionEventTime) {
         assertEquals("Foreground interaction time was not updated correctly.",
-                fgInteractionTime, mProcessRecord.getFgInteractionTime());
+                fgInteractionTime, mProcessRecord.mState.getFgInteractionTime());
         assertEquals("Interaction was not updated correctly.",
-                reportedInteraction, mProcessRecord.reportedInteraction);
+                reportedInteraction, mProcessRecord.mState.hasReportedInteraction());
         assertEquals("Interaction event time was not updated correctly.",
-                interactionEventTime, mProcessRecord.getInteractionEventTime());
+                interactionEventTime, mProcessRecord.mState.getInteractionEventTime());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
index ded14b8..6538a17 100644
--- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
@@ -42,7 +42,8 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-import java.util.Collections;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 
 /**
  * Build/Install/Run:
@@ -55,6 +56,7 @@
     private static ActivityManagerService sService;
 
     private ProcessRecord mProcessRecord;
+    private ProcessErrorStateRecord mProcessErrorState;
 
     @BeforeClass
     public static void setUpOnce() throws Exception {
@@ -67,6 +69,13 @@
             sService.mActivityTaskManager = new ActivityTaskManagerService(sContext);
             sService.mActivityTaskManager.initialize(null, null, sContext.getMainLooper());
             sService.mAtmInternal = sService.mActivityTaskManager.getAtmInternal();
+            final AppProfiler profiler = mock(AppProfiler.class);
+            setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
+            setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
+            setFieldValue(ActivityManagerService.class, sService, "mProcLock",
+                    new ActivityManagerProcLock());
+            final ProcessList processList = new ProcessList();
+            setFieldValue(ActivityManagerService.class, sService, "mProcessList", processList);
         });
 
         // Avoid NPE when initializing {@link ProcessRecord#mWindowProcessController}.
@@ -76,6 +85,18 @@
         doReturn(sysUiName).when(packageManagerInternal).getSystemUiServiceComponent();
     }
 
+    private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+        try {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            Field mfield = Field.class.getDeclaredField("accessFlags");
+            mfield.setAccessible(true);
+            mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+            field.set(obj, val);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+        }
+    }
+
     @AfterClass
     public static void tearDownOnce() {
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -85,12 +106,12 @@
     public void setUpProcess() throws Exception {
         // Need to run with dexmaker share class loader to mock package private class.
         runWithDexmakerShareClassLoader(() -> {
-            mProcessRecord = spy(new ProcessRecord(sService, sContext.getApplicationInfo(),
-                    "name", 12345));
-            doNothing().when(mProcessRecord).startAppProblemLocked();
-            doReturn(false).when(mProcessRecord).isSilentAnr();
-            doReturn(false).when(mProcessRecord).isMonitorCpuUsage();
-            doReturn(Collections.emptyList()).when(mProcessRecord).getLruProcessList();
+            mProcessRecord = new ProcessRecord(sService, sContext.getApplicationInfo(),
+                    "name", 12345);
+            mProcessErrorState = spy(mProcessRecord.mErrorState);
+            doNothing().when(mProcessErrorState).startAppProblemLSP();
+            doReturn(false).when(mProcessErrorState).isSilentAnr();
+            doReturn(false).when(mProcessErrorState).isMonitorCpuUsage();
         });
     }
 
@@ -101,10 +122,10 @@
      */
     @Test
     public void testProcessDefaultAnrRelatedStatus() {
-        assertFalse(mProcessRecord.isNotResponding());
-        assertFalse(mProcessRecord.isCrashing());
-        assertFalse(mProcessRecord.killedByAm);
-        assertFalse(mProcessRecord.killed);
+        assertFalse(mProcessErrorState.isNotResponding());
+        assertFalse(mProcessErrorState.isCrashing());
+        assertFalse(mProcessRecord.isKilledByAm());
+        assertFalse(mProcessRecord.isKilled());
     }
 
     /**
@@ -112,12 +133,12 @@
      */
     @Test
     public void testAnrWhenCrash() {
-        mProcessRecord.setCrashing(true);
-        assertTrue(mProcessRecord.isCrashing());
-        appNotResponding(mProcessRecord, "Test ANR when crash");
-        assertFalse(mProcessRecord.isNotResponding());
-        assertFalse(mProcessRecord.killedByAm);
-        assertFalse(mProcessRecord.killed);
+        mProcessErrorState.setCrashing(true);
+        assertTrue(mProcessErrorState.isCrashing());
+        appNotResponding(mProcessErrorState, "Test ANR when crash");
+        assertFalse(mProcessErrorState.isNotResponding());
+        assertFalse(mProcessRecord.isKilledByAm());
+        assertFalse(mProcessRecord.isKilled());
     }
 
     /**
@@ -125,11 +146,11 @@
      */
     @Test
     public void testAnrWhenKilledByAm() {
-        mProcessRecord.killedByAm = true;
-        appNotResponding(mProcessRecord, "Test ANR when killed by AM");
-        assertFalse(mProcessRecord.isNotResponding());
-        assertFalse(mProcessRecord.isCrashing());
-        assertFalse(mProcessRecord.killed);
+        mProcessRecord.setKilledByAm(true);
+        appNotResponding(mProcessErrorState, "Test ANR when killed by AM");
+        assertFalse(mProcessErrorState.isNotResponding());
+        assertFalse(mProcessErrorState.isCrashing());
+        assertFalse(mProcessRecord.isKilled());
     }
 
     /**
@@ -137,11 +158,11 @@
      */
     @Test
     public void testAnrWhenKilled() {
-        mProcessRecord.killed = true;
-        appNotResponding(mProcessRecord, "Test ANR when killed");
-        assertFalse(mProcessRecord.isNotResponding());
-        assertFalse(mProcessRecord.isCrashing());
-        assertFalse(mProcessRecord.killedByAm);
+        mProcessRecord.setKilled(true);
+        appNotResponding(mProcessErrorState, "Test ANR when killed");
+        assertFalse(mProcessErrorState.isNotResponding());
+        assertFalse(mProcessErrorState.isCrashing());
+        assertFalse(mProcessRecord.isKilledByAm());
     }
 
     /**
@@ -150,11 +171,11 @@
      */
     @Test
     public void testNonSilentAnr() {
-        appNotResponding(mProcessRecord, "Test non-silent ANR");
-        assertTrue(mProcessRecord.isNotResponding());
-        assertFalse(mProcessRecord.isCrashing());
-        assertFalse(mProcessRecord.killedByAm);
-        assertFalse(mProcessRecord.killed);
+        appNotResponding(mProcessErrorState, "Test non-silent ANR");
+        assertTrue(mProcessErrorState.isNotResponding());
+        assertFalse(mProcessErrorState.isCrashing());
+        assertFalse(mProcessRecord.isKilledByAm());
+        assertFalse(mProcessRecord.isKilled());
     }
 
     /**
@@ -164,16 +185,17 @@
     @Test
     public void testSilentAnr() {
         // Silent Anr will run through even without a parent process, and directly killed by AM.
-        doReturn(true).when(mProcessRecord).isSilentAnr();
-        appNotResponding(mProcessRecord, "Test silent ANR");
-        assertTrue(mProcessRecord.isNotResponding());
-        assertFalse(mProcessRecord.isCrashing());
-        assertTrue(mProcessRecord.killedByAm);
-        assertTrue(mProcessRecord.killed);
+        doReturn(true).when(mProcessErrorState).isSilentAnr();
+        appNotResponding(mProcessErrorState, "Test silent ANR");
+        assertTrue(mProcessErrorState.isNotResponding());
+        assertFalse(mProcessErrorState.isCrashing());
+        assertTrue(mProcessRecord.isKilledByAm());
+        assertTrue(mProcessRecord.isKilled());
     }
 
-    private static void appNotResponding(ProcessRecord processRecord, String annotation) {
-        processRecord.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
+    private static void appNotResponding(ProcessErrorStateRecord processErrorState,
+            String annotation) {
+        processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
                 null /* parentShortComponentName */, null /* parentProcess */,
                 false /* aboveSystem */, annotation, false /* onlyDumpSelf */);
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 784718b..9ffb5017 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -770,7 +770,7 @@
         }
 
         @Override
-        void reportGlobalUsageEventLocked(int event) {
+        void reportGlobalUsageEvent(int event) {
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
new file mode 100644
index 0000000..738f008
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 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.app;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.content.Context;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GameManagerServiceSettingsTests {
+
+    private static final String TAG = "GameServiceSettingsTests";
+    private static final String PACKAGE_NAME_1 = "com.android.app1";
+    private static final String PACKAGE_NAME_2 = "com.android.app2";
+    private static final String PACKAGE_NAME_3 = "com.android.app3";
+
+    private void writeFile(File file, byte[] data) {
+        file.mkdirs();
+        try {
+            AtomicFile aFile = new AtomicFile(file);
+            FileOutputStream fos = aFile.startWrite();
+            fos.write(data);
+            aFile.finishWrite(fos);
+        } catch (IOException ioe) {
+            Log.e(TAG, "Cannot write file " + file.getPath());
+        }
+    }
+
+    private void writeGameServiceXml() {
+        writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
+                        "system/game-manager-service.xml"),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                        + "<packages>\n"
+                        + "  <package name=\"com.android.app1\" gameMode=\"1\">\n"
+                        + "  </package>\n"
+                        + "  <package name=\"com.android.app2\" gameMode=\"2\">\n"
+                        + "  </package>\n"
+                        + "  <package name=\"com.android.app3\" gameMode=\"3\">\n"
+                        + "  </package>\n"
+                        + "</packages>\n").getBytes());
+    }
+
+    private void deleteSystemFolder() {
+        File systemFolder = new File(InstrumentationRegistry.getContext().getFilesDir(), "system");
+        deleteFolder(systemFolder);
+    }
+
+    private static void deleteFolder(File folder) {
+        File[] files = folder.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                deleteFolder(file);
+            }
+        }
+        folder.delete();
+    }
+
+    private void writeOldFiles() {
+        deleteSystemFolder();
+        writeGameServiceXml();
+    }
+
+    private void verifyGameServiceSettingsData(Settings settings) {
+        assertThat(settings.getGameModeLocked(PACKAGE_NAME_1), is(1));
+        assertThat(settings.getGameModeLocked(PACKAGE_NAME_2), is(2));
+        assertThat(settings.getGameModeLocked(PACKAGE_NAME_3), is(3));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
+    }
+
+    /** read in data and verify */
+    @Test
+    public void testReadGameServiceSettings() {
+        /* write out files and read */
+        writeOldFiles();
+        final Context context = InstrumentationRegistry.getContext();
+        Settings settings = new Settings(context.getFilesDir());
+        assertThat(settings.readPersistentDataLocked(), is(true));
+        verifyGameServiceSettingsData(settings);
+    }
+
+    /** read in data, write it out, and read it back in.  Verify same. */
+    @Test
+    public void testWriteGameServiceSettings() {
+        // write out files and read
+        writeOldFiles();
+        final Context context = InstrumentationRegistry.getContext();
+        Settings settings = new Settings(context.getFilesDir());
+        assertThat(settings.readPersistentDataLocked(), is(true));
+
+        // write out, read back in and verify the same
+        settings.writePersistentDataLocked();
+        assertThat(settings.readPersistentDataLocked(), is(true));
+        verifyGameServiceSettingsData(settings);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
new file mode 100644
index 0000000..caa46da
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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.app;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.GameManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GameManagerServiceTests {
+
+    private static final String TAG = "GameServiceTests";
+    private static final String PACKAGE_NAME_0 = "com.android.app0";
+    private static final String PACKAGE_NAME_1 = "com.android.app1";
+    private static final int USER_ID_1 = 1001;
+    private static final int USER_ID_2 = 1002;
+
+    /**
+     * By default game mode is not supported.
+     */
+    @Test
+    public void testGameModeDefaultValue() {
+        GameManagerService gameManagerService =
+                new GameManagerService(InstrumentationRegistry.getContext());
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(PACKAGE_NAME_0, USER_ID_1));
+    }
+
+    /**
+     * Test the default behaviour for a nonexistent user.
+     */
+    @Test
+    public void testDefaultValueForNonexistentUser() {
+        GameManagerService gameManagerService =
+                new GameManagerService(InstrumentationRegistry.getContext());
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_2);
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_2));
+    }
+
+    /**
+     * Test getter and setter of game modes.
+     */
+    @Test
+    public void testGameMode() {
+        GameManagerService gameManagerService =
+                new GameManagerService(InstrumentationRegistry.getContext());
+        gameManagerService.onUserStarting(USER_ID_1);
+
+        assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+                gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+        gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_STANDARD,
+                gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+        gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE,
+                USER_ID_1);
+        assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+                gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
new file mode 100644
index 0000000..1328b91
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 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.apphibernation;
+
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.returnsArgAt;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.intThat;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.IActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.UserManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.server.apphibernation.AppHibernationService}
+ */
+@SmallTest
+public final class AppHibernationServiceTest {
+    private static final String PACKAGE_SCHEME = "package";
+    private static final String PACKAGE_NAME_1 = "package1";
+    private static final String PACKAGE_NAME_2 = "package2";
+    private static final int USER_ID_1 = 1;
+    private static final int USER_ID_2 = 2;
+
+    private final List<UserInfo> mUserInfos = new ArrayList<>();
+
+    private AppHibernationService mAppHibernationService;
+    private BroadcastReceiver mBroadcastReceiver;
+    @Mock
+    private Context mContext;
+    @Mock
+    private IPackageManager mIPackageManager;
+    @Mock
+    private IActivityManager mIActivityManager;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore;
+    @Captor
+    private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;
+
+    @Before
+    public void setUp() throws RemoteException {
+        // Share class loader to allow access to package-private classes
+        System.setProperty("dexmaker.share_classloader", "true");
+        MockitoAnnotations.initMocks(this);
+        doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
+
+        mAppHibernationService = new AppHibernationService(new MockInjector(mContext));
+
+        verify(mContext).registerReceiver(mReceiverCaptor.capture(), any());
+        mBroadcastReceiver = mReceiverCaptor.getValue();
+
+        doReturn(mUserInfos).when(mUserManager).getUsers();
+
+        doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(),
+                anyInt(), anyBoolean(), anyBoolean(), any(), any());
+
+        List<PackageInfo> packages = new ArrayList<>();
+        packages.add(makePackageInfo(PACKAGE_NAME_1));
+        doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
+                intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt());
+        mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+        UserInfo userInfo = addUser(USER_ID_1);
+        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo));
+        doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1);
+    }
+
+    @Test
+    public void testSetHibernatingForUser_packageIsHibernating() {
+        // WHEN we hibernate a package for a user
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
+
+        // THEN the package is marked hibernating for the user
+        assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1));
+    }
+
+    @Test
+    public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating() {
+        // WHEN a new package is added and it is hibernated
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED,
+                Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */));
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_1);
+        mBroadcastReceiver.onReceive(mContext, intent);
+
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_1, true);
+
+        // THEN the new package is hibernated
+        assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_2, USER_ID_1));
+    }
+
+    @Test
+    public void testSetHibernatingForUser_newUserUnlocked_packageIsHibernating()
+            throws RemoteException {
+        // WHEN a new user is added and a package from the user is hibernated
+        UserInfo user2 = addUser(USER_ID_2);
+        mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2));
+        doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2);
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true);
+
+        // THEN the new user's package is hibernated
+        assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_2));
+    }
+
+    @Test
+    public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating() {
+        // GIVEN a package is currently hibernated
+        mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
+
+        // WHEN the package is removed but marked as replacing
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED,
+                Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */));
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_1);
+        intent.putExtra(Intent.EXTRA_REPLACING, true);
+        mBroadcastReceiver.onReceive(mContext, intent);
+
+        // THEN the package is still hibernating
+        assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1));
+    }
+
+    @Test
+    public void testSetHibernatingGlobally_packageIsHibernatingGlobally() throws RemoteException {
+        // WHEN we hibernate a package
+        mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true);
+
+        // THEN the package is marked hibernating for the user
+        assertTrue(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
+    }
+
+    /**
+     * Add a mock user with one package.
+     */
+    private UserInfo addUser(int userId) throws RemoteException {
+        return addUser(userId, new String[]{PACKAGE_NAME_1});
+    }
+
+    /**
+     * Add a mock user with the packages specified.
+     */
+    private UserInfo addUser(int userId, String[] packageNames) throws RemoteException {
+        UserInfo userInfo = new UserInfo(userId, "user_" + userId, 0 /* flags */);
+        mUserInfos.add(userInfo);
+        List<PackageInfo> userPackages = new ArrayList<>();
+        for (String pkgName : packageNames) {
+            userPackages.add(makePackageInfo(pkgName));
+        }
+        doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager)
+                .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId));
+        return userInfo;
+    }
+
+    private static PackageInfo makePackageInfo(String packageName) {
+        PackageInfo pkg = new PackageInfo();
+        pkg.packageName = packageName;
+        return pkg;
+    }
+
+    private class MockInjector implements AppHibernationService.Injector {
+        private final Context mContext;
+
+        MockInjector(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public IActivityManager getActivityManager() {
+            return mIActivityManager;
+        }
+
+        @Override
+        public Context getContext() {
+            return mContext;
+        }
+
+        @Override
+        public IPackageManager getPackageManager() {
+            return mIPackageManager;
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
+            return Mockito.mock(HibernationStateDiskStore.class);
+        }
+
+        @Override
+        public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
+            return Mockito.mock(HibernationStateDiskStore.class);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java
new file mode 100644
index 0000000..59f3c35
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2021 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.apphibernation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.FileUtils;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+
+@SmallTest
+public class HibernationStateDiskStoreTest {
+    private static final String STATES_FILE_NAME = "states";
+    private final MockScheduledExecutorService mMockScheduledExecutorService =
+            new MockScheduledExecutorService();
+
+    private File mFile;
+    private HibernationStateDiskStore<String> mHibernationStateDiskStore;
+
+
+    @Before
+    public void setUp() {
+        mFile = new File(InstrumentationRegistry.getContext().getCacheDir(), "test");
+        mHibernationStateDiskStore = new HibernationStateDiskStore<>(mFile,
+                new MockProtoReadWriter(), mMockScheduledExecutorService, STATES_FILE_NAME);
+    }
+
+    @After
+    public void tearDown() {
+        FileUtils.deleteContentsAndDir(mFile);
+    }
+
+    @Test
+    public void testScheduleWriteHibernationStates_writesDataThatCanBeRead() {
+        // GIVEN some data to be written
+        List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B"));
+
+        // WHEN the data is written
+        mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite);
+        mMockScheduledExecutorService.executeScheduledTask();
+
+        // THEN the read data is equal to what was written
+        List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates();
+        for (int i = 0; i < toWrite.size(); i++) {
+            assertEquals(toWrite.get(i), storedStrings.get(i));
+        }
+    }
+
+    @Test
+    public void testScheduleWriteHibernationStates_laterWritesOverwritePrevious() {
+        // GIVEN store has some data it is scheduled to write
+        mHibernationStateDiskStore.scheduleWriteHibernationStates(
+                new ArrayList<>(Arrays.asList("C", "D")));
+
+        // WHEN a write is scheduled with new data
+        List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B"));
+        mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite);
+        mMockScheduledExecutorService.executeScheduledTask();
+
+        // THEN the written data is the last scheduled data
+        List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates();
+        for (int i = 0; i < toWrite.size(); i++) {
+            assertEquals(toWrite.get(i), storedStrings.get(i));
+        }
+    }
+
+    /**
+     * Mock proto read / writer that just writes and reads a list of String data.
+     */
+    private final class MockProtoReadWriter implements ProtoReadWriter<List<String>> {
+        private static final long FIELD_ID = 1;
+
+        @Override
+        public void writeToProto(@NonNull ProtoOutputStream stream,
+                @NonNull List<String> data) {
+            for (int i = 0, size = data.size(); i < size; i++) {
+                stream.write(FIELD_ID, data.get(i));
+            }
+        }
+
+        @Nullable
+        @Override
+        public List<String> readFromProto(@NonNull ProtoInputStream stream)
+                throws IOException {
+            ArrayList<String> list = new ArrayList<>();
+            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                list.add(stream.readString(FIELD_ID));
+            }
+            return list;
+        }
+    }
+
+    /**
+     * Mock scheduled executor service that has minimum implementation and can synchronously
+     * execute scheduled tasks.
+     */
+    private final class MockScheduledExecutorService implements ScheduledExecutorService {
+
+        Runnable mScheduledRunnable = null;
+
+        @Override
+        public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+            mScheduledRunnable = command;
+            return Mockito.mock(ScheduledFuture.class);
+        }
+
+        @Override
+        public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,
+                long period, TimeUnit unit) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+                long delay, TimeUnit unit) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void shutdown() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public List<Runnable> shutdownNow() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isShutdown() {
+            return false;
+        }
+
+        @Override
+        public boolean isTerminated() {
+            return false;
+        }
+
+        @Override
+        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> Future<T> submit(Callable<T> task) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> Future<T> submit(Runnable task, T result) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Future<?> submit(Runnable task) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+                throws InterruptedException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
+                TimeUnit unit) throws InterruptedException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+                throws InterruptedException, ExecutionException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException, TimeoutException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            throw new UnsupportedOperationException();
+        }
+
+        void executeScheduledTask() {
+            mScheduledRunnable.run();
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index 8d744c4..9b6c723 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -700,14 +700,14 @@
     public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception {
         SearchSpec searchSpec =
                 new SearchSpec.Builder()
-                        .addSchemaType("FakeType")
+                        .addFilterSchemas("FakeType")
                         .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
                         .build();
         mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);
 
         searchSpec =
                 new SearchSpec.Builder()
-                        .addNamespace("FakeNamespace")
+                        .addFilterNamespaces("FakeNamespace")
                         .setTermMatch(TermMatchType.Code.PREFIX_VALUE)
                         .build();
         mAppSearchImpl.removeByQuery("package", "EmptyDatabase", "", searchSpec);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 90ce6cb..2d45726 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -33,6 +33,7 @@
 import android.hardware.biometrics.IBiometricService;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.annotation.NonNull;
@@ -195,7 +196,8 @@
         BiometricSchedulerProto bsp = getDump(true /* clearSchedulerBuffer */);
         assertEquals(BiometricsProto.CM_NONE, bsp.currentOperation);
         assertEquals(0, bsp.totalOperations);
-        assertEquals(0, bsp.recentOperations.length);
+        // TODO:(b/178828362) See bug and/or commit message :/
+        // assertEquals(0, bsp.recentOperations.length);
 
         // Pretend the scheduler is busy enrolling, and check the proto dump again.
         final TestClientMonitor2 client = new TestClientMonitor2(mContext, mToken,
@@ -206,7 +208,11 @@
         assertEquals(BiometricsProto.CM_ENROLL, bsp.currentOperation);
         // No operations have completed yet
         assertEquals(0, bsp.totalOperations);
-        assertEquals(0, bsp.recentOperations.length);
+
+        // TODO:(b/178828362) See bug and/or commit message :/
+        assertEquals(1, bsp.recentOperations.length);
+        assertEquals(BiometricsProto.CM_NONE, bsp.recentOperations[0]);
+
         // Finish this operation, so the next scheduled one can start
         client.getCallback().onClientFinished(client, true);
     }
@@ -222,7 +228,8 @@
         assertEquals(BiometricsProto.CM_ENROLL, bsp.currentOperation);
         // No operations have completed yet
         assertEquals(0, bsp.totalOperations);
-        assertEquals(0, bsp.recentOperations.length);
+        // TODO:(b/178828362) See bug and/or commit message :/
+        // assertEquals(0, bsp.recentOperations.length);
         // Finish this operation, so the next scheduled one can start
         client.getCallback().onClientFinished(client, true);
 
@@ -264,7 +271,43 @@
 
         // RecentOperations queue is cleared (by the previous dump)
         bsp = getDump(true /* clearSchedulerBuffer */);
-        assertEquals(0, bsp.recentOperations.length);
+
+        // TODO:(b/178828362) See bug and/or commit message :/
+        assertEquals(1, bsp.recentOperations.length);
+        assertEquals(BiometricsProto.CM_NONE, bsp.recentOperations[0]);
+    }
+
+    @Test
+    public void testCancelPendingAuth() throws RemoteException {
+        final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+
+        final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon);
+        final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+        final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
+                mToken, callback);
+
+        // Add a non-cancellable client, then add the auth client
+        mScheduler.scheduleClientMonitor(client1);
+        mScheduler.scheduleClientMonitor(client2);
+        waitForIdle();
+
+        assertEquals(mScheduler.getCurrentClient(), client1);
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE,
+                mScheduler.mPendingOperations.getFirst().mState);
+
+        // Request cancel before the authentication client has started
+        mScheduler.cancelAuthentication(mToken);
+        waitForIdle();
+        assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
+                mScheduler.mPendingOperations.getFirst().mState);
+
+        // Finish the blocking client. The authentication client should send ERROR_CANCELED
+        client1.getCallback().onClientFinished(client1, true /* success */);
+        waitForIdle();
+        verify(callback).onError(anyInt(), anyInt(),
+                eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
+                eq(0) /* vendorCode */);
+        assertNull(mScheduler.getCurrentClient());
     }
 
     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
@@ -293,6 +336,29 @@
         }
     }
 
+    private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+
+        public TestAuthenticationClient(@NonNull Context context,
+                @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
+                @NonNull ClientMonitorCallbackConverter listener) {
+            super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
+                    false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
+                    TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
+                    0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
+                    false /* isKeyguard */);
+        }
+
+        @Override
+        protected void stopHalOperation() {
+
+        }
+
+        @Override
+        protected void startHalOperation() {
+
+        }
+    }
+
     private static class TestClientMonitor2 extends TestClientMonitor {
         private final int mProtoEnum;
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index 61cc8e6..904ade8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -19,17 +19,20 @@
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
@@ -80,7 +83,7 @@
                 .thenReturn(5);
 
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-        mFingerprint21 = new Fingerprint21(mContext, mScheduler,
+        mFingerprint21 = new TestableFingerprint21(mContext, mScheduler,
                 new Handler(Looper.getMainLooper()), SENSOR_ID,
                 BiometricManager.Authenticators.BIOMETRIC_WEAK, mLockoutResetDispatcher,
                 mHalResultController);
@@ -100,4 +103,21 @@
         waitForIdle();
         verify(mScheduler).reset();
     }
+
+    private static class TestableFingerprint21 extends Fingerprint21 {
+
+        TestableFingerprint21(@NonNull Context context,
+                @NonNull BiometricScheduler scheduler,
+                @NonNull Handler handler, int sensorId, int strength,
+                @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+                @NonNull HalResultController controller) {
+            super(context, scheduler, handler, sensorId, strength, lockoutResetDispatcher,
+                    controller);
+        }
+
+        @Override
+        synchronized IBiometricsFingerprint getDaemon() {
+            return mock(IBiometricsFingerprint.class);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index ac8dc34..a53ff9b 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -44,6 +44,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.UUID;
 
 @RunWith(AndroidJUnit4.class)
@@ -69,6 +71,10 @@
         os.close();
     }
 
+    private String readFile(File file) throws IOException {
+        return new String(Files.readAllBytes(Paths.get(file.toURI())));
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -499,4 +505,86 @@
         assertThat(compatConfig.isChangeEnabled(1236L,
             ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
     }
+
+    @Test
+    public void testSaveOverrides() throws Exception {
+        File overridesFile = new File(createTempDir(), "overrides.xml");
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1L)
+                .addEnableSinceSdkChangeWithId(2, 2L)
+                .build();
+        compatConfig.forceNonDebuggableFinalForTest(true);
+        compatConfig.initOverrides(overridesFile);
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                                .withPackageName("foo.bar")
+                                .debuggable()
+                                .build());
+        when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        compatConfig.addOverride(1L, "foo.bar", true);
+        compatConfig.addOverride(2L, "bar.baz", false);
+
+        assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+                + "<overrides>\n"
+                + "    <change-overrides changeId=\"1\">\n"
+                + "        <validated>\n"
+                + "            <override-value packageName=\"foo.bar\" enabled=\"true\">\n"
+                + "            </override-value>\n"
+                + "        </validated>\n"
+                + "        <deferred>\n"
+                + "        </deferred>\n"
+                + "    </change-overrides>\n"
+                + "    <change-overrides changeId=\"2\">\n"
+                + "        <validated>\n"
+                + "        </validated>\n"
+                + "        <deferred>\n"
+                + "            <override-value packageName=\"bar.baz\" enabled=\"false\">\n"
+                + "            </override-value>\n"
+                + "        </deferred>\n"
+                + "    </change-overrides>\n"
+                + "</overrides>\n");
+    }
+
+    @Test
+    public void testLoadOverrides() throws Exception {
+        File tempDir = createTempDir();
+        File overridesFile = new File(tempDir, "overrides.xml");
+        // Change 1 is enabled for foo.bar (validated)
+        // Change 2 is disabled for bar.baz (deferred)
+        String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+                       + "<overrides>"
+                       +    "<change-overrides changeId=\"1\">"
+                       +        "<deferred/>"
+                       +        "<validated>"
+                       +            "<override-value packageName=\"foo.bar\" enabled=\"true\"/>"
+                       +        "</validated>"
+                       +    "</change-overrides>"
+                       +    "<change-overrides changeId=\"2\">"
+                       +        "<deferred>"
+                       +           "<override-value packageName=\"bar.baz\" enabled=\"false\"/>"
+                       +        "</deferred>"
+                       +        "<validated/>"
+                       +    "</change-overrides>"
+                       + "</overrides>";
+        writeToFile(tempDir, "overrides.xml", xmlData);
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1L)
+                .addEnableSinceSdkChangeWithId(2, 2L)
+                .build();
+        compatConfig.forceNonDebuggableFinalForTest(true);
+        compatConfig.initOverrides(overridesFile);
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("foo.bar")
+                .debuggable()
+                .build();
+        when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt()))
+                .thenReturn(applicationInfo);
+        when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue();
+        assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 8c853cc..7597cbf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5616,43 +5616,52 @@
     public void testDisallowSharingIntoProfileSetRestriction() {
         when(mServiceContext.resources.getString(R.string.config_managed_provisioning_package))
                 .thenReturn("com.android.managedprovisioning");
+        when(getServices().userManagerInternal.getProfileParentId(anyInt()))
+                .thenReturn(UserHandle.USER_SYSTEM);
+        mServiceContext.binder.callingPid = DpmMockContext.SYSTEM_PID;
+        mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         Bundle restriction = new Bundle();
         restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true);
 
-        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
-        RestrictionsListener listener = new RestrictionsListener(mContext);
+        RestrictionsListener listener = new RestrictionsListener(
+                mServiceContext, getServices().userManagerInternal, dpms);
         listener.onUserRestrictionsChanged(CALLER_USER_HANDLE, restriction, new Bundle());
-        verifyDataSharingChangedBroadcast();
+
+        verifyDataSharingAppliedBroadcast();
     }
 
     @Test
     public void testDisallowSharingIntoProfileClearRestriction() {
         when(mServiceContext.resources.getString(R.string.config_managed_provisioning_package))
                 .thenReturn("com.android.managedprovisioning");
+        when(getServices().userManagerInternal.getProfileParentId(anyInt()))
+                .thenReturn(UserHandle.USER_SYSTEM);
+        mServiceContext.binder.callingPid = DpmMockContext.SYSTEM_PID;
+        mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         Bundle restriction = new Bundle();
         restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true);
 
-        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
-        RestrictionsListener listener = new RestrictionsListener(mContext);
+        RestrictionsListener listener = new RestrictionsListener(
+                mServiceContext, getServices().userManagerInternal, dpms);
         listener.onUserRestrictionsChanged(CALLER_USER_HANDLE, new Bundle(), restriction);
-        verifyDataSharingChangedBroadcast();
+
+        verifyDataSharingAppliedBroadcast();
     }
 
     @Test
     public void testDisallowSharingIntoProfileUnchanged() {
-        RestrictionsListener listener = new RestrictionsListener(mContext);
+        RestrictionsListener listener = new RestrictionsListener(
+                mContext, getServices().userManagerInternal, dpms);
         listener.onUserRestrictionsChanged(CALLER_USER_HANDLE, new Bundle(), new Bundle());
         verify(mContext.spiedContext, never()).sendBroadcastAsUser(any(), any());
     }
 
-    private void verifyDataSharingChangedBroadcast() {
+    private void verifyDataSharingAppliedBroadcast() {
         Intent expectedIntent = new Intent(
-                DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED);
-        expectedIntent.setPackage("com.android.managedprovisioning");
-        expectedIntent.putExtra(Intent.EXTRA_USER_ID, CALLER_USER_HANDLE);
+                DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_APPLIED);
         verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
                 MockUtils.checkIntent(expectedIntent),
-                MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
+                MockUtils.checkUserHandle(CALLER_USER_HANDLE));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 95aac60..1a22661 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -19,10 +19,14 @@
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertThrows;
 
+import android.hardware.devicestate.DeviceStateRequest;
 import android.hardware.devicestate.IDeviceStateManagerCallback;
+import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
@@ -33,6 +37,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.Optional;
+
 import javax.annotation.Nullable;
 
 /**
@@ -43,9 +50,9 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class DeviceStateManagerServiceTest {
-    private static final int DEFAULT_DEVICE_STATE = 0;
-    private static final int OTHER_DEVICE_STATE = 1;
-    private static final int UNSUPPORTED_DEVICE_STATE = 999;
+    private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
+    private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER");
+    private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(999, "UNSUPPORTED");
 
     private TestDeviceStatePolicy mPolicy;
     private TestDeviceStateProvider mProvider;
@@ -59,139 +66,106 @@
     }
 
     @Test
-    public void requestStateChange() {
+    public void baseStateChanged() {
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
 
-        mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
         assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
     }
 
     @Test
-    public void requestStateChange_pendingState() {
+    public void baseStateChanged_withStatePendingPolicyCallback() {
         mPolicy.blockConfigure();
 
-        mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), OTHER_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
 
-        mProvider.notifyRequestState(DEFAULT_DEVICE_STATE);
+        mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), OTHER_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getPendingState().get(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
 
         mPolicy.resumeConfigure();
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
     }
 
     @Test
-    public void requestStateChange_unsupportedState() {
-        mProvider.notifyRequestState(UNSUPPORTED_DEVICE_STATE);
-        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
-    }
-
-    @Test
-    public void requestStateChange_invalidState() {
+    public void baseStateChanged_unsupportedState() {
         assertThrows(IllegalArgumentException.class, () -> {
-            mProvider.notifyRequestState(INVALID_DEVICE_STATE);
+            mProvider.setState(UNSUPPORTED_DEVICE_STATE.getIdentifier());
         });
+
+        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
     }
 
     @Test
-    public void requestOverrideState() {
-        mService.setOverrideState(OTHER_DEVICE_STATE);
-        // Committed state changes as there is a requested override.
-        assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
+    public void baseStateChanged_invalidState() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            mProvider.setState(INVALID_DEVICE_STATE);
+        });
 
-        // Committed state is set back to the requested state once the override is cleared.
-        mService.clearOverrideState();
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
-    }
-
-    @Test
-    public void requestOverrideState_unsupportedState() {
-        mService.setOverrideState(UNSUPPORTED_DEVICE_STATE);
-        // Committed state remains the same as the override state is unsupported.
-        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
     }
 
     @Test
     public void supportedStatesChanged() {
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
 
-        mProvider.notifySupportedDeviceStates(new int []{ DEFAULT_DEVICE_STATE });
+        mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
 
         // The current committed and requests states do not change because the current state remains
         // supported.
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
     }
 
     @Test
-    public void supportedStatesChanged_invalidState() {
-        assertThrows(IllegalArgumentException.class, () -> {
-            mProvider.notifySupportedDeviceStates(new int []{ INVALID_DEVICE_STATE });
-        });
-    }
-
-    @Test
-    public void supportedStatesChanged_unsupportedRequestedState() {
+    public void supportedStatesChanged_unsupportedBaseState() {
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
 
-        mProvider.notifySupportedDeviceStates(new int []{ OTHER_DEVICE_STATE });
+        mProvider.notifySupportedDeviceStates(new DeviceState[]{ OTHER_DEVICE_STATE });
 
         // The current requested state is cleared because it is no longer supported.
         assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), INVALID_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState(), Optional.empty());
 
-        mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
 
         assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
-        assertEquals(mService.getPendingState(), INVALID_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), OTHER_DEVICE_STATE);
-    }
-
-    @Test
-    public void supportedStatesChanged_unsupportedOverrideState() {
-        mService.setOverrideState(OTHER_DEVICE_STATE);
-        // Committed state changes as there is a requested override.
-        assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), OTHER_DEVICE_STATE);
-
-        mProvider.notifySupportedDeviceStates(new int []{ DEFAULT_DEVICE_STATE });
-
-        // Committed state is set back to the requested state as the override state is no longer
-        // supported.
-        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mService.getRequestedState(), DEFAULT_DEVICE_STATE);
-        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getPendingState(), Optional.empty());
+        assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE);
     }
 
     @Test
@@ -199,23 +173,27 @@
         TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
         mService.getBinderService().registerCallback(callback);
 
-        mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
         assertNotNull(callback.getLastNotifiedValue());
-        assertEquals(callback.getLastNotifiedValue().intValue(), OTHER_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedValue().intValue(),
+                OTHER_DEVICE_STATE.getIdentifier());
 
-        mProvider.notifyRequestState(DEFAULT_DEVICE_STATE);
-        assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE);
+        mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedValue().intValue(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
 
         mPolicy.blockConfigure();
-        mProvider.notifyRequestState(OTHER_DEVICE_STATE);
+        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
         // The callback should not have been notified of the state change as the policy is still
         // pending callback.
-        assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedValue().intValue(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
 
         mPolicy.resumeConfigure();
         // Now that the policy is finished processing the callback should be notified of the state
         // change.
-        assertEquals(callback.getLastNotifiedValue().intValue(), OTHER_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedValue().intValue(),
+                OTHER_DEVICE_STATE.getIdentifier());
     }
 
     @Test
@@ -223,7 +201,150 @@
         TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
         mService.getBinderService().registerCallback(callback);
         assertNotNull(callback.getLastNotifiedValue());
-        assertEquals(callback.getLastNotifiedValue().intValue(), DEFAULT_DEVICE_STATE);
+        assertEquals(callback.getLastNotifiedValue().intValue(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
+    }
+
+    @Test
+    public void getSupportedDeviceStates() throws RemoteException {
+        final int[] expectedStates = new int[] { 0, 1 };
+        assertEquals(mService.getBinderService().getSupportedDeviceStates(), expectedStates);
+    }
+
+    @Test
+    public void requestState() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        final IBinder token = new Binder();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+        mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
+                0 /* flags */);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
+
+
+        mService.getBinderService().cancelRequest(token);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
+        // Committed state is set back to the requested state once the override is cleared.
+        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
+    }
+
+    @Test
+    public void requestState_flagCancelWhenBaseChanges() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        final IBinder token = new Binder();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+        mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
+                DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+
+        // Request is canceled because the base state changed.
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
+        // Committed state is set back to the requested state once the override is cleared.
+        assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getBaseState().get(), OTHER_DEVICE_STATE);
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
+    }
+
+    @Test
+    public void requestState_becomesUnsupported() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        final IBinder token = new Binder();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+        mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
+                0 /* flags */);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), OTHER_DEVICE_STATE);
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
+
+        // Request is canceled because the state is no longer supported.
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
+        // Committed state is set back to the requested state as the override state is no longer
+        // supported.
+        assertEquals(mService.getCommittedState(), DEFAULT_DEVICE_STATE);
+        assertEquals(mService.getBaseState().get(), DEFAULT_DEVICE_STATE);
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
+    }
+
+    @Test
+    public void requestState_unsupportedState() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestState(token,
+                    UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+        });
+    }
+
+    @Test
+    public void requestState_invalidState() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestState(token, INVALID_DEVICE_STATE, 0 /* flags */);
+        });
+    }
+
+    @Test
+    public void requestState_beforeRegisteringCallback() {
+        assertThrows(IllegalStateException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
+                    0 /* flags */);
+        });
     }
 
     private static final class TestDeviceStatePolicy implements DeviceStatePolicy {
@@ -275,9 +396,8 @@
     }
 
     private static final class TestDeviceStateProvider implements DeviceStateProvider {
-        private int[] mSupportedDeviceStates = new int[]{ DEFAULT_DEVICE_STATE,
+        private DeviceState[] mSupportedDeviceStates = new DeviceState[]{ DEFAULT_DEVICE_STATE,
                 OTHER_DEVICE_STATE };
-        private int mCurrentDeviceState = DEFAULT_DEVICE_STATE;
         private Listener mListener;
 
         @Override
@@ -288,32 +408,56 @@
 
             mListener = listener;
             mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates);
-            mListener.onStateChanged(mCurrentDeviceState);
+            mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
         }
 
-        public void notifySupportedDeviceStates(int[] supportedDeviceStates) {
+        public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
             mSupportedDeviceStates = supportedDeviceStates;
             mListener.onSupportedDeviceStatesChanged(supportedDeviceStates);
         }
 
-        public void notifyRequestState(int state) {
-            mCurrentDeviceState = state;
-            mListener.onStateChanged(state);
+        public void setState(int identifier) {
+            mListener.onStateChanged(identifier);
         }
     }
 
     private static final class TestDeviceStateManagerCallback extends
             IDeviceStateManagerCallback.Stub {
-        Integer mLastNotifiedValue;
+        public static final int STATUS_UNKNOWN = 0;
+        public static final int STATUS_ACTIVE = 1;
+        public static final int STATUS_SUSPENDED = 2;
+        public static final int STATUS_CANCELED = 3;
+
+        private Integer mLastNotifiedValue;
+        private final HashMap<IBinder, Integer> mLastNotifiedStatus = new HashMap<>();
 
         @Override
         public void onDeviceStateChanged(int deviceState) {
             mLastNotifiedValue = deviceState;
         }
 
+        @Override
+        public void onRequestActive(IBinder token) {
+            mLastNotifiedStatus.put(token, STATUS_ACTIVE);
+        }
+
+        @Override
+        public void onRequestSuspended(IBinder token) {
+            mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
+        }
+
+        @Override
+        public void onRequestCanceled(IBinder token) {
+            mLastNotifiedStatus.put(token, STATUS_CANCELED);
+        }
+
         @Nullable
         Integer getLastNotifiedValue() {
             return mLastNotifiedValue;
         }
+
+        int getLastNotifiedStatus(IBinder requestToken) {
+            return mLastNotifiedStatus.getOrDefault(requestToken, STATUS_UNKNOWN);
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index ae966aa..f0b4f1b 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -227,6 +227,31 @@
     }
 
     @Test
+    public void testPhysicalStrategyRecalculateSplines() {
+        Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS,
+                BACKLIGHT_RANGE);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
+        float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
+        for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
+            adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
+        }
+
+        // Default is unadjusted
+        assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */);
+        assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */);
+
+        // When adjustment is turned on, adjustment array is used
+        strategy.recalculateSplines(true, adjustedNits50p);
+        assertEquals(1.3425f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */);
+        assertEquals(239.25f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */);
+
+        // When adjustment is turned off, adjustment array is ignored
+        strategy.recalculateSplines(false, adjustedNits50p);
+        assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */);
+        assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */);
+    }
+
+    @Test
     public void testDefaultStrategyIsPhysical() {
         Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
                 DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index e02a46af..d362791 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -335,6 +335,9 @@
         assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
         assertTrue(event.nightMode);
         assertEquals(3333, event.colorTemperature);
+        assertTrue(event.reduceBrightColors);
+        assertEquals(40, event.reduceBrightColorsStrength);
+        assertEquals(20f, event.reduceBrightColorsOffset, FLOAT_DELTA);
         assertEquals("a.package", event.packageName);
         assertEquals(0, event.userId);
         assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets);
@@ -561,6 +564,9 @@
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
 
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
+
         startTracker(mTracker);
         mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
                 batteryChangeEvent(30, 100));
@@ -592,6 +598,9 @@
         assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
         assertTrue(event.nightMode);
         assertEquals(3339, event.colorTemperature);
+        assertTrue(event.reduceBrightColors);
+        assertEquals(40, event.reduceBrightColorsStrength);
+        assertEquals(20f, event.reduceBrightColorsOffset, FLOAT_DELTA);
         assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA);
         assertTrue(event.isUserSetBrightness);
         assertFalse(event.isDefaultBrightnessConfig);
@@ -606,6 +615,9 @@
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
         mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
 
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
+
         startTracker(mTracker);
         mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
                 batteryChangeEvent(30, 100));
@@ -639,6 +651,7 @@
         assertEquals(brightness, event.brightness, FLOAT_DELTA);
         assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
         assertTrue(event.nightMode);
+        assertTrue(event.reduceBrightColors);
         assertEquals(3339, event.colorTemperature);
     }
 
@@ -661,6 +674,9 @@
         builder.setBatteryLevel(0.7f);
         builder.setNightMode(false);
         builder.setColorTemperature(345);
+        builder.setReduceBrightColors(false);
+        builder.setReduceBrightColorsStrength(40);
+        builder.setReduceBrightColorsOffset(20f);
         builder.setLastBrightness(50f);
         builder.setColorValues(new long[] {23, 34, 45}, 1000L);
         BrightnessChangeEvent event = builder.build();
@@ -684,6 +700,9 @@
         assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA);
         assertEquals(event.nightMode, event2.nightMode);
         assertEquals(event.colorTemperature, event2.colorTemperature);
+        assertEquals(event.reduceBrightColors, event2.reduceBrightColors);
+        assertEquals(event.reduceBrightColorsStrength, event2.reduceBrightColorsStrength);
+        assertEquals(event.reduceBrightColorsOffset, event2.reduceBrightColorsOffset, FLOAT_DELTA);
         assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
         assertArrayEquals(event.colorValueBuckets, event2.colorValueBuckets);
         assertEquals(event.colorSampleDuration, event2.colorSampleDuration);
@@ -1020,6 +1039,18 @@
         }
 
         @Override
+        public int getReduceBrightColorsStrength(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL,
+                    0);
+        }
+
+        @Override
+        public boolean isReduceBrightColorsActivated(Context context) {
+            return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+                    0) == 1;
+        }
+
+        @Override
         public DisplayedContentSample sampleColor(int noFramesToSample) {
             return new DisplayedContentSample(600L,
                     null,
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 26c304f..2e3178b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -818,10 +818,5 @@
                         PEAK_REFRESH_RATE_URI);
             }
         }
-
-        @Override
-        public boolean isDeviceInteractive(@NonNull Context context) {
-            return true;
-        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
new file mode 100644
index 0000000..35014dc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 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.display.color;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ReduceBrightColorsTintControllerTest {
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        final Resources mockResources = mock(Resources.class);
+        when(mockResources.getStringArray(
+                com.android.internal.R.array.config_reduceBrightColorsCoefficients))
+                .thenReturn(new String[]{"-0.000000000000001", "-0.955555555555554",
+                        "1.000000000000000"});
+        when(mockResources.getStringArray(
+                com.android.internal.R.array.config_reduceBrightColorsCoefficientsNonlinear))
+                .thenReturn(new String[]{"-0.4429953456", "-0.2434077725", "0.9809063061"});
+        mContext = mock(Context.class);
+        when(mContext.getResources()).thenReturn(mockResources);
+    }
+
+    @Test
+    public void setAndGetMatrix() {
+        final ReduceBrightColorsTintController tintController =
+                new ReduceBrightColorsTintController();
+        tintController.setUp(mContext, /* needsLinear= */ true);
+        tintController.setMatrix(50);
+        tintController.setActivated(true);
+        assertThat(tintController.getStrength()).isEqualTo(50);
+        assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+                .containsExactly(
+                        0.5222222f, 0f, 0f, 0f,
+                        0f, 0.5222222f, 0f, 0f,
+                        0f, 0f, 0.5222222f, 0f,
+                        0f, 0f, 0f, 1f)
+                .inOrder();
+    }
+
+    @Test
+    public void setAndGetMatrixClampToZero() {
+        final ReduceBrightColorsTintController tintController =
+                new ReduceBrightColorsTintController();
+        tintController.setUp(mContext, /* needsLinear= */ true);
+        tintController.setMatrix(-50);
+        tintController.setActivated(true);
+        assertThat(tintController.getStrength()).isEqualTo(0);
+        assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+                .containsExactly(
+                        1f, 0f, 0f, 0f,
+                        0f, 1f, 0f, 0f,
+                        0f, 0f, 1f, 0f,
+                        0f, 0f, 0f, 1f)
+                .inOrder();
+    }
+
+    @Test
+    public void setAndGetMatrixClampTo100() {
+        final ReduceBrightColorsTintController tintController =
+                new ReduceBrightColorsTintController();
+        tintController.setUp(mContext, /* needsLinear= */ true);
+        tintController.setMatrix(120);
+        tintController.setActivated(true);
+        assertThat(tintController.getStrength()).isEqualTo(100);
+        assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+                .containsExactly(
+                        0.04444444f, 0f, 0f, 0f,
+                        0f, 0.04444444f, 0f, 0f,
+                        0f, 0f, 0.04444444f, 0f,
+                        0f, 0f, 0f, 1f)
+                .inOrder();
+    }
+
+    @Test
+    public void returnsIdentityMatrixWhenNotActivated() {
+        final ReduceBrightColorsTintController tintController =
+                new ReduceBrightColorsTintController();
+        tintController.setUp(mContext, /* needsLinear= */ true);
+        tintController.setMatrix(50);
+        tintController.setActivated(true);
+        tintController.setActivated(false);
+        assertThat(tintController.getStrength()).isEqualTo(50);
+        assertThat(tintController.getMatrix()).usingTolerance(0.00001f)
+                .containsExactly(
+                        1f, 0f, 0f, 0f,
+                        0f, 1f, 0f, 0f,
+                        0f, 0f, 1f, 0f,
+                        0f, 0f, 0f, 1f)
+                .inOrder();
+    }
+
+    @Test
+    public void getAdjustedBrightnessZeroRbcStrengthFullBrightness() {
+        final ReduceBrightColorsTintController tintController =
+                new ReduceBrightColorsTintController();
+        tintController.setUp(mContext, /* needsLinear= */ true);
+        tintController.setMatrix(0);
+        assertThat(tintController.getAdjustedBrightness(450f)).isEqualTo(450f);
+    }
+
+    @Test
+    public void getAdjustedBrightnessFullRbcStrengthFullBrightness() {
+        final ReduceBrightColorsTintController tintController =
+                new ReduceBrightColorsTintController();
+        tintController.setUp(mContext, /* needsLinear= */ true);
+        tintController.setMatrix(100);
+        assertThat(tintController.getAdjustedBrightness(450f)).isEqualTo(19.999998f);
+    }
+
+    @Test
+    public void getAdjustedBrightnessZeroRbcStrengthLowBrightness() {
+        final ReduceBrightColorsTintController tintController =
+                new ReduceBrightColorsTintController();
+        tintController.setUp(mContext, /* needsLinear= */ true);
+        tintController.setMatrix(0);
+        assertThat(tintController.getAdjustedBrightness(2.2f)).isEqualTo(2.2f);
+    }
+
+    @Test
+    public void getAdjustedBrightnessFullRbcStrengthLowBrightness() {
+        final ReduceBrightColorsTintController tintController =
+                new ReduceBrightColorsTintController();
+        tintController.setUp(mContext, /* needsLinear= */ true);
+        tintController.setMatrix(100);
+        assertThat(tintController.getAdjustedBrightness(2.2f)).isEqualTo(0.09777778f);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java
new file mode 100644
index 0000000..275e7c7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/FontCrashDetectorTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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.graphics.fonts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.FileUtils;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class FontCrashDetectorTest {
+
+    private File mCacheDir;
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest");
+        FileUtils.deleteContentsAndDir(mCacheDir);
+        mCacheDir.mkdirs();
+    }
+
+    @Test
+    public void detectCrash() throws Exception {
+        // Prepare a marker file.
+        File file = new File(mCacheDir, "detectCrash");
+        assertThat(file.createNewFile()).isTrue();
+
+        FontCrashDetector detector = new FontCrashDetector(file);
+        assertThat(detector.hasCrashed()).isTrue();
+
+        detector.clear();
+        assertThat(detector.hasCrashed()).isFalse();
+        assertThat(file.exists()).isFalse();
+    }
+
+    @Test
+    public void monitorCrash() {
+        File file = new File(mCacheDir, "monitorCrash");
+        FontCrashDetector detector = new FontCrashDetector(file);
+        assertThat(detector.hasCrashed()).isFalse();
+
+        FontCrashDetector.MonitoredBlock block = detector.start();
+        assertThat(file.exists()).isTrue();
+
+        block.close();
+        assertThat(file.exists()).isFalse();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
new file mode 100644
index 0000000..86054e4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.graphics.fonts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class PersistentSystemFontConfigTest {
+
+    @Test
+    public void testWriteRead() throws IOException, XmlPullParserException {
+        long expectedModifiedDate = 1234567890;
+        PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+        config.lastModifiedDate = expectedModifiedDate;
+        config.updatedFontDirs.add("~~abc");
+        config.updatedFontDirs.add("~~def");
+
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+            PersistentSystemFontConfig.writeToXml(baos, config);
+
+            byte[] written = baos.toByteArray();
+            assertThat(written).isNotEmpty();
+
+            try (ByteArrayInputStream bais = new ByteArrayInputStream(written)) {
+                PersistentSystemFontConfig.Config another = new PersistentSystemFontConfig.Config();
+                PersistentSystemFontConfig.loadFromXml(bais, another);
+
+                assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate);
+                assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def");
+            }
+        }
+    }
+
+    @Test
+    public void testWrongType() throws IOException, XmlPullParserException {
+        String xml = "<fontConfig>"
+                + "  <lastModifiedDate value=\"string\" />"
+                + "</fontConfig>";
+
+        try (ByteArrayInputStream bais =
+                     new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
+            PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+            PersistentSystemFontConfig.loadFromXml(bais, config);
+            assertThat(config.lastModifiedDate).isEqualTo(0);
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 75bf1e6..cb83b0f 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -22,8 +22,12 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.graphics.fonts.FontManager;
+import android.graphics.fonts.FontUpdateRequest;
 import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.Presubmit;
+import android.system.Os;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -35,10 +39,12 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -107,6 +113,7 @@
 
     private File mCacheDir;
     private File mUpdatableFontFilesDir;
+    private File mConfigFile;
     private List<File> mPreinstalledFontDirs;
 
     @SuppressWarnings("ResultOfMethodCallIgnored")
@@ -124,6 +131,7 @@
         for (File dir : mPreinstalledFontDirs) {
             dir.mkdir();
         }
+        mConfigFile = new File(mCacheDir, "config.xml");
     }
 
     @After
@@ -133,19 +141,32 @@
 
     @Test
     public void construct() throws Exception {
+        long expectedModifiedDate = 1234567890;
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+        config.lastModifiedDate = expectedModifiedDate;
+        writeConfig(config, mConfigFile);
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
-        installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dirForPreparation.loadFontFileMap();
+        assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
+                .isEqualTo(expectedModifiedDate);
+        dirForPreparation.update(Arrays.asList(
+                newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
+                newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
+        assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
+                .isNotEqualTo(expectedModifiedDate);
 
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
         assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
         assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -159,7 +180,9 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
     }
 
@@ -168,18 +191,23 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
-        installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dirForPreparation.loadFontFileMap();
+        dirForPreparation.update(Arrays.asList(
+                newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
+                newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
         fakeFsverityUtil.remove(
                 dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath());
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -190,11 +218,14 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
-        installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dirForPreparation.loadFontFileMap();
+        dirForPreparation.update(Arrays.asList(
+                newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
+                newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
@@ -202,7 +233,9 @@
         FileUtils.stringToFile(dirForPreparation.getFontFileMap().get("foo.ttf"), "bar,4");
 
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
         assertThat(dir.getFontFileMap()).isEmpty();
         // All font dirs (including dir for "bar.ttf") should be deleted.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
@@ -213,11 +246,14 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
-        installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE);
-        installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dirForPreparation.loadFontFileMap();
+        dirForPreparation.update(Arrays.asList(
+                newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,2", GOOD_SIGNATURE),
+                newFontUpdateRequest("foo,3", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,4", GOOD_SIGNATURE)));
         // Four font dirs are created.
         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
 
@@ -226,7 +262,9 @@
         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "bar.ttf"), "bar,1");
         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2");
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
         // For foo.ttf, preinstalled font (revision 5) should be used.
         assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
         // For bar.ttf, updated font (revision 4) should be used.
@@ -239,15 +277,60 @@
     }
 
     @Test
+    public void construct_failedToLoadConfig() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                new File("/dev/null"));
+        dir.loadFontFileMap();
+        assertThat(dir.getFontFileMap()).isEmpty();
+    }
+
+    @Test
+    public void construct_afterBatchFailure() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dirForPreparation = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dirForPreparation.loadFontFileMap();
+        dirForPreparation.update(
+                Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+        try {
+            dirForPreparation.update(Arrays.asList(
+                    newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
+                    newFontUpdateRequest("bar,2", "Invalid signature")));
+            fail("Batch update with invalid signature should fail");
+        } catch (FontManagerService.SystemFontException e) {
+            // Expected
+        }
+
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+        // The state should be rolled back as a whole if one of the update requests fail.
+        assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
+        assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
+    }
+
+    @Test
     public void installFontFile() throws Exception {
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
 
-        installFontFile(dir, "test,1", GOOD_SIGNATURE);
+        dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
         assertThat(dir.getFontFileMap()).containsKey("test.ttf");
         assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(1);
+        File fontFile = dir.getFontFileMap().get("test.ttf");
+        assertThat(Os.stat(fontFile.getAbsolutePath()).st_mode & 0777).isEqualTo(0644);
+        File fontDir = fontFile.getParentFile();
+        assertThat(Os.stat(fontDir.getAbsolutePath()).st_mode & 0777).isEqualTo(0711);
     }
 
     @Test
@@ -255,11 +338,13 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
 
-        installFontFile(dir, "test,1", GOOD_SIGNATURE);
+        dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
         Map<String, File> mapBeforeUpgrade = dir.getFontFileMap();
-        installFontFile(dir, "test,2", GOOD_SIGNATURE);
+        dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
         assertThat(dir.getFontFileMap()).containsKey("test.ttf");
         assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(2);
         assertThat(mapBeforeUpgrade).containsKey("test.ttf");
@@ -272,14 +357,16 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
 
-        installFontFile(dir, "test,2", GOOD_SIGNATURE);
+        dir.update(Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
         try {
-            installFontFile(dir, "test,1", GOOD_SIGNATURE);
+            dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
             fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expect
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
         }
         assertThat(dir.getFontFileMap()).containsKey("test.ttf");
         assertWithMessage("Font should not be downgraded to an older revision")
@@ -291,10 +378,12 @@
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
 
-        installFontFile(dir, "foo,1", GOOD_SIGNATURE);
-        installFontFile(dir, "bar,2", GOOD_SIGNATURE);
+        dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+        dir.update(Collections.singletonList(newFontUpdateRequest("bar,2", GOOD_SIGNATURE)));
         assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
         assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
         assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
@@ -302,17 +391,39 @@
     }
 
     @Test
-    public void installFontFile_invalidSignature() {
+    public void installFontFile_batch() throws Exception {
         FakeFontFileParser parser = new FakeFontFileParser();
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        dir.update(Arrays.asList(
+                newFontUpdateRequest("foo,1", GOOD_SIGNATURE),
+                newFontUpdateRequest("bar,2", GOOD_SIGNATURE)));
+        assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
+        assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
+        assertThat(dir.getFontFileMap()).containsKey("bar.ttf");
+        assertThat(parser.getRevision(dir.getFontFileMap().get("bar.ttf"))).isEqualTo(2);
+    }
+
+    @Test
+    public void installFontFile_invalidSignature() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
 
         try {
-            installFontFile(dir, "test,1", "Invalid signature");
-            fail("Expect IOException");
-        } catch (IOException e) {
-            // Expect
+            dir.update(
+                    Collections.singletonList(newFontUpdateRequest("test,1", "Invalid signature")));
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.RESULT_ERROR_VERIFICATION_FAILURE);
         }
         assertThat(dir.getFontFileMap()).isEmpty();
     }
@@ -323,23 +434,184 @@
         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1");
         UpdatableFontDir dir = new UpdatableFontDir(
-                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil);
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
 
         try {
-            installFontFile(dir, "test,1", GOOD_SIGNATURE);
+            dir.update(Collections.singletonList(newFontUpdateRequest("test,1", GOOD_SIGNATURE)));
             fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expect
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
         }
         assertThat(dir.getFontFileMap()).isEmpty();
     }
 
-    private void installFontFile(UpdatableFontDir dir, String content, String signature)
-            throws IOException {
+    @Test
+    public void installFontFile_failedToWriteConfigXml() throws Exception {
+        long expectedModifiedDate = 1234567890;
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1");
+
+        File readonlyDir = new File(mCacheDir, "readonly");
+        assertThat(readonlyDir.mkdir()).isTrue();
+        File readonlyFile = new File(readonlyDir, "readonly_config.xml");
+
+        PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
+        config.lastModifiedDate = expectedModifiedDate;
+        writeConfig(config, readonlyFile);
+
+        assertThat(readonlyDir.setWritable(false, false)).isTrue();
+        try {
+            UpdatableFontDir dir = new UpdatableFontDir(
+                    mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                    readonlyFile);
+            dir.loadFontFileMap();
+
+            try {
+                dir.update(
+                        Collections.singletonList(newFontUpdateRequest("test,2", GOOD_SIGNATURE)));
+            } catch (FontManagerService.SystemFontException e) {
+                assertThat(e.getErrorCode())
+                        .isEqualTo(FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG);
+            }
+            assertThat(dir.getSystemFontConfig().getLastModifiedTimeMillis())
+                    .isEqualTo(expectedModifiedDate);
+            assertThat(dir.getFontFileMap()).isEmpty();
+        } finally {
+            assertThat(readonlyDir.setWritable(true, true)).isTrue();
+        }
+    }
+
+    @Test
+    public void installFontFile_failedToParsePostScript() throws Exception {
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs,
+                new UpdatableFontDir.FontFileParser() {
+                    @Override
+                    public String getPostScriptName(File file) throws IOException {
+                        return null;
+                    }
+
+                    @Override
+                    public long getRevision(File file) throws IOException {
+                        return 0;
+                    }
+                }, fakeFsverityUtil, mConfigFile);
+        dir.loadFontFileMap();
+
+        try {
+            dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.RESULT_ERROR_INVALID_FONT_NAME);
+        }
+        assertThat(dir.getFontFileMap()).isEmpty();
+    }
+
+    @Test
+    public void installFontFile_failedToParsePostScriptName_invalidFont() throws Exception {
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs,
+                new UpdatableFontDir.FontFileParser() {
+                    @Override
+                    public String getPostScriptName(File file) throws IOException {
+                        throw new IOException();
+                    }
+
+                    @Override
+                    public long getRevision(File file) throws IOException {
+                        return 0;
+                    }
+                }, fakeFsverityUtil, mConfigFile);
+        dir.loadFontFileMap();
+
+        try {
+            dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.RESULT_ERROR_INVALID_FONT_FILE);
+        }
+        assertThat(dir.getFontFileMap()).isEmpty();
+    }
+
+    @Test
+    public void installFontFile_renameToPsNameFailure() throws Exception {
+        UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() {
+            private final FakeFsverityUtil mFake = new FakeFsverityUtil();
+
+            @Override
+            public boolean hasFsverity(String path) {
+                return mFake.hasFsverity(path);
+            }
+
+            @Override
+            public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
+                mFake.setUpFsverity(path, pkcs7Signature);
+            }
+
+            @Override
+            public boolean rename(File src, File dest) {
+                return false;
+            }
+        };
+        FakeFontFileParser parser = new FakeFontFileParser();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        try {
+            dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE);
+        }
+        assertThat(dir.getFontFileMap()).isEmpty();
+    }
+
+    @Test
+    public void installFontFile_batchFailure() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dir = new UpdatableFontDir(
+                mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
+                mConfigFile);
+        dir.loadFontFileMap();
+
+        dir.update(Collections.singletonList(newFontUpdateRequest("foo,1", GOOD_SIGNATURE)));
+        try {
+            dir.update(Arrays.asList(
+                    newFontUpdateRequest("foo,2", GOOD_SIGNATURE),
+                    newFontUpdateRequest("bar,2", "Invalid signature")));
+            fail("Batch update with invalid signature should fail");
+        } catch (FontManagerService.SystemFontException e) {
+            // Expected
+        }
+        // The state should be rolled back as a whole if one of the update requests fail.
+        assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
+        assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(1);
+    }
+
+    private FontUpdateRequest newFontUpdateRequest(String content, String signature)
+            throws Exception {
         File file = File.createTempFile("font", "ttf", mCacheDir);
         FileUtils.stringToFile(file, content);
-        try (FileInputStream in = new FileInputStream(file)) {
-            dir.installFontFile(in.getFD(), signature.getBytes());
+        return new FontUpdateRequest(
+                ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY),
+                signature.getBytes());
+    }
+
+    private void writeConfig(PersistentSystemFontConfig.Config config,
+            File file) throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            PersistentSystemFontConfig.writeToXml(fos, config);
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
index a408d4c..137bd88 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeHdmiCecConfig.java
@@ -113,6 +113,15 @@
                     + "    </allowed-values>"
                     + "    <default-value int-value=\"1\" />"
                     + "  </setting>"
+                    + "  <setting name=\"tv_send_standby_on_sleep\""
+                    + "           value-type=\"int\""
+                    + "           user-configurable=\"true\">"
+                    + "    <allowed-values>"
+                    + "      <value int-value=\"0\" />"
+                    + "      <value int-value=\"1\" />"
+                    + "    </allowed-values>"
+                    + "    <default-value int-value=\"1\" />"
+                    + "  </setting>"
                     + "  <setting name=\"rc_profile_tv\""
                     + "           value-type=\"int\""
                     + "           user-configurable=\"false\">"
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 95a0a74..eedbc95 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -596,6 +596,15 @@
     }
 
     @Test
+    public void setArcStatus() {
+        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+
+        mHdmiCecLocalDeviceAudioSystem.setArcStatus(false);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+    }
+
+    @Test
     @Ignore("b/151150320")
     public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() {
         HdmiCecMessage message =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 882d2f5..e6b56ca 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -35,7 +35,6 @@
 import android.os.PowerManager;
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
-import android.provider.Settings.Global;
 import android.sysprop.HdmiProperties;
 import android.view.KeyEvent;
 
@@ -133,7 +132,6 @@
                     }
                 };
 
-        mHdmiControlService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true);
         mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService);
         mHdmiCecLocalDevicePlayback.init();
         mHdmiControlService.setIoLooper(mMyLooper);
@@ -560,7 +558,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_TV);
         mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
                 "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -580,7 +580,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_BROADCAST);
         mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
                 "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -600,7 +602,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_NONE);
         mHdmiCecLocalDevicePlayback.setActiveSource(ADDR_TV, 0x0000,
                 "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -620,7 +624,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_TV);
         mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -640,7 +646,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_BROADCAST);
         mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
@@ -660,7 +668,9 @@
                 HdmiControlManager.POWER_CONTROL_MODE_NONE);
         mHdmiCecLocalDevicePlayback.setActiveSource(mPlaybackLogicalAddress,
                 mPlaybackPhysicalAddress, "HdmiCecLocalDevicePlaybackTest");
-        mHdmiCecLocalDevicePlayback.setAutoDeviceOff(true);
+        mHdmiCecLocalDevicePlayback.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         mHdmiCecLocalDevicePlayback.onStandby(false, HdmiControlService.STANDBY_SCREEN_OFF);
         mTestLooper.dispatchAll();
 
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index ec806fab..0f527f3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.hdmi;
 
+import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
@@ -269,6 +270,30 @@
     }
 
     @Test
+    public void tvSendStandbyOnSleep_Enabled() {
+        mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED);
+        mTestLooper.dispatchAll();
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage standby = HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_BROADCAST);
+        assertThat(mNativeWrapper.getResultMessages()).contains(standby);
+    }
+
+    @Test
+    public void tvSendStandbyOnSleep_Disabled() {
+        mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
+                HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_DISABLED);
+        mTestLooper.dispatchAll();
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage standby = HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_BROADCAST);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(standby);
+    }
+
+    @Test
     public void getRcFeatures() {
         ArrayList<Integer> features = new ArrayList<>(mHdmiCecLocalDeviceTv.getRcFeatures());
         assertThat(features.contains(Constants.RC_PROFILE_TV_NONE)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java
deleted file mode 100644
index e5529cb..0000000
--- a/services/tests/servicestests/src/com/android/server/job/JobCountTrackerTest.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright (C) 2019 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.job;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.util.Log;
-
-import com.android.server.job.JobConcurrencyManager.JobCountTracker;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Random;
-
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-/**
- * Test for {@link com.android.server.job.JobConcurrencyManager.JobCountTracker}.
- */
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class JobCountTrackerTest {
-    private static final String TAG = "JobCountTrackerTest";
-
-    private Random mRandom;
-    private JobCountTracker mJobCountTracker;
-
-    @Before
-    public void setUp() {
-        mRandom = new Random(1); // Always use the same series of pseudo random values.
-        mJobCountTracker = new JobCountTracker();
-    }
-
-    /**
-     * Represents running and pending jobs.
-     */
-    class Jobs {
-        public int runningFg;
-        public int runningBg;
-        public int pendingFg;
-        public int pendingBg;
-
-        public void maybeEnqueueJobs(double startRatio, double fgJobRatio) {
-            while (mRandom.nextDouble() < startRatio) {
-                if (mRandom.nextDouble() < fgJobRatio) {
-                    pendingFg++;
-                } else {
-                    pendingBg++;
-                }
-            }
-        }
-
-        public void maybeFinishJobs(double stopRatio) {
-            for (int i = runningBg; i > 0; i--) {
-                if (mRandom.nextDouble() < stopRatio) {
-                    runningBg--;
-                }
-            }
-            for (int i = runningFg; i > 0; i--) {
-                if (mRandom.nextDouble() < stopRatio) {
-                    runningFg--;
-                }
-            }
-        }
-    }
-
-
-    private void startPendingJobs(Jobs jobs, int totalMax, int maxBg, int minBg) {
-        mJobCountTracker.reset(totalMax, maxBg, minBg);
-
-        for (int i = 0; i < jobs.runningFg; i++) {
-            mJobCountTracker.incrementRunningJobCount(true);
-        }
-        for (int i = 0; i < jobs.runningBg; i++) {
-            mJobCountTracker.incrementRunningJobCount(false);
-        }
-
-        for (int i = 0; i < jobs.pendingFg; i++) {
-            mJobCountTracker.incrementPendingJobCount(true);
-        }
-        for (int i = 0; i < jobs.pendingBg; i++) {
-            mJobCountTracker.incrementPendingJobCount(false);
-        }
-
-        mJobCountTracker.onCountDone();
-
-        while ((jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true))
-                || (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false))) {
-            final boolean isStartingFg = mRandom.nextBoolean();
-
-            if (isStartingFg) {
-                if (jobs.pendingFg > 0 && mJobCountTracker.canJobStart(true)) {
-                    jobs.pendingFg--;
-                    jobs.runningFg++;
-                    mJobCountTracker.onStartingNewJob(true);
-                }
-            } else {
-                if (jobs.pendingBg > 0 && mJobCountTracker.canJobStart(false)) {
-                    jobs.pendingBg--;
-                    jobs.runningBg++;
-                    mJobCountTracker.onStartingNewJob(false);
-                }
-            }
-        }
-
-        Log.i(TAG, "" + mJobCountTracker);
-    }
-
-    /**
-     * Used by the following testRandom* tests.
-     */
-    private void checkRandom(Jobs jobs, int numTests, int totalMax, int maxBg, int minBg,
-            double startRatio, double fgJobRatio, double stopRatio) {
-        for (int i = 0; i < numTests; i++) {
-
-            jobs.maybeFinishJobs(stopRatio);
-            jobs.maybeEnqueueJobs(startRatio, fgJobRatio);
-
-            startPendingJobs(jobs, totalMax, maxBg, minBg);
-
-            assertThat(jobs.runningFg).isAtMost(totalMax);
-            assertThat(jobs.runningBg).isAtMost(totalMax);
-            assertThat(jobs.runningFg + jobs.runningBg).isAtMost(totalMax);
-            assertThat(jobs.runningBg).isAtMost(maxBg);
-        }
-    }
-
-    /**
-     * Randomly enqueue / stop jobs and make sure we won't run more jobs than we should.
-     */
-    @Test
-    public void testRandom1() {
-        final Jobs jobs = new Jobs();
-
-        final int numTests = 5000;
-        final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
-        final double stopRatio = 0.1;
-        final double fgJobRatio = 0.5;
-        final double startRatio = 0.1;
-
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio , stopRatio);
-    }
-
-    @Test
-    public void testRandom2() {
-        final Jobs jobs = new Jobs();
-
-        final int numTests = 5000;
-        final int totalMax = 2;
-        final int maxBg = 2;
-        final int minBg = 0;
-        final double stopRatio = 0.5;
-        final double fgJobRatio = 0.5;
-        final double startRatio = 0.5;
-
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
-    }
-
-    @Test
-    public void testRandom3() {
-        final Jobs jobs = new Jobs();
-
-        final int numTests = 5000;
-        final int totalMax = 2;
-        final int maxBg = 2;
-        final int minBg = 2;
-        final double stopRatio = 0.5;
-        final double fgJobRatio = 0.5;
-        final double startRatio = 0.5;
-
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
-    }
-
-    @Test
-    public void testRandom4() {
-        final Jobs jobs = new Jobs();
-
-        final int numTests = 5000;
-        final int totalMax = 10;
-        final int maxBg = 2;
-        final int minBg = 0;
-        final double stopRatio = 0.5;
-        final double fgJobRatio = 0.5;
-        final double startRatio = 0.5;
-
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
-    }
-
-    @Test
-    public void testRandom5() {
-        final Jobs jobs = new Jobs();
-
-        final int numTests = 5000;
-        final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
-        final double stopRatio = 0.5;
-        final double fgJobRatio = 0.1;
-        final double startRatio = 0.5;
-
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
-    }
-
-    @Test
-    public void testRandom6() {
-        final Jobs jobs = new Jobs();
-
-        final int numTests = 5000;
-        final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
-        final double stopRatio = 0.5;
-        final double fgJobRatio = 0.9;
-        final double startRatio = 0.5;
-
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
-    }
-
-    @Test
-    public void testRandom7() {
-        final Jobs jobs = new Jobs();
-
-        final int numTests = 5000;
-        final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
-        final double stopRatio = 0.4;
-        final double fgJobRatio = 0.1;
-        final double startRatio = 0.5;
-
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
-    }
-
-    @Test
-    public void testRandom8() {
-        final Jobs jobs = new Jobs();
-
-        final int numTests = 5000;
-        final int totalMax = 6;
-        final int maxBg = 4;
-        final int minBg = 2;
-        final double stopRatio = 0.4;
-        final double fgJobRatio = 0.9;
-        final double startRatio = 0.5;
-
-        checkRandom(jobs, numTests, totalMax, maxBg, minBg, startRatio, fgJobRatio, stopRatio);
-    }
-
-    /** Used by the following tests */
-    private void checkSimple(int totalMax, int maxBg, int minBg,
-            int runningFg, int runningBg, int pendingFg, int pendingBg,
-            int resultRunningFg, int resultRunningBg, int resultPendingFg, int resultPendingBg) {
-        final Jobs jobs = new Jobs();
-        jobs.runningFg = runningFg;
-        jobs.runningBg = runningBg;
-        jobs.pendingFg = pendingFg;
-        jobs.pendingBg = pendingBg;
-
-        startPendingJobs(jobs, totalMax, maxBg, minBg);
-
-        assertThat(jobs.runningFg).isEqualTo(resultRunningFg);
-        assertThat(jobs.runningBg).isEqualTo(resultRunningBg);
-
-        assertThat(jobs.pendingFg).isEqualTo(resultPendingFg);
-        assertThat(jobs.pendingBg).isEqualTo(resultPendingBg);
-    }
-
-
-    @Test
-    public void testBasic() {
-        // Args are:
-        // First 3: Total-max, bg-max, bg-min.
-        // Next 2:  Running FG / BG
-        // Next 2:  Pending FG / BG
-        // Next 4:  Result running FG / BG, pending FG/BG.
-        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 1, 0, /*res run/pen=*/ 1, 0, 0, 0);
-
-        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 0, /*res run/pen=*/ 6, 0, 4, 0);
-
-        // When there are BG jobs pending, 2 (min-BG) jobs should run.
-        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 1, /*res run/pen=*/ 5, 1, 5, 0);
-        checkSimple(6, 4, 2, /*run=*/ 0, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 4, 2, 6, 1);
-
-        checkSimple(6, 4, 2, /*run=*/ 6, 0, /*pen=*/ 10, 3, /*res run/pen=*/ 6, 0, 10, 3);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java b/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
deleted file mode 100644
index 4c36747..0000000
--- a/services/tests/servicestests/src/com/android/server/job/MaxJobCountsTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2019 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.job;
-
-import android.annotation.Nullable;
-import android.provider.DeviceConfig;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.job.JobSchedulerService.MaxJobCounts;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MaxJobCountsTest {
-    @After
-    public void tearDown() throws Exception {
-        resetConfig();
-    }
-
-    private void resetConfig() {
-        // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually.
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "total", "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "maxbg", "", false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, "minbg", "", false);
-    }
-
-    private void check(@Nullable DeviceConfig.Properties config,
-            int defaultTotal, int defaultMaxBg, int defaultMinBg,
-            int expectedTotal, int expectedMaxBg, int expectedMinBg) throws Exception {
-        resetConfig();
-        if (config != null) {
-            DeviceConfig.setProperties(config);
-        }
-
-        final MaxJobCounts counts = new JobSchedulerService.MaxJobCounts(
-                defaultTotal, "total",
-                defaultMaxBg, "maxbg",
-                defaultMinBg, "minbg");
-
-        counts.update();
-
-        Assert.assertEquals(expectedTotal, counts.getMaxTotal());
-        Assert.assertEquals(expectedMaxBg, counts.getMaxBg());
-        Assert.assertEquals(expectedMinBg, counts.getMinBg());
-    }
-
-    @Test
-    public void test() throws Exception {
-        // Tests with various combinations.
-        check(null, /*default*/ 5, 1, 0, /*expected*/ 5, 1, 0);
-        check(null, /*default*/ 5, 0, 0, /*expected*/ 5, 1, 0);
-        check(null, /*default*/ 0, 0, 0, /*expected*/ 1, 1, 0);
-        check(null, /*default*/ -1, -1, -1, /*expected*/ 1, 1, 0);
-        check(null, /*default*/ 5, 5, 5, /*expected*/ 5, 5, 4);
-        check(null, /*default*/ 6, 5, 6, /*expected*/ 6, 5, 5);
-        check(null, /*default*/ 4, 5, 6, /*expected*/ 4, 4, 3);
-        check(null, /*default*/ 5, 1, 1, /*expected*/ 5, 1, 1);
-        check(null, /*default*/ 15, 15, 15, /*expected*/ 15, 15, 14);
-        check(null, /*default*/ 16, 16, 16, /*expected*/ 16, 16, 15);
-        check(null, /*default*/ 20, 20, 20, /*expected*/ 16, 16, 15);
-
-        // Test for overriding with a setting string.
-        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt("total", 5)
-                        .setInt("maxbg", 4)
-                        .setInt("minbg", 3)
-                        .build(),
-                /*default*/ 9, 9, 9, /*expected*/ 5, 4, 3);
-        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt("total", 5).build(),
-                /*default*/ 9, 9, 9, /*expected*/ 5, 5, 4);
-        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt("maxbg", 4).build(),
-                /*default*/ 9, 9, 9, /*expected*/ 9, 4, 4);
-        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
-                        .setInt("minbg", 3).build(),
-                /*default*/ 9, 9, 9, /*expected*/ 9, 9, 3);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
new file mode 100644
index 0000000..263cf48
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2019 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.job;
+
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.NonNull;
+import android.util.Pair;
+import android.util.SparseIntArray;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.job.JobConcurrencyManager.WorkCountTracker;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Test for {@link WorkCountTracker}.
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class WorkCountTrackerTest {
+    private static final String TAG = "WorkerCountTrackerTest";
+
+    private Random mRandom;
+    private WorkCountTracker mWorkCountTracker;
+
+    @Before
+    public void setUp() {
+        mRandom = new Random(1); // Always use the same series of pseudo random values.
+        mWorkCountTracker = new WorkCountTracker();
+    }
+
+    /**
+     * Represents running and pending jobs.
+     */
+    class Jobs {
+        public final SparseIntArray running = new SparseIntArray();
+        public final SparseIntArray pending = new SparseIntArray();
+
+        public void maybeEnqueueJobs(double startRatio, double fgJobRatio) {
+            while (mRandom.nextDouble() < startRatio) {
+                if (mRandom.nextDouble() < fgJobRatio) {
+                    pending.put(WORK_TYPE_TOP, pending.get(WORK_TYPE_TOP) + 1);
+                } else {
+                    pending.put(WORK_TYPE_BG, pending.get(WORK_TYPE_BG) + 1);
+                }
+            }
+        }
+
+        public void maybeFinishJobs(double stopRatio) {
+            for (int i = running.get(WORK_TYPE_BG); i > 0; i--) {
+                if (mRandom.nextDouble() < stopRatio) {
+                    running.put(WORK_TYPE_BG, running.get(WORK_TYPE_BG) - 1);
+                    mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+                }
+            }
+            for (int i = running.get(WORK_TYPE_TOP); i > 0; i--) {
+                if (mRandom.nextDouble() < stopRatio) {
+                    running.put(WORK_TYPE_TOP, running.get(WORK_TYPE_TOP) - 1);
+                    mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+                }
+            }
+        }
+    }
+
+    private void recount(Jobs jobs, int totalMax,
+            @NonNull List<Pair<Integer, Integer>> minLimits,
+            @NonNull List<Pair<Integer, Integer>> maxLimits) {
+        mWorkCountTracker.setConfig(new JobConcurrencyManager.WorkTypeConfig(
+                "test", totalMax, minLimits, maxLimits));
+        mWorkCountTracker.resetCounts();
+
+        for (int i = 0; i < jobs.running.size(); ++i) {
+            final int workType = jobs.running.keyAt(i);
+            final int count = jobs.running.valueAt(i);
+
+            for (int c = 0; c < count; ++c) {
+                mWorkCountTracker.incrementRunningJobCount(workType);
+            }
+        }
+        for (int i = 0; i < jobs.pending.size(); ++i) {
+            final int workType = jobs.pending.keyAt(i);
+            final int count = jobs.pending.valueAt(i);
+
+            for (int c = 0; c < count; ++c) {
+                mWorkCountTracker.incrementPendingJobCount(workType);
+            }
+        }
+
+        mWorkCountTracker.onCountDone();
+    }
+
+    private void startPendingJobs(Jobs jobs) {
+        while ((jobs.pending.get(WORK_TYPE_TOP) > 0
+                && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE)
+                || (jobs.pending.get(WORK_TYPE_BG) > 0
+                && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE)) {
+            final boolean isStartingFg = mRandom.nextBoolean();
+
+            if (isStartingFg) {
+                if (jobs.pending.get(WORK_TYPE_TOP) > 0
+                        && mWorkCountTracker.canJobStart(WORK_TYPE_TOP) != WORK_TYPE_NONE) {
+                    jobs.pending.put(WORK_TYPE_TOP, jobs.pending.get(WORK_TYPE_TOP) - 1);
+                    jobs.running.put(WORK_TYPE_TOP, jobs.running.get(WORK_TYPE_TOP) + 1);
+                    mWorkCountTracker.stageJob(WORK_TYPE_TOP);
+                    mWorkCountTracker.onJobStarted(WORK_TYPE_TOP);
+                }
+            } else {
+                if (jobs.pending.get(WORK_TYPE_BG) > 0
+                        && mWorkCountTracker.canJobStart(WORK_TYPE_BG) != WORK_TYPE_NONE) {
+                    jobs.pending.put(WORK_TYPE_BG, jobs.pending.get(WORK_TYPE_BG) - 1);
+                    jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) + 1);
+                    mWorkCountTracker.stageJob(WORK_TYPE_BG);
+                    mWorkCountTracker.onJobStarted(WORK_TYPE_BG);
+                }
+            }
+        }
+    }
+
+    /**
+     * Used by the following testRandom* tests.
+     */
+    private void checkRandom(Jobs jobs, int numTests, int totalMax,
+            @NonNull List<Pair<Integer, Integer>> minLimits,
+            @NonNull List<Pair<Integer, Integer>> maxLimits,
+            double startRatio, double fgJobRatio, double stopRatio) {
+        for (int i = 0; i < numTests; i++) {
+            jobs.maybeFinishJobs(stopRatio);
+            jobs.maybeEnqueueJobs(startRatio, fgJobRatio);
+
+            recount(jobs, totalMax, minLimits, maxLimits);
+            startPendingJobs(jobs);
+
+            int totalRunning = 0;
+            for (int r = 0; r < jobs.running.size(); ++r) {
+                final int numRunning = jobs.running.valueAt(r);
+                assertWithMessage(
+                        "Work type " + jobs.running.keyAt(r) + " is running too many jobs")
+                        .that(numRunning).isAtMost(totalMax);
+                totalRunning += numRunning;
+            }
+            assertThat(totalRunning).isAtMost(totalMax);
+            for (Pair<Integer, Integer> maxLimit : maxLimits) {
+                assertWithMessage("Work type " + maxLimit.first + " is running too many jobs")
+                        .that(jobs.running.get(maxLimit.first)).isAtMost(maxLimit.second);
+            }
+        }
+    }
+
+    /**
+     * Randomly enqueue / stop jobs and make sure we won't run more jobs than we should.
+     */
+    @Test
+    public void testRandom1() {
+        final Jobs jobs = new Jobs();
+
+        final int numTests = 5000;
+        final int totalMax = 6;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final double stopRatio = 0.1;
+        final double fgJobRatio = 0.5;
+        final double startRatio = 0.1;
+
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
+    }
+
+    @Test
+    public void testRandom2() {
+        final Jobs jobs = new Jobs();
+
+        final int numTests = 5000;
+        final int totalMax = 2;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final List<Pair<Integer, Integer>> minLimits = List.of();
+        final double stopRatio = 0.5;
+        final double fgJobRatio = 0.5;
+        final double startRatio = 0.5;
+
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
+    }
+
+    @Test
+    public void testRandom3() {
+        final Jobs jobs = new Jobs();
+
+        final int numTests = 5000;
+        final int totalMax = 2;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final double stopRatio = 0.5;
+        final double fgJobRatio = 0.5;
+        final double startRatio = 0.5;
+
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
+    }
+
+    @Test
+    public void testRandom4() {
+        final Jobs jobs = new Jobs();
+
+        final int numTests = 5000;
+        final int totalMax = 10;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final List<Pair<Integer, Integer>> minLimits = List.of();
+        final double stopRatio = 0.5;
+        final double fgJobRatio = 0.5;
+        final double startRatio = 0.5;
+
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
+    }
+
+    @Test
+    public void testRandom5() {
+        final Jobs jobs = new Jobs();
+
+        final int numTests = 5000;
+        final int totalMax = 6;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final double stopRatio = 0.5;
+        final double fgJobRatio = 0.1;
+        final double startRatio = 0.5;
+
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
+    }
+
+    @Test
+    public void testRandom6() {
+        final Jobs jobs = new Jobs();
+
+        final int numTests = 5000;
+        final int totalMax = 6;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final double stopRatio = 0.5;
+        final double fgJobRatio = 0.9;
+        final double startRatio = 0.5;
+
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
+    }
+
+    @Test
+    public void testRandom7() {
+        final Jobs jobs = new Jobs();
+
+        final int numTests = 5000;
+        final int totalMax = 6;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final double stopRatio = 0.4;
+        final double fgJobRatio = 0.1;
+        final double startRatio = 0.5;
+
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
+    }
+
+    @Test
+    public void testRandom8() {
+        final Jobs jobs = new Jobs();
+
+        final int numTests = 5000;
+        final int totalMax = 6;
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 4));
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 2));
+        final double stopRatio = 0.4;
+        final double fgJobRatio = 0.9;
+        final double startRatio = 0.5;
+
+        checkRandom(jobs, numTests, totalMax, minLimits, maxLimits,
+                startRatio, fgJobRatio, stopRatio);
+    }
+
+    /** Used by the following tests */
+    private void checkSimple(int totalMax,
+            @NonNull List<Pair<Integer, Integer>> minLimits,
+            @NonNull List<Pair<Integer, Integer>> maxLimits,
+            @NonNull List<Pair<Integer, Integer>> running,
+            @NonNull List<Pair<Integer, Integer>> pending,
+            @NonNull List<Pair<Integer, Integer>> resultRunning,
+            @NonNull List<Pair<Integer, Integer>> resultPending) {
+        final Jobs jobs = new Jobs();
+        for (Pair<Integer, Integer> run : running) {
+            jobs.running.put(run.first, run.second);
+        }
+        for (Pair<Integer, Integer> pend : pending) {
+            jobs.pending.put(pend.first, pend.second);
+        }
+
+        recount(jobs, totalMax, minLimits, maxLimits);
+        startPendingJobs(jobs);
+
+        for (Pair<Integer, Integer> run : resultRunning) {
+            assertWithMessage("Incorrect running result for work type " + run.first)
+                    .that(jobs.running.get(run.first)).isEqualTo(run.second);
+        }
+        for (Pair<Integer, Integer> pend : resultPending) {
+            assertWithMessage("Incorrect pending result for work type " + pend.first)
+                    .that(jobs.pending.get(pend.first)).isEqualTo(pend.second);
+        }
+    }
+
+    @Test
+    public void testBasic() {
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 1)),
+                /* resPen */ List.of());
+
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 4)));
+
+        // When there are BG jobs pending, 2 (min-BG) jobs should run.
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 1)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 5), Pair.create(WORK_TYPE_BG, 1)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 5)));
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 2)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 1)));
+
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* run */ List.of(),
+                /* pen */ List.of(Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 43)));
+
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_BG, 47)));
+
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
+                /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 4)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 4), Pair.create(WORK_TYPE_BG, 4)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 6)),
+                /* run */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 1)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 48)));
+
+        checkSimple(8,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+                /* max */ List.of(Pair.create(WORK_TYPE_TOP, 6), Pair.create(WORK_TYPE_BG, 2)),
+                /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 49)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 2), Pair.create(WORK_TYPE_BG, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 8), Pair.create(WORK_TYPE_BG, 49)));
+
+        checkSimple(6,
+                /* min */ List.of(Pair.create(WORK_TYPE_BG, 2)),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 4)),
+                /* run */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_TOP, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
+
+        // This could happen if we lower the effective config due to higher memory pressure after
+        // we've already started running jobs. We shouldn't stop already running jobs, but also
+        // shouldn't start new ones.
+        checkSimple(5,
+                /* min */ List.of(),
+                /* max */ List.of(Pair.create(WORK_TYPE_BG, 1)),
+                /* run */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* pen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)),
+                /* resRun */ List.of(Pair.create(WORK_TYPE_BG, 6)),
+                /* resPen */ List.of(Pair.create(WORK_TYPE_TOP, 10), Pair.create(WORK_TYPE_BG, 3)));
+    }
+
+    /** Tests that the counter updates properly when jobs are stopped. */
+    @Test
+    public void testJobLifecycleLoop() {
+        final Jobs jobs = new Jobs();
+        jobs.pending.put(WORK_TYPE_TOP, 11);
+        jobs.pending.put(WORK_TYPE_BG, 10);
+
+        final int totalMax = 6;
+        final List<Pair<Integer, Integer>> minLimits = List.of(Pair.create(WORK_TYPE_BG, 1));
+        final List<Pair<Integer, Integer>> maxLimits = List.of(Pair.create(WORK_TYPE_BG, 5));
+
+        recount(jobs, totalMax, minLimits, maxLimits);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(5);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(6);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(9);
+
+        // Stop all jobs
+        jobs.maybeFinishJobs(1);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(5);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(8);
+
+        // Stop only a bg job and make sure the counter only allows another bg job to start.
+        jobs.running.put(WORK_TYPE_BG, jobs.running.get(WORK_TYPE_BG) - 1);
+        mWorkCountTracker.onJobFinished(WORK_TYPE_BG);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_NONE);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(5);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(7);
+
+        // Stop only a top job and make sure the counter only allows another top job to start.
+        jobs.running.put(WORK_TYPE_TOP, jobs.running.get(WORK_TYPE_TOP) - 1);
+        mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_TOP)).isEqualTo(WORK_TYPE_TOP);
+        assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_NONE);
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(5);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(1);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(7);
+
+        // Now that there are no more TOP jobs pending, BG should be able to start when TOP stops.
+        for (int i = jobs.running.get(WORK_TYPE_TOP); i > 0; --i) {
+            jobs.running.put(WORK_TYPE_TOP, jobs.running.get(WORK_TYPE_TOP) - 1);
+            mWorkCountTracker.onJobFinished(WORK_TYPE_TOP);
+
+            assertThat(mWorkCountTracker.canJobStart(WORK_TYPE_BG)).isEqualTo(WORK_TYPE_BG);
+        }
+
+        startPendingJobs(jobs);
+
+        assertThat(jobs.running.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.running.get(WORK_TYPE_BG)).isEqualTo(5);
+        assertThat(jobs.pending.get(WORK_TYPE_TOP)).isEqualTo(0);
+        assertThat(jobs.pending.get(WORK_TYPE_BG)).isEqualTo(3);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
new file mode 100644
index 0000000..c28292f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 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.job;
+
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+
+import static org.junit.Assert.fail;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.provider.DeviceConfig;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WorkTypeConfigTest {
+    private static final String KEY_MAX_TOTAL = "concurrency_max_total_test";
+    private static final String KEY_MAX_TOP = "concurrency_max_top_test";
+    private static final String KEY_MAX_BG = "concurrency_max_bg_test";
+    private static final String KEY_MIN_TOP = "concurrency_min_top_test";
+    private static final String KEY_MIN_BG = "concurrency_min_bg_test";
+
+    @After
+    public void tearDown() throws Exception {
+        resetConfig();
+    }
+
+    private void resetConfig() {
+        // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually.
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, "", false);
+    }
+
+    private void check(@Nullable DeviceConfig.Properties config,
+            int defaultTotal,
+            @Nullable Pair<Integer, Integer> defaultTopLimits,
+            @Nullable Pair<Integer, Integer> defaultBgLimits,
+            boolean expectedValid, int expectedTotal,
+            @NonNull Pair<Integer, Integer> expectedTopLimits,
+            @NonNull Pair<Integer, Integer> expectedBgLimits) throws Exception {
+        resetConfig();
+        if (config != null) {
+            DeviceConfig.setProperties(config);
+        }
+
+        List<Pair<Integer, Integer>> defaultMin = new ArrayList<>();
+        List<Pair<Integer, Integer>> defaultMax = new ArrayList<>();
+        Integer val;
+        if (defaultTopLimits != null) {
+            if ((val = defaultTopLimits.first) != null) {
+                defaultMin.add(Pair.create(WORK_TYPE_TOP, val));
+            }
+            if ((val = defaultTopLimits.second) != null) {
+                defaultMax.add(Pair.create(WORK_TYPE_TOP, val));
+            }
+        }
+        if (defaultBgLimits != null) {
+            if ((val = defaultBgLimits.first) != null) {
+                defaultMin.add(Pair.create(WORK_TYPE_BG, val));
+            }
+            if ((val = defaultBgLimits.second) != null) {
+                defaultMax.add(Pair.create(WORK_TYPE_BG, val));
+            }
+        }
+
+        final WorkTypeConfig counts;
+        try {
+            counts = new WorkTypeConfig("test",
+                    defaultTotal, defaultMin, defaultMax);
+            if (!expectedValid) {
+                fail("Invalid config successfully created");
+                return;
+            }
+        } catch (IllegalArgumentException e) {
+            if (expectedValid) {
+                throw e;
+            } else {
+                // Success
+                return;
+            }
+        }
+
+        counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
+
+        Assert.assertEquals(expectedTotal, counts.getMaxTotal());
+        Assert.assertEquals((int) expectedTopLimits.first, counts.getMinReserved(WORK_TYPE_TOP));
+        Assert.assertEquals((int) expectedTopLimits.second, counts.getMax(WORK_TYPE_TOP));
+        Assert.assertEquals((int) expectedBgLimits.first, counts.getMinReserved(WORK_TYPE_BG));
+        Assert.assertEquals((int) expectedBgLimits.second, counts.getMax(WORK_TYPE_BG));
+    }
+
+    @Test
+    public void test() throws Exception {
+        // Tests with various combinations.
+        check(null, /*default*/ 5, Pair.create(4, null), Pair.create(0, 1),
+                /*expected*/ true, 5, Pair.create(4, 5), Pair.create(0, 1));
+        check(null, /*default*/ 5, Pair.create(5, null), Pair.create(0, 0),
+                /*expected*/ true, 5, Pair.create(5, 5), Pair.create(0, 1));
+        check(null, /*default*/ 0, Pair.create(5, null), Pair.create(0, 0),
+                /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
+        check(null, /*default*/ -1, null, Pair.create(-1, -1),
+                /*expected*/ false, 1, Pair.create(1, 1), Pair.create(0, 1));
+        check(null, /*default*/ 5, null, Pair.create(5, 5),
+                /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
+        check(null, /*default*/ 6, Pair.create(1, null), Pair.create(6, 5),
+                /*expected*/ false, 6, Pair.create(1, 6), Pair.create(5, 5));
+        check(null, /*default*/ 4, null, Pair.create(6, 5),
+                /*expected*/ false, 4, Pair.create(1, 4), Pair.create(3, 4));
+        check(null, /*default*/ 5, Pair.create(4, null), Pair.create(1, 1),
+                /*expected*/ true, 5, Pair.create(4, 5), Pair.create(1, 1));
+        check(null, /*default*/ 15, null, Pair.create(15, 15),
+                /*expected*/ true, 15, Pair.create(1, 15), Pair.create(14, 15));
+        check(null, /*default*/ 16, null, Pair.create(16, 16),
+                /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
+        check(null, /*default*/ 20, null, Pair.create(20, 20),
+                /*expected*/ false, 16, Pair.create(1, 16), Pair.create(15, 16));
+        check(null, /*default*/ 20, null, Pair.create(16, 16),
+                /*expected*/ true, 16, Pair.create(1, 16), Pair.create(15, 16));
+
+        // Test for overriding with a setting string.
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt(KEY_MAX_TOTAL, 5)
+                        .setInt(KEY_MAX_BG, 4)
+                        .setInt(KEY_MIN_BG, 3)
+                        .build(),
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 5, Pair.create(1, 5), Pair.create(3, 4));
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt(KEY_MAX_TOTAL, 5).build(),
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 5, Pair.create(1, 5), Pair.create(4, 5));
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt(KEY_MAX_BG, 4).build(),
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 9, Pair.create(1, 9), Pair.create(4, 4));
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt(KEY_MIN_BG, 3).build(),
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 9, Pair.create(1, 9), Pair.create(3, 9));
+        check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
+                        .setInt(KEY_MAX_TOTAL, 20)
+                        .setInt(KEY_MAX_BG, 20)
+                        .setInt(KEY_MIN_BG, 8)
+                        .build(),
+                /*default*/ 9, null, Pair.create(9, 9),
+                /*expected*/ true, 16, Pair.create(1, 16), Pair.create(8, 16));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS
deleted file mode 100644
index 28aff18..0000000
--- a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 847766
-nfuller@google.com
-include /core/java/android/app/timedetector/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index 1581d9a..691d174 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -82,6 +82,11 @@
     }
 
     @Override
+    String getRebootEscrowServerBlob() {
+        return makeDirs(mStorageDir, super.getRebootEscrowServerBlob()).getAbsolutePath();
+    }
+
+    @Override
     protected File getSyntheticPasswordDirectoryForUser(int userId) {
         return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser(
                 userId).getAbsolutePath());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index f74e45b..a4ba4c8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
@@ -52,6 +53,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.widget.RebootEscrowListener;
+import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -92,6 +94,7 @@
     private UserManager mUserManager;
     private RebootEscrowManager.Callbacks mCallbacks;
     private IRebootEscrow mRebootEscrow;
+    private ResumeOnRebootServiceConnection mServiceConnection;
     private RebootEscrowKeyStoreManager mKeyStoreManager;
 
     LockSettingsStorageTestable mStorage;
@@ -108,6 +111,7 @@
 
     static class MockInjector extends RebootEscrowManager.Injector {
         private final IRebootEscrow mRebootEscrow;
+        private final ResumeOnRebootServiceConnection mServiceConnection;
         private final RebootEscrowProviderInterface mRebootEscrowProvider;
         private final UserManager mUserManager;
         private final MockableRebootEscrowInjected mInjected;
@@ -116,10 +120,11 @@
         MockInjector(Context context, UserManager userManager,
                 IRebootEscrow rebootEscrow,
                 RebootEscrowKeyStoreManager keyStoreManager,
+                LockSettingsStorageTestable storage,
                 MockableRebootEscrowInjected injected) {
-            super(context);
+            super(context, storage);
             mRebootEscrow = rebootEscrow;
-
+            mServiceConnection = null;
             RebootEscrowProviderHalImpl.Injector halInjector =
                     new RebootEscrowProviderHalImpl.Injector() {
                         @Override
@@ -133,6 +138,22 @@
             mInjected = injected;
         }
 
+        MockInjector(Context context, UserManager userManager,
+                ResumeOnRebootServiceConnection serviceConnection,
+                RebootEscrowKeyStoreManager keyStoreManager,
+                LockSettingsStorageTestable storage,
+                MockableRebootEscrowInjected injected) {
+            super(context, storage);
+            mServiceConnection = serviceConnection;
+            mRebootEscrow = null;
+            RebootEscrowProviderServerBasedImpl.Injector injector =
+                    new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection);
+            mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(storage, injector);
+            mUserManager = userManager;
+            mKeyStoreManager = keyStoreManager;
+            mInjected = injected;
+        }
+
         @Override
         public UserManager getUserManager() {
             return mUserManager;
@@ -165,6 +186,7 @@
         mUserManager = mock(UserManager.class);
         mCallbacks = mock(RebootEscrowManager.Callbacks.class);
         mRebootEscrow = mock(IRebootEscrow.class);
+        mServiceConnection = mock(ResumeOnRebootServiceConnection.class);
         mKeyStoreManager = mock(RebootEscrowKeyStoreManager.class);
         mAesKey = new SecretKeySpec(TEST_AES_KEY, "AES");
 
@@ -186,7 +208,12 @@
         when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
         mInjected = mock(MockableRebootEscrowInjected.class);
         mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
-                mKeyStoreManager, mInjected), mCallbacks, mStorage);
+                mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage);
+    }
+
+    private void setServerBasedRebootEscrowProvider() throws Exception {
+        mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager,
+                mServiceConnection, mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage);
     }
 
     @Test
@@ -202,6 +229,19 @@
     }
 
     @Test
+    public void prepareRebootEscrowServerBased_Success() throws Exception {
+        setServerBasedRebootEscrowProvider();
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+        assertFalse(mStorage.hasRebootEscrowServerBlob());
+    }
+
+    @Test
     public void prepareRebootEscrow_ClearCredentials_Success() throws Exception {
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
         mService.setRebootEscrowListener(mockListener);
@@ -246,6 +286,28 @@
     }
 
     @Test
+    public void armServiceServerBased_Success() throws Exception {
+        setServerBasedRebootEscrowProvider();
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertTrue(mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+
+        assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+        assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+    }
+
+    @Test
     public void armService_HalFailure_NonFatal() throws Exception {
         RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
         mService.setRebootEscrowListener(mockListener);
@@ -346,6 +408,40 @@
     }
 
     @Test
+    public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception {
+        setServerBasedRebootEscrowProvider();
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertTrue(mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());
+
+        when(mServiceConnection.unwrap(any(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        mService.loadRebootEscrowDataIfAvailable();
+        verify(mServiceConnection).unwrap(any(), anyLong());
+        assertTrue(metricsSuccessCaptor.getValue());
+        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
+    }
+
+    @Test
     public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception {
         when(mInjected.getBootCount()).thenReturn(0);
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
new file mode 100644
index 0000000..bc1e025
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 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.locksettings;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RebootEscrowProviderServerBasedImplTests {
+    private SecretKey mKeyStoreEncryptionKey;
+    private RebootEscrowKey mRebootEscrowKey;
+    private ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection mServiceConnection;
+    private LockSettingsStorageTestable mStorage;
+    private RebootEscrowProviderServerBasedImpl mRebootEscrowProvider;
+    private Answer<byte[]> mFakeEncryption;
+
+    private static final byte[] TEST_AES_KEY = new byte[] {
+            0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
+            0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
+            0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
+            0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
+    };
+
+    @Before
+    public void setUp() throws Exception {
+        mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES");
+        mRebootEscrowKey = RebootEscrowKey.generate();
+        mServiceConnection = mock(
+                ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection.class);
+
+        Context context = new ContextWrapper(InstrumentationRegistry.getContext());
+        mStorage = new LockSettingsStorageTestable(context,
+                new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
+        mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mStorage,
+                new RebootEscrowProviderServerBasedImpl.Injector(mServiceConnection));
+
+        mFakeEncryption = invocation -> {
+            byte[] secret = invocation.getArgument(0);
+            for (int i = 0; i < secret.length; i++) {
+                secret[i] = (byte) (secret[i] ^ 0xf);
+            }
+            return secret;
+        };
+    }
+
+    @Test
+    public void getAndClearRebootEscrowKey_loopback_success() throws Exception {
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
+        when(mServiceConnection.unwrap(any(), anyLong())).thenAnswer(mFakeEncryption);
+
+        assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
+        mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+
+        RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey(
+                mKeyStoreEncryptionKey);
+        assertThat(ks.getKeyBytes(), is(mRebootEscrowKey.getKeyBytes()));
+        assertFalse(mStorage.hasRebootEscrowServerBlob());
+    }
+
+    @Test
+    public void getAndClearRebootEscrowKey_WrongDecryptionMethod_failure() throws Exception {
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
+        when(mServiceConnection.unwrap(any(), anyLong())).thenAnswer(
+                invocation -> {
+                    byte[] secret = invocation.getArgument(0);
+                    for (int i = 0; i < secret.length; i++) {
+                        secret[i] = (byte) (secret[i] ^ 0xe);
+                    }
+                    return secret;
+                }
+        );
+
+        assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
+        mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // Expect to get wrong key bytes
+        RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey(
+                mKeyStoreEncryptionKey);
+        assertNotEquals(ks.getKeyBytes(), mRebootEscrowKey.getKeyBytes());
+        assertFalse(mStorage.hasRebootEscrowServerBlob());
+    }
+
+    @Test
+    public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception {
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption);
+        doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong());
+
+        assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport());
+        mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey);
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // Expect to get null key bytes when the server service fails to unwrap the blob.
+        RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey(
+                mKeyStoreEncryptionKey);
+        assertNull(ks);
+        assertFalse(mStorage.hasRebootEscrowServerBlob());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 4db7ce2..58ba907 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -1829,11 +1829,11 @@
     }
 
     /**
-     * Exhaustively test isUidNetworkingBlocked to output the expected results based on external
+     * Exhaustively test checkUidNetworkingBlocked to output the expected results based on external
      * conditions.
      */
     @Test
-    public void testIsUidNetworkingBlocked() {
+    public void testCheckUidNetworkingBlocked() {
         final ArrayList<Pair<Boolean, Integer>> expectedBlockedStates = new ArrayList<>();
 
         // Metered network. Data saver on.
@@ -1877,17 +1877,16 @@
 
     private void verifyNetworkBlockedState(boolean metered, boolean backgroundRestricted,
             ArrayList<Pair<Boolean, Integer>> expectedBlockedStateForRules) {
-        final NetworkPolicyManagerInternal npmi = LocalServices
-                .getService(NetworkPolicyManagerInternal.class);
 
         for (Pair<Boolean, Integer> pair : expectedBlockedStateForRules) {
             final boolean expectedResult = pair.first;
             final int rule = pair.second;
             assertEquals(formatBlockedStateError(UID_A, rule, metered, backgroundRestricted),
-                    expectedResult,
-                    npmi.isUidNetworkingBlocked(UID_A, rule, metered, backgroundRestricted));
+                    expectedResult, mService.checkUidNetworkingBlocked(UID_A, rule,
+                            metered, backgroundRestricted));
             assertFalse(formatBlockedStateError(SYSTEM_UID, rule, metered, backgroundRestricted),
-                    npmi.isUidNetworkingBlocked(SYSTEM_UID, rule, metered, backgroundRestricted));
+                    mService.checkUidNetworkingBlocked(SYSTEM_UID, rule, metered,
+                            backgroundRestricted));
         }
     }
 
@@ -2051,6 +2050,7 @@
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_IFACE);
         final NetworkCapabilities networkCapabilities = new NetworkCapabilities();
+        networkCapabilities.setSSID(TEST_SSID);
         return new NetworkState(info, prop, networkCapabilities, null, null, TEST_SSID);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 63330d5..6e57896 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -515,6 +515,92 @@
     }
 
     @Test
+    public void testGetConversationReturnsCustomizedConversation() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+
+        listenerService.onNotificationPosted(mStatusBarNotification);
+        shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNotNull();
+
+        listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
+                mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
+
+        ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        assertThat(result).isNotNull();
+        assertThat(result.hasBirthdayToday()).isFalse();
+        assertThat(result.getStatuses()).isEmpty();
+    }
+
+    @Test
+    public void testGetConversation() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+            TEST_SHORTCUT_ID)).isNull();
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID)).isNotNull();
+        assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID + "1")).isNull();
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationPosted(mStatusBarNotification);
+        ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+        mDataManager.addOrUpdateStatus(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, cs);
+
+        ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+        assertThat(result).isNotNull();
+        assertEquals(shortcut.getId(), result.getShortcutInfo().getId());
+        assertEquals(1, result.getShortcutInfo().getPersons().length);
+        assertEquals(CONTACT_URI, result.getShortcutInfo().getPersons()[0].getUri());
+        assertEquals(mParentNotificationChannel.getId(),
+                result.getParentNotificationChannel().getId());
+        assertEquals(mStatusBarNotification.getPostTime(), result.getLastEventTimestamp());
+        assertTrue(result.hasActiveNotifications());
+        assertFalse(result.hasBirthdayToday());
+        assertThat(result.getStatuses()).containsExactly(cs);
+    }
+
+    @Test
+    public void testGetConversationGetsPersonsData() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+        mDataManager.addOrUpdateConversationInfo(shortcut);
+
+        NotificationListenerService listenerService =
+                mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+        listenerService.onNotificationPosted(mStatusBarNotification);
+
+        ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+                TEST_SHORTCUT_ID);
+
+        verify(mShortcutServiceInternal).getShortcuts(
+                anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
+                mQueryFlagsCaptor.capture(), anyInt(), anyInt(), anyInt());
+        Integer queryFlags = mQueryFlagsCaptor.getValue();
+        assertThat(hasFlag(queryFlags, ShortcutQuery.FLAG_GET_PERSONS_DATA)).isTrue();
+    }
+
+    @Test
     public void testNotificationChannelCreated() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
         mDataManager.onUserUnlocked(USER_ID_SECONDARY);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java
new file mode 100644
index 0000000..764c504
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/BundleUtilsTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 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.pm;
+
+import static com.android.server.devicepolicy.DpmTestUtils.assertRestrictions;
+import static com.android.server.devicepolicy.DpmTestUtils.newRestrictions;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.BundleUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest com.android.server.pm.BundleUtilsTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BundleUtilsTest {
+
+    @Test
+    public void testIsEmpty() {
+        assertThat(BundleUtils.isEmpty(null)).isTrue();
+        assertThat(BundleUtils.isEmpty(new Bundle())).isTrue();
+        assertThat(BundleUtils.isEmpty(newRestrictions("a"))).isFalse();
+    }
+
+    @Test
+    public void testClone() {
+        Bundle in = new Bundle();
+        Bundle out = BundleUtils.clone(in);
+        assertThat(in).isNotSameInstanceAs(out);
+        assertRestrictions(out, new Bundle());
+
+        out = BundleUtils.clone(null);
+        assertThat(out).isNotNull();
+        out.putBoolean("a", true); // Should not be Bundle.EMPTY.
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
index 9ba0967..7b9a00d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
@@ -71,12 +71,12 @@
     @Before
     public void setUp() {
         mIncrementalStates = new IncrementalStates();
-        assertFalse(mIncrementalStates.isStartable());
+        assertFalse(mIncrementalStates.getIncrementalStatesInfo().isStartable());
         mIncrementalStates.setCallback(mCallback);
         mIncrementalStates.onCommit(true);
         // Test that package is now startable and loading
-        assertTrue(mIncrementalStates.isStartable());
-        assertTrue(mIncrementalStates.isLoading());
+        assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable());
+        assertTrue(mIncrementalStates.getIncrementalStatesInfo().isLoading());
         mUnstartableCalled.close();
         mFullyLoadedCalled.close();
     }
@@ -90,7 +90,7 @@
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
         // Test that package is still startable
         assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
+        assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable());
         assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN, mUnstartableReason.get());
     }
 
@@ -104,7 +104,7 @@
                 IStorageHealthListener.HEALTH_STATUS_READS_PENDING);
         // Test that package is still startable
         assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
+        assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable());
     }
 
     /**
@@ -116,7 +116,7 @@
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_STORAGE);
         // Test that package is still startable
         assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
+        assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable());
         assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN,
                 mUnstartableReason.get());
     }
@@ -130,7 +130,7 @@
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_TRANSPORT);
         // Test that package is still startable
         assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
+        assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable());
         assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN,
                 mUnstartableReason.get());
     }
@@ -145,12 +145,12 @@
         mIncrementalStates.setProgress(1.0f);
         // Test that package is now fully loaded
         assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isLoading());
+        assertFalse(mIncrementalStates.getIncrementalStatesInfo().isLoading());
         mIncrementalStates.onStorageHealthStatusChanged(
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
         // Test that package is still startable
         assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
+        assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable());
     }
 
     /**
@@ -159,6 +159,6 @@
     @Test
     public void testStartableTransition_AppCrashOrAnr() {
         mIncrementalStates.onCrashOrAnr();
-        assertTrue(mIncrementalStates.isStartable());
+        assertTrue(mIncrementalStates.getIncrementalStatesInfo().isStartable());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
index 90edaef..709b009 100644
--- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java
@@ -37,9 +37,10 @@
     private KeySetManagerService mKsms;
 
     public PackageSetting generateFakePackageSetting(String name) {
-        return new PackageSetting(name, name, new File(mContext.getCacheDir(), "fakeCodePath"),
-                "", "", "", "", 1, 0, 0, 0 /*sharedUserId*/, null /*usesStaticLibraries*/,
-                null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/);
+        return new PackageSettingBuilder()
+                .setName(name)
+                .setCodePath(new File(mContext.getCacheDir(), "fakeCodePath").getAbsolutePath())
+                .build();
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index 4ce1bbc..558fb30 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -107,10 +107,15 @@
 
         // Create a real (non-null) PackageSetting and confirm that the removed
         // users are copied properly
-        setting = new PackageSetting("name", "realName", new File("codePath"),
-                "legacyNativeLibraryPathString", "primaryCpuAbiString", "secondaryCpuAbiString",
-                "cpuAbiOverrideString", 0, 0, 0, 0,
-                null, null, null);
+        setting = new PackageSettingBuilder()
+                .setName("name")
+                .setRealName("realName")
+                .setCodePath("codePath")
+                .setLegacyNativeLibraryPathString("legacyNativeLibraryPathString")
+                .setPrimaryCpuAbiString("primaryCpuAbiString")
+                .setSecondaryCpuAbiString("secondaryCpuAbiString")
+                .setCpuAbiOverrideString("cpuAbiOverrideString")
+                .build();
         pri.populateUsers(new int[] {
                 1, 2, 3, 4, 5
         }, setting);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 333ec929..59458e8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -33,10 +33,10 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.app.PropertyInvalidatedCache;
-import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
@@ -59,6 +59,7 @@
 
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.server.LocalServices;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
@@ -80,6 +81,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.UUID;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -94,10 +96,14 @@
     RuntimePermissionsPersistence mRuntimePermissionsPersistence;
     @Mock
     LegacyPermissionDataProvider mPermissionDataProvider;
+    @Mock
+    DomainVerificationManagerInternal mDomainVerificationManager;
 
     @Before
     public void initializeMocks() {
         MockitoAnnotations.initMocks(this);
+        when(mDomainVerificationManager.generateNewId())
+                .thenAnswer(invocation -> UUID.randomUUID());
     }
 
     @Before
@@ -112,10 +118,7 @@
             throws ReflectiveOperationException, IllegalAccessException {
         /* write out files and read */
         writeOldFiles();
-        final Context context = InstrumentationRegistry.getContext();
-        final Object lock = new Object();
-        Settings settings = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        Settings settings = makeSettings();
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         verifyKeySetMetaData(settings);
     }
@@ -126,10 +129,7 @@
             throws ReflectiveOperationException, IllegalAccessException {
         // write out files and read
         writeOldFiles();
-        final Context context = InstrumentationRegistry.getContext();
-        final Object lock = new Object();
-        Settings settings = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        Settings settings = makeSettings();
         assertThat(settings.readLPw(createFakeUsers()), is(true));
 
         // write out, read back in and verify the same
@@ -142,10 +142,7 @@
     public void testSettingsReadOld() {
         // Write delegateshellthe package files and make sure they're parsed properly the first time
         writeOldFiles();
-        final Context context = InstrumentationRegistry.getContext();
-        final Object lock = new Object();
-        Settings settings = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        Settings settings = makeSettings();
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
         assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
@@ -164,16 +161,12 @@
     public void testNewPackageRestrictionsFile() throws ReflectiveOperationException {
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
-        final Context context = InstrumentationRegistry.getContext();
-        final Object lock = new Object();
-        Settings settings = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        Settings settings = makeSettings();
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         settings.writeLPr();
 
         // Create Settings again to make it read from the new files
-        settings = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        settings = makeSettings();
         assertThat(settings.readLPw(createFakeUsers()), is(true));
 
         PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
@@ -200,10 +193,7 @@
     @Test
     public void testReadPackageRestrictions_noSuspendingPackage() {
         writePackageRestrictions_noSuspendingPackageXml(0);
-        final Object lock = new Object();
-        final Context context = InstrumentationRegistry.getTargetContext();
-        final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
-                lock);
+        Settings settingsUnderTest = makeSettings();
         final WatchableTester watcher =
                 new WatchableTester(settingsUnderTest, "noSuspendingPackage");
         watcher.register();
@@ -244,10 +234,7 @@
     @Test
     public void testReadPackageRestrictions_noSuspendParamsMap() {
         writePackageRestrictions_noSuspendParamsMapXml(0);
-        final Object lock = new Object();
-        final Context context = InstrumentationRegistry.getTargetContext();
-        final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
-                lock);
+        final Settings settingsUnderTest = makeSettings();
         final WatchableTester watcher =
                 new WatchableTester(settingsUnderTest, "noSuspendParamsMap");
         watcher.register();
@@ -281,9 +268,7 @@
 
     @Test
     public void testReadWritePackageRestrictions_suspendInfo() {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
-                new Object());
+        final Settings settingsUnderTest = makeSettings();
         final WatchableTester watcher = new WatchableTester(settingsUnderTest, "suspendInfo");
         watcher.register();
         final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
@@ -397,9 +382,7 @@
 
     @Test
     public void testReadWritePackageRestrictions_distractionFlags() {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        final Settings settingsUnderTest = new Settings(context.getFilesDir(), null, null,
-                new Object());
+        final Settings settingsUnderTest = makeSettings();
         final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
         final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
         final PackageSetting ps3 = createPackageSetting(PACKAGE_NAME_3);
@@ -440,10 +423,7 @@
 
     @Test
     public void testWriteReadUsesStaticLibraries() {
-        final Context context = InstrumentationRegistry.getTargetContext();
-        final Object lock = new Object();
-        final Settings settingsUnderTest = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        final Settings settingsUnderTest = makeSettings();
         final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
         ps1.appId = Process.FIRST_APPLICATION_UID;
         ps1.pkg = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
@@ -516,10 +496,7 @@
     public void testEnableDisable() {
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
-        final Context context = InstrumentationRegistry.getContext();
-        final Object lock = new Object();
-        Settings settings = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        Settings settings = makeSettings();
         final WatchableTester watcher = new WatchableTester(settings, "testEnableDisable");
         watcher.register();
         assertThat(settings.readLPw(createFakeUsers()), is(true));
@@ -585,7 +562,8 @@
                 0,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
         verifySettingCopy(origPkgSetting01, testPkgSetting01);
     }
@@ -606,7 +584,8 @@
                 0,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         final PackageSetting testPkgSetting01 = new PackageSetting(
                 PACKAGE_NAME /*pkgName*/,
                 REAL_PACKAGE_NAME /*realPkgName*/,
@@ -621,7 +600,8 @@
                 0,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         testPkgSetting01.copyFrom(origPkgSetting01);
         verifySettingCopy(origPkgSetting01, testPkgSetting01);
     }
@@ -648,7 +628,8 @@
                 UserManagerService.getInstance(),
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
         assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
         assertThat(testPkgSetting01.pkgFlags, is(0));
@@ -681,7 +662,8 @@
                 UserManagerService.getInstance(),
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         assertThat(testPkgSetting01.primaryCpuAbiString, is("arm64-v8a"));
         assertThat(testPkgSetting01.secondaryCpuAbiString, is("armeabi"));
         assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
@@ -698,12 +680,9 @@
     /** Update package; changing shared user throws exception */
     @Test
     public void testUpdatePackageSetting03() {
-        final Context context = InstrumentationRegistry.getContext();
-        final Object lock = new Object();
-        final Settings testSettings01 = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        Settings settings = makeSettings();
         final SharedUserSetting testUserSetting01 = createSharedUserSetting(
-                testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+                settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
         final PackageSetting testPkgSetting01 =
                 createPackageSetting(0 /*sharedUserId*/, 0 /*pkgFlags*/);
         try {
@@ -720,7 +699,8 @@
                     UserManagerService.getInstance(),
                     null /*usesStaticLibraries*/,
                     null /*usesStaticLibrariesVersions*/,
-                    null /*mimeGroups*/);
+                    null /*mimeGroups*/,
+                    UUID.randomUUID());
             fail("Expected a PackageManagerException");
         } catch (PackageManagerException expected) {
         }
@@ -752,7 +732,8 @@
                 UserManagerService.getInstance(),
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
         assertThat(testPkgSetting01.pkgFlags, is(ApplicationInfo.FLAG_SYSTEM));
@@ -790,7 +771,8 @@
                 UserManagerService.getInstance(),
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         assertThat(testPkgSetting01.appId, is(0));
         assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -808,12 +790,9 @@
     /** Create PackageSetting for a shared user */
     @Test
     public void testCreateNewSetting03() {
-        final Context context = InstrumentationRegistry.getContext();
-        final Object lock = new Object();
-        final Settings testSettings01 = new Settings(context.getFilesDir(),
-                mRuntimePermissionsPersistence, mPermissionDataProvider, lock);
+        Settings settings = makeSettings();
         final SharedUserSetting testUserSetting01 = createSharedUserSetting(
-                testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
+                settings, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
         final PackageSetting testPkgSetting01 = Settings.createNewSetting(
                 PACKAGE_NAME,
                 null /*originalPkg*/,
@@ -834,7 +813,8 @@
                 UserManagerService.getInstance(),
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         assertThat(testPkgSetting01.appId, is(10064));
         assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));
         assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -875,7 +855,8 @@
                 UserManagerService.getInstance(),
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
         assertThat(testPkgSetting01.appId, is(10064));
         assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
         assertThat(testPkgSetting01.name, is(PACKAGE_NAME));
@@ -934,6 +915,7 @@
         assertThat(origPkgSetting.getPathString(), is(testPkgSetting.getPathString()));
         assertSame(origPkgSetting.cpuAbiOverrideString, testPkgSetting.cpuAbiOverrideString);
         assertThat(origPkgSetting.cpuAbiOverrideString, is(testPkgSetting.cpuAbiOverrideString));
+        assertThat(origPkgSetting.getDomainSetId(), is(testPkgSetting.getDomainSetId()));
         assertThat(origPkgSetting.firstInstallTime, is(testPkgSetting.firstInstallTime));
         assertSame(origPkgSetting.installSource, testPkgSetting.installSource);
         assertThat(origPkgSetting.installPermissionsFixed,
@@ -976,8 +958,6 @@
         assertNotSame(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
         // No equals() method for SparseArray object
         // assertThat(origPkgSetting.getUserState(), is(testPkgSetting.getUserState()));
-        assertSame(origPkgSetting.verificationInfo, testPkgSetting.verificationInfo);
-        assertThat(origPkgSetting.verificationInfo, is(testPkgSetting.verificationInfo));
         assertThat(origPkgSetting.versionCode, is(testPkgSetting.versionCode));
         assertSame(origPkgSetting.volumeUuid, testPkgSetting.volumeUuid);
         assertThat(origPkgSetting.volumeUuid, is(testPkgSetting.volumeUuid));
@@ -1006,7 +986,8 @@
                 sharedUserId,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
     }
 
     private PackageSetting createPackageSetting(String packageName) {
@@ -1024,7 +1005,8 @@
                 0,
                 null /*usesStaticLibraries*/,
                 null /*usesStaticLibrariesVersions*/,
-                null /*mimeGroups*/);
+                null /*mimeGroups*/,
+                UUID.randomUUID());
     }
 
     private @NonNull List<UserInfo> createFakeUsers() {
@@ -1212,6 +1194,12 @@
         deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
     }
 
+    private Settings makeSettings() {
+        return new Settings(InstrumentationRegistry.getContext().getFilesDir(),
+                mRuntimePermissionsPersistence, mPermissionDataProvider,
+                mDomainVerificationManager, new Object());
+    }
+
     private void verifyKeySetMetaData(Settings settings)
             throws ReflectiveOperationException, IllegalAccessException {
         ArrayMap<String, PackageSetting> packages =
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 90c2982..e6a238a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -546,12 +546,17 @@
     }
 
     private static PackageSetting mockPkgSetting(AndroidPackage pkg) {
-        return new PackageSetting(pkg.getPackageName(), pkg.getRealPackage(),
-                new File(pkg.getPath()), null, pkg.getPrimaryCpuAbi(), pkg.getSecondaryCpuAbi(),
-                null, pkg.getVersionCode(),
-                PackageInfoUtils.appInfoFlags(pkg, null),
-                PackageInfoUtils.appInfoPrivateFlags(pkg, null),
-                pkg.getSharedUserLabel(), null, null, null);
+        return new PackageSettingBuilder()
+                .setName(pkg.getPackageName())
+                .setRealName(pkg.getRealPackage())
+                .setCodePath(pkg.getPath())
+                .setPrimaryCpuAbiString(pkg.getPrimaryCpuAbi())
+                .setSecondaryCpuAbiString(pkg.getSecondaryCpuAbi())
+                .setPVersionCode(pkg.getLongVersionCode())
+                .setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
+                .setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
+                .setSharedUserId(pkg.getSharedUserLabel())
+                .build();
     }
 
     // NOTE: The equality assertions below are based on code autogenerated by IntelliJ.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 84551c5..f75751b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -22,10 +22,10 @@
 import android.util.SparseArray;
 
 import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 
 import java.io.File;
 import java.util.Map;
+import java.util.UUID;
 
 public class PackageSettingBuilder {
     private String mName;
@@ -48,6 +48,7 @@
     private long[] mUsesStaticLibrariesVersions;
     private Map<String, ArraySet<String>> mMimeGroups;
     private PackageParser.SigningDetails mSigningDetails;
+    private UUID mDomainSetId = UUID.randomUUID();
 
     public PackageSettingBuilder setPackage(AndroidPackage pkg) {
         this.mPkg = pkg;
@@ -163,12 +164,17 @@
         return this;
     }
 
+    public PackageSettingBuilder setDomainSetId(UUID domainSetId) {
+        mDomainSetId = domainSetId;
+        return this;
+    }
+
     public PackageSetting build() {
         final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
                 new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
                 mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
                 mPrivateFlags, mSharedUserId, mUsesStaticLibraries, mUsesStaticLibrariesVersions,
-                mMimeGroups);
+                mMimeGroups, mDomainSetId);
         packageSetting.signatures = mSigningDetails != null
                 ? new PackageSignatures(mSigningDetails)
                 : new PackageSignatures();
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
index 90658055..27f3eec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSignaturesTest.java
@@ -465,10 +465,11 @@
     private static PackageSetting createPackageSetting() {
         // Generic PackageSetting object with values from a test app installed on a device to be
         // used to test the methods under the PackageSignatures signatures data member.
-        File appPath = new File("/data/app/app");
-        PackageSetting result = new PackageSetting("test.app", null, appPath,
-                "/data/app/app", null, null, null, 1, 940097092, 0, 0 /*userId*/, null, null,
-                null /*mimeGroups*/);
-        return result;
+        return new PackageSettingBuilder()
+                .setName("test.app")
+                .setCodePath("/data/app/app")
+                .setPVersionCode(1)
+                .setPkgFlags(940097092)
+                .build();
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
index 1cfbad9..938e4cc 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java
@@ -53,18 +53,10 @@
         assertThat(testUserState.equals(oldUserState), is(true));
 
         oldUserState = new PackageUserState();
-        oldUserState.appLinkGeneration = 6;
-        assertThat(testUserState.equals(oldUserState), is(false));
-
-        oldUserState = new PackageUserState();
         oldUserState.ceDataInode = 4000L;
         assertThat(testUserState.equals(oldUserState), is(false));
 
         oldUserState = new PackageUserState();
-        oldUserState.domainVerificationStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-        assertThat(testUserState.equals(oldUserState), is(false));
-
-        oldUserState = new PackageUserState();
         oldUserState.enabled = COMPONENT_ENABLED_STATE_ENABLED;
         assertThat(testUserState.equals(oldUserState), is(false));
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index d8c3979..b5add84 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -50,6 +50,7 @@
 import android.util.Pair;
 
 import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.PackageImpl;
@@ -91,6 +92,14 @@
         when(mMockInjector.getAbiHelper()).thenReturn(mMockPackageAbiHelper);
         when(mMockInjector.getUserManagerInternal()).thenReturn(mMockUserManager);
         when(mMockInjector.getCompatibility()).thenReturn(mMockCompatibility);
+
+        DomainVerificationManagerInternal domainVerificationManager =
+                mock(DomainVerificationManagerInternal.class);
+        when(domainVerificationManager.generateNewId())
+                .thenAnswer(invocation -> UUID.randomUUID());
+
+        when(mMockInjector.getDomainVerificationManagerInternal())
+                .thenReturn(domainVerificationManager);
     }
 
     @Before
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
index e6c3d7c..62b6c71 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java
@@ -18,6 +18,7 @@
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.array;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertContains;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertHaveIds;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertSuccess;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
@@ -25,6 +26,8 @@
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resultContains;
 
 import android.content.ComponentName;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -47,6 +50,9 @@
  */
 @SmallTest
 public class ShortcutManagerTest7 extends BaseShortcutManagerTest {
+
+    private static final int CACHE_OWNER = LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
+
     private List<String> callShellCommand(String... args) throws IOException, RemoteException {
 
         // For reset to work, the current time needs to be incrementing.
@@ -323,6 +329,72 @@
         });
     }
 
+    public void testGetShortcuts() throws Exception {
+
+        mRunningUsers.put(USER_10, true);
+
+        // Add two manifests and two dynamics.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_2);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertTrue(mManager.addDynamicShortcuts(list(
+                    makeLongLivedShortcut("s1"), makeShortcut("s2"))));
+        });
+        runWithCaller(LAUNCHER_1, USER_10, () -> {
+            mInjectCheckAccessShortcutsPermission = true;
+            mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_10,
+                    CACHE_OWNER);
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2", "s2"), HANDLE_USER_10);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1", "ms2", "s1", "s2")
+                    .areAllEnabled()
+
+                    .selectPinned()
+                    .haveIds("ms2", "s2");
+        });
+
+
+        mRunningUsers.put(USER_10, true);
+        mUnlockedUsers.put(USER_10, true);
+
+        mInjectedCallingUid = Process.SHELL_UID;
+
+        assertHaveIds(callShellCommand("get-shortcuts", "--user", "10", "--flags",
+                Integer.toString(ShortcutManager.FLAG_MATCH_CACHED), CALLING_PACKAGE_1),
+                "s1");
+
+        assertHaveIds(callShellCommand("get-shortcuts", "--user", "10", "--flags",
+                Integer.toString(ShortcutManager.FLAG_MATCH_DYNAMIC), CALLING_PACKAGE_1),
+                "s1", "s2");
+
+        assertHaveIds(callShellCommand("get-shortcuts", "--user", "10", "--flags",
+                Integer.toString(ShortcutManager.FLAG_MATCH_MANIFEST), CALLING_PACKAGE_1),
+                "ms1", "ms2");
+
+        assertHaveIds(callShellCommand("get-shortcuts", "--user", "10", "--flags",
+                Integer.toString(ShortcutManager.FLAG_MATCH_PINNED), CALLING_PACKAGE_1),
+                "ms2", "s2");
+
+        assertHaveIds(callShellCommand("get-shortcuts", "--user", "10", "--flags",
+                Integer.toString(ShortcutManager.FLAG_MATCH_DYNAMIC
+                        | ShortcutManager.FLAG_MATCH_PINNED), CALLING_PACKAGE_1),
+                "ms2", "s1", "s2");
+
+        assertHaveIds(callShellCommand("get-shortcuts", "--user", "10", "--flags",
+                Integer.toString(ShortcutManager.FLAG_MATCH_MANIFEST
+                        | ShortcutManager.FLAG_MATCH_CACHED), CALLING_PACKAGE_1),
+                "ms1", "ms2", "s1");
+
+    }
+
     public void testDumpsysArgs() {
         checkDumpsysArgs(null, true, false, false);
         checkDumpsysArgs(array("-u"), true, true, false);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index ee30f68..cd98d44 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -72,11 +72,18 @@
     @Test
     public void testUserTypeBuilder_createUserType() {
         final Bundle restrictions = makeRestrictionsBundle("r1", "r2");
+        final Bundle systemSettings = makeSettingsBundle("s1", "s2");
+        final Bundle secureSettings = makeSettingsBundle("secure_s1", "secure_s2");
+        final List<DefaultCrossProfileIntentFilter> filters = List.of(
+                new DefaultCrossProfileIntentFilter.Builder(
+                DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+                /* flags= */0,
+                /* letsPersonalDataIntoProfile= */false).build());
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
                 .setEnabled(true)
                 .setMaxAllowed(21)
-                .setBaseType(FLAG_FULL)
+                .setBaseType(FLAG_PROFILE)
                 .setDefaultUserInfoPropertyFlags(FLAG_EPHEMERAL)
                 .setBadgeLabels(23, 24, 25)
                 .setBadgeColors(26, 27)
@@ -86,20 +93,45 @@
                 .setLabel(31)
                 .setMaxAllowedPerParent(32)
                 .setDefaultRestrictions(restrictions)
+                .setDefaultSystemSettings(systemSettings)
+                .setDefaultSecureSettings(secureSettings)
+                .setDefaultCrossProfileIntentFilters(filters)
                 .createUserTypeDetails();
 
         assertEquals("a.name", type.getName());
         assertTrue(type.isEnabled());
         assertEquals(21, type.getMaxAllowed());
-        assertEquals(FLAG_FULL | FLAG_EPHEMERAL, type.getDefaultUserInfoFlags());
+        assertEquals(FLAG_PROFILE | FLAG_EPHEMERAL, type.getDefaultUserInfoFlags());
         assertEquals(28, type.getIconBadge());
         assertEquals(29, type.getBadgePlain());
         assertEquals(30, type.getBadgeNoBackground());
         assertEquals(31, type.getLabel());
         assertEquals(32, type.getMaxAllowedPerParent());
+
         assertTrue(UserRestrictionsUtils.areEqual(restrictions, type.getDefaultRestrictions()));
         assertNotSame(restrictions, type.getDefaultRestrictions());
 
+        assertNotSame(systemSettings, type.getDefaultSystemSettings());
+        assertEquals(systemSettings.size(), type.getDefaultSystemSettings().size());
+        for (String key : systemSettings.keySet()) {
+            assertEquals(
+                    systemSettings.getString(key),
+                    type.getDefaultSystemSettings().getString(key));
+        }
+
+        assertNotSame(secureSettings, type.getDefaultSecureSettings());
+        assertEquals(secureSettings.size(), type.getDefaultSecureSettings().size());
+        for (String key : secureSettings.keySet()) {
+            assertEquals(
+                    secureSettings.getString(key),
+                    type.getDefaultSecureSettings().getString(key));
+        }
+
+        assertNotSame(filters, type.getDefaultCrossProfileIntentFilters());
+        assertEquals(filters.size(), type.getDefaultCrossProfileIntentFilters().size());
+        for (int i = 0; i < filters.size(); i++) {
+            assertEquals(filters.get(i), type.getDefaultCrossProfileIntentFilters().get(i));
+        }
 
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
@@ -135,6 +167,9 @@
         assertEquals(Resources.ID_NULL, type.getBadgeColor(0));
         assertEquals(Resources.ID_NULL, type.getLabel());
         assertTrue(type.getDefaultRestrictions().isEmpty());
+        assertTrue(type.getDefaultSystemSettings().isEmpty());
+        assertTrue(type.getDefaultSecureSettings().isEmpty());
+        assertTrue(type.getDefaultCrossProfileIntentFilters().isEmpty());
 
         assertFalse(type.hasBadge());
     }
@@ -416,4 +451,13 @@
         }
         return bundle;
     }
+
+    /** Creates a Bundle of the given settings keys and puts true for the value. */
+    private static Bundle makeSettingsBundle(String ... settings) {
+        final Bundle bundle = new Bundle();
+        for (String setting : settings) {
+            bundle.putBoolean(setting, true);
+        }
+        return bundle;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
index dc181a9..ddf0cd0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
@@ -48,23 +48,6 @@
         assertSame(in, UserRestrictionsUtils.nonNull(in));
     }
 
-    public void testIsEmpty() {
-        assertTrue(UserRestrictionsUtils.isEmpty(null));
-        assertTrue(UserRestrictionsUtils.isEmpty(new Bundle()));
-        assertFalse(UserRestrictionsUtils.isEmpty(newRestrictions("a")));
-    }
-
-    public void testClone() {
-        Bundle in = new Bundle();
-        Bundle out = UserRestrictionsUtils.clone(in);
-        assertNotSame(in, out);
-        assertRestrictions(out, new Bundle());
-
-        out = UserRestrictionsUtils.clone(null);
-        assertNotNull(out);
-        out.putBoolean("a", true); // Should not be Bundle.EMPTY.
-    }
-
     public void testMerge() {
         Bundle a = newRestrictions("a", "d");
         Bundle b = newRestrictions("b", "d", "e");
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index eaf62cb..0dcd608 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -23,12 +23,11 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.dex.DexMetadataHelper;
+import android.content.pm.parsing.ApkLite;
 import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.FileUtils;
@@ -38,6 +37,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.servicestests.R;
+import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
@@ -207,28 +207,35 @@
     }
 
     @Test
-    public void testPackageSizeWithDmFile()
-            throws IOException, PackageParserException {
+    public void testPackageSizeWithDmFile() throws IOException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
-        File dm = createDexMetadataFile("install_split_base.apk");
-        ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+        final File dm = createDexMetadataFile("install_split_base.apk");
+        final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
                 ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */);
         if (result.isError()) {
             throw new IllegalStateException(result.getErrorMessage(), result.getException());
         }
-        PackageParser.PackageLite pkg = result.getResult();
+        final PackageLite pkg = result.getResult();
         Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkg));
     }
 
     // This simulates the 'adb shell pm install' flow.
     @Test
-    public void testPackageSizeWithPartialPackageLite() throws IOException, PackageParserException {
-        File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base);
-        File dm = createDexMetadataFile("install_split_base.apk");
+    public void testPackageSizeWithPartialPackageLite() throws IOException,
+            PackageManagerException {
+        final File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base);
+        final File dm = createDexMetadataFile("install_split_base.apk");
         try (FileInputStream is = new FileInputStream(base)) {
-            ApkLite baseApk = PackageParser.parseApkLite(is.getFD(), base.getAbsolutePath(), 0);
-            PackageLite pkgLite = new PackageLite(null, baseApk.codePath, baseApk, null, null, null,
-                    null, null, null);
+            final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(
+                    ParseTypeImpl.forDefaultParsing().reset(), is.getFD(),
+                    base.getAbsolutePath(), /* flags */ 0);
+            if (result.isError()) {
+                throw new PackageManagerException(result.getErrorCode(),
+                        result.getErrorMessage(), result.getException());
+            }
+            final ApkLite baseApk = result.getResult();
+            final PackageLite pkgLite = new PackageLite(null, baseApk.getPath(), baseApk, null,
+                    null, null, null, null, null);
             Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite));
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index bb223b3..fb13d87 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -55,6 +55,7 @@
 
 import java.io.File;
 import java.io.InputStream;
+import java.util.Collections;
 import java.util.function.Function;
 
 /**
@@ -523,7 +524,7 @@
         int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
 
         ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile,
-                flags, false /*collectCertificates*/);
+                flags, Collections.emptyList(), false /*collectCertificates*/);
         if (result.isError()) {
             throw new IllegalStateException(result.getErrorMessage(), result.getException());
         }
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
index d8910de..4dc9a90 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
@@ -19,12 +19,10 @@
 import android.annotation.RawRes
 import android.content.Context
 import android.content.pm.parsing.ParsingPackage
-import android.content.pm.parsing.ParsingPackageImpl
 import android.content.pm.parsing.ParsingPackageUtils
 import android.content.pm.parsing.result.ParseInput
 import android.content.pm.parsing.result.ParseInput.DeferredError
 import android.content.pm.parsing.result.ParseResult
-import android.content.res.TypedArray
 import android.os.Build
 import androidx.test.InstrumentationRegistry
 import com.android.frameworks.servicestests.R
@@ -130,7 +128,7 @@
                 input.copyTo(output)
             }
         }
-        return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/,
+        return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(),
                 false /*collectCertificates*/)
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 0bea584..4f36c8a 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -40,6 +40,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.server.LocalServices;
+import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
 
 import org.junit.After;
@@ -61,7 +62,8 @@
  * Run with <code>atest DeviceStateProviderImplTest</code>.
  */
 public final class DeviceStateProviderImplTest {
-    private final ArgumentCaptor<int[]> mIntArrayCaptor = ArgumentCaptor.forClass(int[].class);
+    private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
+            DeviceState[].class);
     private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
 
     private Context mContext;
@@ -120,11 +122,12 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
-        assertArrayEquals(new int[] { DEFAULT_DEVICE_STATE }, mIntArrayCaptor.getValue());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        assertArrayEquals(new DeviceState[]{DEFAULT_DEVICE_STATE},
+                mDeviceStateArrayCaptor.getValue());
 
         verify(listener).onStateChanged(mIntegerCaptor.capture());
-        assertEquals(DEFAULT_DEVICE_STATE, mIntegerCaptor.getValue().intValue());
+        assertEquals(DEFAULT_DEVICE_STATE.getIdentifier(), mIntegerCaptor.getValue().intValue());
     }
 
     @Test
@@ -146,8 +149,10 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
-        assertArrayEquals(new int[] { 1, 2 }, mIntArrayCaptor.getValue());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, null),
+                new DeviceState(2, null) };
+        assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
 
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(1, mIntegerCaptor.getValue().intValue());
@@ -166,6 +171,7 @@
                 + "    </device-state>\n"
                 + "    <device-state>\n"
                 + "        <identifier>2</identifier>\n"
+                + "        <name>CLOSED</name>\n"
                 + "        <conditions>\n"
                 + "            <lid-switch>\n"
                 + "                <open>false</open>\n"
@@ -180,8 +186,10 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
-        assertArrayEquals(new int[] { 1, 2 }, mIntArrayCaptor.getValue());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, null),
+                new DeviceState(2, "CLOSED") };
+        assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
 
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(2, mIntegerCaptor.getValue().intValue());
@@ -189,8 +197,7 @@
         Mockito.clearInvocations(listener);
 
         provider.notifyLidSwitchChanged(0, true /* lidOpen */);
-
-        verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(1, mIntegerCaptor.getValue().intValue());
     }
@@ -203,6 +210,7 @@
         String configString = "<device-state-config>\n"
                 + "    <device-state>\n"
                 + "        <identifier>1</identifier>\n"
+                + "        <name>CLOSED</name>\n"
                 + "        <conditions>\n"
                 + "            <sensor>\n"
                 + "                <type>" + sensor.getStringType() + "</type>\n"
@@ -215,6 +223,7 @@
                 + "    </device-state>\n"
                 + "    <device-state>\n"
                 + "        <identifier>2</identifier>\n"
+                + "        <name>HALF_OPENED</name>\n"
                 + "        <conditions>\n"
                 + "            <sensor>\n"
                 + "                <type>" + sensor.getStringType() + "</type>\n"
@@ -228,6 +237,7 @@
                 + "    </device-state>\n"
                 + "    <device-state>\n"
                 + "        <identifier>3</identifier>\n"
+                + "        <name>OPENED</name>\n"
                 + "        <conditions>\n"
                 + "            <sensor>\n"
                 + "                <type>" + sensor.getStringType() + "</type>\n"
@@ -246,8 +256,10 @@
         DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
         provider.setListener(listener);
 
-        verify(listener).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
-        assertArrayEquals(new int[] { 1, 2, 3 }, mIntArrayCaptor.getValue());
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+        assertArrayEquals(
+                new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"),
+                        new DeviceState(3, "OPENED") }, mDeviceStateArrayCaptor.getValue());
 
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(1, mIntegerCaptor.getValue().intValue());
@@ -256,11 +268,11 @@
 
         SensorEvent event0 = mock(SensorEvent.class);
         event0.sensor = sensor;
-        FieldSetter.setField(event0, event0.getClass().getField("values"), new float[] { 180 });
+        FieldSetter.setField(event0, event0.getClass().getField("values"), new float[]{180});
 
         provider.onSensorChanged(event0);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(3, mIntegerCaptor.getValue().intValue());
 
@@ -268,11 +280,11 @@
 
         SensorEvent event1 = mock(SensorEvent.class);
         event1.sensor = sensor;
-        FieldSetter.setField(event1, event1.getClass().getField("values"), new float[] { 90 });
+        FieldSetter.setField(event1, event1.getClass().getField("values"), new float[]{90});
 
         provider.onSensorChanged(event1);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(2, mIntegerCaptor.getValue().intValue());
 
@@ -280,11 +292,11 @@
 
         SensorEvent event2 = mock(SensorEvent.class);
         event2.sensor = sensor;
-        FieldSetter.setField(event2, event2.getClass().getField("values"), new float[] { 0 });
+        FieldSetter.setField(event2, event2.getClass().getField("values"), new float[]{0});
 
         provider.onSensorChanged(event2);
 
-        verify(listener, never()).onSupportedDeviceStatesChanged(mIntArrayCaptor.capture());
+        verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
         verify(listener).onStateChanged(mIntegerCaptor.capture());
         assertEquals(1, mIntegerCaptor.getValue().intValue());
     }
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 533dc17..592eea1 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -179,7 +179,8 @@
                 .thenReturn(mPowerSaveState);
         when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
         when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
-        when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true);
+        when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(), anyBoolean()))
+                .thenReturn(true);
         when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
         when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
 
@@ -877,6 +878,22 @@
     }
 
     @Test
+    public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted()
+            throws Exception {
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
+        createService();
+        mService.systemReady(null);
+
+        mService.getBinderServiceInstance().wakeUp(mClock.now(),
+                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
+
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+                DisplayPowerRequest.POLICY_BRIGHT);
+    }
+
+    @Test
     public void testIsAmbientDisplayAvailable_available() throws Exception {
         createService();
         when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index b6ae855..03e60af 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.hardware.power.stats.Channel;
 import android.hardware.power.stats.EnergyConsumer;
+import android.hardware.power.stats.EnergyConsumerAttribution;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyMeasurement;
 import android.hardware.power.stats.PowerEntity;
@@ -67,11 +68,13 @@
     private static final String RESIDENCY_FILENAME = "residencytest";
     private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto";
     private static final String CHANNEL_NAME = "channelname";
+    private static final String CHANNEL_SUBSYSTEM = "channelsubsystem";
     private static final String POWER_ENTITY_NAME = "powerentityinfo";
     private static final String STATE_NAME = "stateinfo";
     private static final String ENERGY_CONSUMER_NAME = "energyconsumer";
     private static final int ENERGY_METER_COUNT = 8;
     private static final int ENERGY_CONSUMER_COUNT = 2;
+    private static final int ENERGY_CONSUMER_ATTRIBUTION_COUNT = 5;
     private static final int POWER_ENTITY_COUNT = 3;
     private static final int STATE_INFO_COUNT = 5;
     private static final int STATE_RESIDENCY_COUNT = 4;
@@ -203,6 +206,13 @@
                 energyConsumedList[i].id = i;
                 energyConsumedList[i].timestampMs = i;
                 energyConsumedList[i].energyUWs = i;
+                energyConsumedList[i].attribution =
+                    new EnergyConsumerAttribution[ENERGY_CONSUMER_ATTRIBUTION_COUNT];
+                for (int j = 0; j < energyConsumedList[i].attribution.length; j++) {
+                    energyConsumedList[i].attribution[j] = new EnergyConsumerAttribution();
+                    energyConsumedList[i].attribution[j].uid = j;
+                    energyConsumedList[i].attribution[j].energyUWs = j;
+                }
             }
             return energyConsumedList;
         }
@@ -214,12 +224,13 @@
                 energyMeterList[i] = new Channel();
                 energyMeterList[i].id = i;
                 energyMeterList[i].name = new String(CHANNEL_NAME + i);
+                energyMeterList[i].subsystem = new String(CHANNEL_SUBSYSTEM + i);
             }
             return energyMeterList;
         }
 
         @Override
-        public EnergyMeasurement[] readEnergyMeters(int[] channelIds) {
+        public EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
             EnergyMeasurement[] energyMeasurementList = new EnergyMeasurement[ENERGY_METER_COUNT];
             for (int i = 0; i < energyMeasurementList.length; i++) {
                 energyMeasurementList[i] = new EnergyMeasurement();
@@ -248,7 +259,7 @@
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
         // Write data to on-device storage.
-        mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER);
+        mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
 
         // The above call puts a message on a handler.  Wait for
         // it to be processed.
@@ -272,6 +283,7 @@
         for (int i = 0; i < pssProto.channel.length; i++) {
             assertTrue(pssProto.channel[i].id == i);
             assertTrue(pssProto.channel[i].name.equals(CHANNEL_NAME + i));
+            assertTrue(pssProto.channel[i].subsystem.equals(CHANNEL_SUBSYSTEM + i));
         }
 
         // Validate the energyMeasurement array matches what was written to on-device storage.
@@ -290,7 +302,7 @@
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
 
         // Write data to on-device storage.
-        mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_TIMER);
+        mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
 
         // The above call puts a message on a handler.  Wait for
         // it to be processed.
@@ -321,6 +333,12 @@
             assertTrue(pssProto.energyConsumerResult[i].id == i);
             assertTrue(pssProto.energyConsumerResult[i].timestampMs == i);
             assertTrue(pssProto.energyConsumerResult[i].energyUws == i);
+            assertTrue(pssProto.energyConsumerResult[i].attribution.length
+                    == ENERGY_CONSUMER_ATTRIBUTION_COUNT);
+            for (int j = 0; j < pssProto.energyConsumerResult[i].attribution.length; j++) {
+                assertTrue(pssProto.energyConsumerResult[i].attribution[j].uid == j);
+                assertTrue(pssProto.energyConsumerResult[i].attribution[j].energyUws  == j);
+            }
         }
     }
 
@@ -414,6 +432,7 @@
         for (int i = 0; i < pssProto.channel.length; i++) {
             assertTrue(pssProto.channel[i].id == i);
             assertTrue(pssProto.channel[i].name.equals(CHANNEL_NAME + i));
+            assertTrue(pssProto.channel[i].subsystem.equals(CHANNEL_SUBSYSTEM + i));
         }
 
         // No energyMeasurements should be written to the incident report since it
@@ -547,6 +566,7 @@
         for (int i = 0; i < pssProto.channel.length; i++) {
             assertTrue(pssProto.channel[i].id == i);
             assertTrue(pssProto.channel[i].name.equals(CHANNEL_NAME + i));
+            assertTrue(pssProto.channel[i].subsystem.equals(CHANNEL_SUBSYSTEM + i));
         }
 
         // No energyMeasurements should be written to the incident report since the
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index c59ead9..6dcfb42 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -1123,10 +1123,10 @@
     }
 
     /**
-     * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
-     * like the real thing should, it also asserts preconditions.
+     * A fake implementation of {@link TimeDetectorStrategyImpl.Environment}. Besides tracking
+     * changes and behaving like the real thing should, it also asserts preconditions.
      */
-    private static class FakeCallback implements TimeDetectorStrategyImpl.Callback {
+    private static class FakeEnvironment implements TimeDetectorStrategyImpl.Environment {
         private boolean mAutoTimeDetectionEnabled;
         private boolean mWakeLockAcquired;
         private long mElapsedRealtimeMillis;
@@ -1254,41 +1254,41 @@
      */
     private class Script {
 
-        private final FakeCallback mFakeCallback;
+        private final FakeEnvironment mFakeEnvironment;
         private final TimeDetectorStrategyImpl mTimeDetectorStrategy;
 
         Script() {
-            mFakeCallback = new FakeCallback();
-            mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeCallback);
+            mFakeEnvironment = new FakeEnvironment();
+            mTimeDetectorStrategy = new TimeDetectorStrategyImpl(mFakeEnvironment);
         }
 
         Script pokeAutoTimeDetectionEnabled(boolean enabled) {
-            mFakeCallback.pokeAutoTimeDetectionEnabled(enabled);
+            mFakeEnvironment.pokeAutoTimeDetectionEnabled(enabled);
             return this;
         }
 
         Script pokeFakeClocks(TimestampedValue<Instant> timeInfo) {
-            mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
-            mFakeCallback.pokeSystemClockMillis(timeInfo.getValue().toEpochMilli());
+            mFakeEnvironment.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis());
+            mFakeEnvironment.pokeSystemClockMillis(timeInfo.getValue().toEpochMilli());
             return this;
         }
 
         Script pokeThresholds(int systemClockUpdateThreshold) {
-            mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
+            mFakeEnvironment.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold);
             return this;
         }
 
         Script pokeAutoOriginPriorities(@Origin int... autoOriginPriorities) {
-            mFakeCallback.pokeAutoOriginPriorities(autoOriginPriorities);
+            mFakeEnvironment.pokeAutoOriginPriorities(autoOriginPriorities);
             return this;
         }
 
         long peekElapsedRealtimeMillis() {
-            return mFakeCallback.peekElapsedRealtimeMillis();
+            return mFakeEnvironment.peekElapsedRealtimeMillis();
         }
 
         long peekSystemClockMillis() {
-            return mFakeCallback.peekSystemClockMillis();
+            return mFakeEnvironment.peekSystemClockMillis();
         }
 
         Script simulateTelephonyTimeSuggestion(TelephonyTimeSuggestion timeSuggestion) {
@@ -1324,13 +1324,13 @@
         }
 
         Script simulateAutoTimeDetectionToggle() {
-            mFakeCallback.simulateAutoTimeZoneDetectionToggle();
+            mFakeEnvironment.simulateAutoTimeZoneDetectionToggle();
             mTimeDetectorStrategy.handleAutoTimeConfigChanged();
             return this;
         }
 
         Script simulateTimePassing(long clockIncrementMillis) {
-            mFakeCallback.simulateTimePassing(clockIncrementMillis);
+            mFakeEnvironment.simulateTimePassing(clockIncrementMillis);
             return this;
         }
 
@@ -1342,14 +1342,14 @@
         }
 
         Script verifySystemClockWasNotSetAndResetCallTracking() {
-            mFakeCallback.verifySystemClockNotSet();
-            mFakeCallback.resetCallTracking();
+            mFakeEnvironment.verifySystemClockNotSet();
+            mFakeEnvironment.resetCallTracking();
             return this;
         }
 
         Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) {
-            mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
-            mFakeCallback.resetCallTracking();
+            mFakeEnvironment.verifySystemClockWasSet(expectedSystemClockMillis);
+            mFakeEnvironment.resetCallTracking();
             return this;
         }
 
@@ -1427,7 +1427,7 @@
         ManualTimeSuggestion generateManualTimeSuggestion(Instant suggestedTime) {
             TimestampedValue<Long> utcTime =
                     new TimestampedValue<>(
-                            mFakeCallback.peekElapsedRealtimeMillis(),
+                            mFakeEnvironment.peekElapsedRealtimeMillis(),
                             suggestedTime.toEpochMilli());
             return new ManualTimeSuggestion(utcTime);
         }
@@ -1461,7 +1461,7 @@
         NetworkTimeSuggestion generateNetworkTimeSuggestion(Instant suggestedTime) {
             TimestampedValue<Long> utcTime =
                     new TimestampedValue<>(
-                            mFakeCallback.peekElapsedRealtimeMillis(),
+                            mFakeEnvironment.peekElapsedRealtimeMillis(),
                             suggestedTime.toEpochMilli());
             return new NetworkTimeSuggestion(utcTime);
         }
@@ -1473,7 +1473,7 @@
         GnssTimeSuggestion generateGnssTimeSuggestion(Instant suggestedTime) {
             TimestampedValue<Long> utcTime =
                     new TimestampedValue<>(
-                            mFakeCallback.peekElapsedRealtimeMillis(),
+                            mFakeEnvironment.peekElapsedRealtimeMillis(),
                             suggestedTime.toEpochMilli());
             return new GnssTimeSuggestion(utcTime);
         }
@@ -1485,7 +1485,7 @@
         ExternalTimeSuggestion generateExternalTimeSuggestion(Instant suggestedTime) {
             TimestampedValue<Long> utcTime =
                     new TimestampedValue<>(
-                            mFakeCallback.peekElapsedRealtimeMillis(),
+                            mFakeEnvironment.peekElapsedRealtimeMillis(),
                             suggestedTime.toEpochMilli());
             return new ExternalTimeSuggestion(utcTime);
         }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index a6ffd20..c8dba5f 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -167,15 +167,15 @@
             createConfig(null, false /* geoDetection */);
 
     private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
-    private FakeCallback mFakeCallback;
+    private FakeEnvironment mFakeEnvironment;
     private MockConfigChangeListener mMockConfigChangeListener;
 
 
     @Before
     public void setUp() {
-        mFakeCallback = new FakeCallback();
+        mFakeEnvironment = new FakeEnvironment();
         mMockConfigChangeListener = new MockConfigChangeListener();
-        mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeCallback);
+        mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
         mTimeZoneDetectorStrategy.addConfigChangeListener(mMockConfigChangeListener);
     }
 
@@ -183,7 +183,7 @@
     public void testGetCurrentUserConfiguration() {
         new Script().initializeConfig(CONFIG_INT_AUTO_ENABLED_GEO_DISABLED);
         ConfigurationInternal expectedConfiguration =
-                mFakeCallback.getConfigurationInternal(USER_ID);
+                mFakeEnvironment.getConfigurationInternal(USER_ID);
         assertEquals(expectedConfiguration,
                 mTimeZoneDetectorStrategy.getCurrentUserConfigurationInternal());
     }
@@ -490,7 +490,7 @@
 
     private void makeSlotIndex1SuggestionAndCheckState(Script script, TelephonyTestCase testCase) {
         // Give the next suggestion a different zone from the currently set device time zone;
-        String currentZoneId = mFakeCallback.getDeviceTimeZone();
+        String currentZoneId = mFakeEnvironment.getDeviceTimeZone();
         String suggestionZoneId =
                 "Europe/London".equals(currentZoneId) ? "Europe/Paris" : "Europe/London";
         TelephonyTimeZoneSuggestion zoneSlotIndex1Suggestion =
@@ -610,9 +610,9 @@
     }
 
     /**
-     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
-     * zone is actually necessary. This test proves that the strategy doesn't assume it knows the
-     * current settings.
+     * The {@link TimeZoneDetectorStrategyImpl.Environment} is left to detect whether changing the
+     * time zone is actually necessary. This test proves that the strategy doesn't assume it knows
+     * the current settings.
      */
     @Test
     public void testTelephonySuggestionStrategyDoesNotAssumeCurrentSetting_autoTelephony() {
@@ -958,7 +958,7 @@
         return builder.build();
     }
 
-    static class FakeCallback implements TimeZoneDetectorStrategyImpl.Callback {
+    static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
 
         private final TestState<ConfigurationInternal> mConfigurationInternal = new TestState<>();
         private final TestState<String> mTimeZoneId = new TestState<>();
@@ -1051,12 +1051,12 @@
     private class Script {
 
         Script initializeConfig(ConfigurationInternal configuration) {
-            mFakeCallback.initializeConfig(configuration);
+            mFakeEnvironment.initializeConfig(configuration);
             return this;
         }
 
         Script initializeTimeZoneSetting(String zoneId) {
-            mFakeCallback.initializeTimeZoneSetting(zoneId);
+            mFakeEnvironment.initializeTimeZoneSetting(zoneId);
             return this;
         }
 
@@ -1084,7 +1084,7 @@
         Script simulateManualTimeZoneSuggestion(
                 @UserIdInt int userId, ManualTimeZoneSuggestion manualTimeZoneSuggestion,
                 boolean expectedResult) {
-            mFakeCallback.assertKnownUser(userId);
+            mFakeEnvironment.assertKnownUser(userId);
             boolean actualResult = mTimeZoneDetectorStrategy.suggestManualTimeZone(
                     userId, manualTimeZoneSuggestion);
             assertEquals(expectedResult, actualResult);
@@ -1104,26 +1104,26 @@
          * state was last reset.
          */
         Script verifyTimeZoneNotChanged() {
-            mFakeCallback.assertTimeZoneNotChanged();
+            mFakeEnvironment.assertTimeZoneNotChanged();
             return this;
         }
 
         /** Verifies the device's time zone has been set and clears change tracking history. */
         Script verifyTimeZoneChangedAndReset(String zoneId) {
-            mFakeCallback.assertTimeZoneChangedTo(zoneId);
-            mFakeCallback.commitAllChanges();
+            mFakeEnvironment.assertTimeZoneChangedTo(zoneId);
+            mFakeEnvironment.commitAllChanges();
             return this;
         }
 
         Script verifyTimeZoneChangedAndReset(ManualTimeZoneSuggestion suggestion) {
-            mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
-            mFakeCallback.commitAllChanges();
+            mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
+            mFakeEnvironment.commitAllChanges();
             return this;
         }
 
         Script verifyTimeZoneChangedAndReset(TelephonyTimeZoneSuggestion suggestion) {
-            mFakeCallback.assertTimeZoneChangedTo(suggestion.getZoneId());
-            mFakeCallback.commitAllChanges();
+            mFakeEnvironment.assertTimeZoneChangedTo(suggestion.getZoneId());
+            mFakeEnvironment.commitAllChanges();
             return this;
         }
 
@@ -1131,9 +1131,9 @@
          * Verifies that the configuration has been changed to the expected value.
          */
         Script verifyConfigurationChangedAndReset(ConfigurationInternal expected) {
-            mFakeCallback.mConfigurationInternal.assertHasBeenSet();
-            assertEquals(expected, mFakeCallback.mConfigurationInternal.getLatest());
-            mFakeCallback.commitAllChanges();
+            mFakeEnvironment.mConfigurationInternal.assertHasBeenSet();
+            assertEquals(expected, mFakeEnvironment.mConfigurationInternal.getLatest());
+            mFakeEnvironment.commitAllChanges();
 
             // Also confirm the listener triggered.
             mMockConfigChangeListener.verifyOnChangeCalled();
@@ -1146,7 +1146,7 @@
          * {@link TimeZoneConfiguration} have been changed.
          */
         Script verifyConfigurationNotChanged() {
-            mFakeCallback.mConfigurationInternal.assertHasNotBeenSet();
+            mFakeEnvironment.mConfigurationInternal.assertHasNotBeenSet();
 
             // Also confirm the listener did not trigger.
             mMockConfigChangeListener.verifyOnChangeNotCalled();
@@ -1154,7 +1154,7 @@
         }
 
         Script resetConfigurationTracking() {
-            mFakeCallback.commitAllChanges();
+            mFakeEnvironment.commitAllChanges();
             return this;
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
index 972b3bb..4284240 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java
@@ -13,18 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
-import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED;
-import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
-import static com.android.server.location.timezone.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED;
-import static com.android.server.location.timezone.TimeZoneProviderEvent.createPermanentFailureEvent;
-import static com.android.server.location.timezone.TimeZoneProviderEvent.createUncertainEvent;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED;
+import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
+import static com.android.server.timezonedetector.location.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -40,10 +38,10 @@
 import android.service.timezone.TimeZoneProviderSuggestion;
 import android.util.IndentingPrintWriter;
 
-import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
 import com.android.server.timezonedetector.TestState;
+import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -66,9 +64,9 @@
     private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2 =
             createSuggestionEvent(asList("Europe/Paris"));
     private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT =
-            createUncertainEvent();
+            TimeZoneProviderEvent.createUncertainEvent();
     private static final TimeZoneProviderEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT =
-            createPermanentFailureEvent("Test");
+            TimeZoneProviderEvent.createPermanentFailureEvent("Test");
 
     private TestThreadingDomain mTestThreadingDomain;
     private TestCallback mTestCallback;
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/HandlerThreadingDomainTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/location/HandlerThreadingDomainTest.java
index 02de24d..e7dd9794 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/HandlerThreadingDomainTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
@@ -24,7 +24,7 @@
 import android.os.HandlerThread;
 import android.platform.test.annotations.Presubmit;
 
-import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue;
+import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
similarity index 92%
rename from services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index cb292db..095c868 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
 import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
 
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
-import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
-import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN;
+import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED;
+import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -40,10 +40,10 @@
 import android.service.timezone.TimeZoneProviderSuggestion;
 import android.util.IndentingPrintWriter;
 
-import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderListener;
-import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.TestState;
+import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderListener;
+import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index 4810563..d319488 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import android.annotation.UserIdInt;
 
diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java
index b1a5ff9..e08fea0 100644
--- a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.location.timezone;
+package com.android.server.timezonedetector.location;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
index 28d313b..e71c2f5 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java
@@ -294,6 +294,8 @@
 
     private InputDevice createInputDevice(int id, boolean hasVibrator) {
         return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0,
-                null, hasVibrator, false, false, false /* hasSensor */);
+                null, hasVibrator, false, false, false /* hasSensor */, false /* hasBattery */);
+
+
     }
 }
diff --git a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index 4c82818..c6e35cf 100644
--- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -26,21 +26,17 @@
 object MockitoUtils {
     val ANSWER_THROWS = Answer<Any?> {
         when (val name = it.method.name) {
-            "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+            "toString" -> return@Answer try {
+                Answers.CALLS_REAL_METHODS.answer(it)
+            } catch (e: Exception) {
+                "failure calling toString"
+            }
             else -> {
                 val arguments = it.arguments
                         ?.takeUnless { it.isEmpty() }
-                        ?.mapIndexed { index, arg ->
-                            try {
-                                arg?.toString()
-                            } catch (e: Exception) {
-                                "toString[$index] threw ${e.message}"
-                            }
-                        }
-                        ?.joinToString()
-                        ?.let {
-                            "with $it"
-                        }
+                        ?.mapIndexed { index, arg -> arg.attemptToString(index) }
+                        ?.joinToString { it.attemptToString(null) }
+                        ?.let { "with $it" }
                         .orEmpty()
 
                 throw UnsupportedOperationException("${it.mock::class.java.simpleName}#$name " +
@@ -48,6 +44,19 @@
             }
         }
     }
+
+    // Sometimes mocks won't have a toString method, so try-catch and return some default
+    private fun Any?.attemptToString(id: Any? = null): String {
+        return try {
+            toString()
+        } catch (e: Exception) {
+            if (id == null) {
+                e.message ?: "ERROR"
+            } else {
+                "$id ${e.message}"
+            }
+        }
+    }
 }
 
 inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
@@ -83,4 +92,4 @@
 inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit = {}) =
         spyThrowOnUnmocked<T>(null, block)
 
-inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)
\ No newline at end of file
+inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)
diff --git a/services/tests/shortcutmanagerutils/OWNERS b/services/tests/shortcutmanagerutils/OWNERS
new file mode 100644
index 0000000..d825dfd
--- /dev/null
+++ b/services/tests/shortcutmanagerutils/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pm/OWNERS
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 907f887..9f1fff7 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -136,6 +136,20 @@
         return sb.toString();
     }
 
+    public static List<String> extractShortcutIds(List<String> result) {
+        final String prefix = "ShortcutInfo {id=";
+        final String postfix = ", ";
+
+        List<String> ids = new ArrayList<>();
+        for (String line : result) {
+            if (line.contains(prefix)) {
+                ids.add(line.substring(
+                        line.indexOf(prefix) + prefix.length(), line.indexOf(postfix)));
+            }
+        }
+        return ids;
+    }
+
     public static boolean resultContains(List<String> result, String expected) {
         for (String line : result) {
             if (line.contains(expected)) {
@@ -160,6 +174,16 @@
         return result;
     }
 
+    public static List<String> assertHaveIds(List<String> result, String... expectedIds) {
+        assertSuccess(result);
+
+        final SortedSet<String> expected = new TreeSet<>(list(expectedIds));
+        final SortedSet<String> actual = new TreeSet<>(extractShortcutIds(result));
+        assertEquals(expected, actual);
+
+        return result;
+    }
+
     public static List<String> runCommand(Instrumentation instrumentation, String command) {
         return runCommand(instrumentation, command, null);
     }
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index e5646db..1dd4212 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -60,6 +60,6 @@
         "libui",
         "libunwindstack",
         "libutils",
-        "netd_aidl_interface-cpp",
+        "netd_aidl_interface-V5-cpp",
     ],
 }
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 8c744c9..acda4d5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -29,8 +29,8 @@
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
 
-import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER;
 import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER;
@@ -3536,6 +3536,28 @@
         assertFalse(conversationWrapperContainsChannel(convos, channel2));
     }
 
+    @Test
+    public void testGetConversations_parentDeleted() {
+        String convoId = "convo";
+        NotificationChannel messages =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
+
+        NotificationChannel channel =
+                new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
+        channel.setConversationId(messages.getId(), convoId);
+        channel.setImportantConversation(true);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+
+        mHelper.permanentlyDeleteNotificationChannel(PKG_O, UID_O, "messages");
+
+        List<ConversationChannelWrapper> convos =
+                mHelper.getConversations(IntArray.wrap(new int[] {0}), true);
+
+        assertEquals(1, convos.size());
+        assertTrue(conversationWrapperContainsChannel(convos, channel));
+    }
+
     private boolean conversationWrapperContainsChannel(List<ConversationChannelWrapper> list,
             NotificationChannel expected) {
         for (ConversationChannelWrapper ccw : list) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 57d5323..72c6028 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -33,8 +33,8 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
 
-import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
 import static com.android.os.AtomsProto.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER;
 import static com.android.os.AtomsProto.DNDModeProto.ENABLED_FIELD_NUMBER;
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index cf977b4..ddf284401 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -51,7 +51,7 @@
     ],
 
     libs: [
-        "android.hardware.power-java",
+        "android.hardware.power-V1-java",
         "android.test.mock",
         "android.test.base",
         "android.test.runner",
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index de2cc76..9385110 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -679,8 +679,10 @@
                 new RemoteAnimationAdapter(new Stub() {
 
                     @Override
-                    public void onAnimationStart(RemoteAnimationTarget[] apps,
+                    public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                            RemoteAnimationTarget[] apps,
                             RemoteAnimationTarget[] wallpapers,
+                            RemoteAnimationTarget[] nonApps,
                             IRemoteAnimationFinishedCallback finishedCallback) {
                     }
 
@@ -1715,9 +1717,8 @@
             doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay(
                     any() /* window */,  any() /* attrs */,
                     anyInt() /* viewVisibility */, anyInt() /* displayId */,
-                    any() /* requestedVisibility */, any() /* outFrame */,
-                    any() /* outInputChannel */, any() /* outInsetsState */,
-                    any() /* outActiveControls */);
+                    any() /* requestedVisibility */, any() /* outInputChannel */,
+                    any() /* outInsetsState */, any() /* outActiveControls */);
             mAtm.mWindowManager.mStartingSurfaceController
                     .createTaskSnapshotSurface(activity, snapshot);
         } catch (RemoteException ignored) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
index ce96771..db241de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
@@ -16,9 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -26,22 +23,17 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
-import android.app.IApplicationThread;
 import android.content.Intent;
-import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wm.ActivityStarter.Factory;
-import com.android.server.wm.ActivityTaskSupervisor.PendingActivityLaunch;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Random;
-
 /**
  * Tests for the {@link ActivityStartController} class.
  *
@@ -66,36 +58,6 @@
     }
 
     /**
-     * Ensures that pending launches are processed.
-     */
-    @Test
-    public void testPendingActivityLaunches() {
-        final Random random = new Random();
-
-        final ActivityRecord activity = new ActivityBuilder(mAtm).build();
-        final ActivityRecord source = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .build();
-        final int startFlags = random.nextInt();
-        final Task rootTask = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
-                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        final WindowProcessController wpc = new WindowProcessController(mAtm,
-                mAtm.mContext.getApplicationInfo(), "name", 12345,
-                UserHandle.getUserId(12345), mock(Object.class),
-                mock(WindowProcessListener.class));
-        wpc.setThread(mock(IApplicationThread.class));
-
-        mController.addPendingActivityLaunch(
-                new PendingActivityLaunch(activity, source, startFlags, rootTask, wpc, null));
-        final boolean resume = random.nextBoolean();
-        mController.doPendingActivityLaunches(resume);
-
-        verify(mStarter, times(1)).startResolvedActivity(eq(activity), eq(source), eq(null),
-                eq(null), eq(startFlags), eq(resume), eq(null), eq(null), eq(null));
-    }
-
-
-    /**
      * Ensures instances are recycled after execution.
      */
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 4bfc837..36adf28 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -27,7 +27,6 @@
 import static android.app.ActivityManager.START_PERMISSION_DENIED;
 import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
 import static android.app.ActivityManager.START_SUCCESS;
-import static android.app.ActivityManager.START_SWITCHES_CANCELED;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -119,7 +118,6 @@
     private static final int PRECONDITION_DIFFERENT_UID = 1 << 7;
     private static final int PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION = 1 << 8;
     private static final int PRECONDITION_CANNOT_START_ANY_ACTIVITY = 1 << 9;
-    private static final int PRECONDITION_DISALLOW_APP_SWITCHING = 1 << 10;
 
     private static final int FAKE_CALLING_UID = 666;
     private static final int FAKE_REAL_CALLING_UID = 667;
@@ -153,8 +151,6 @@
                         | PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION,
                 START_NOT_VOICE_COMPATIBLE);
         verifyStartActivityPreconditions(PRECONDITION_CANNOT_START_ANY_ACTIVITY, START_ABORTED);
-        verifyStartActivityPreconditions(PRECONDITION_DISALLOW_APP_SWITCHING,
-                START_SWITCHES_CANCELED);
     }
 
     private static boolean containsConditions(int preconditions, int mask) {
@@ -244,11 +240,6 @@
             intent.setComponent(source.mActivityComponent);
         }
 
-        if (containsConditions(preconditions, PRECONDITION_DISALLOW_APP_SWITCHING)) {
-            doReturn(false).when(service).checkAppSwitchAllowedLocked(
-                    anyInt(), anyInt(), anyInt(), anyInt(), any());
-        }
-
         if (containsConditions(preconditions, PRECONDITION_CANNOT_START_ANY_ACTIVITY)) {
             doReturn(false).when(service.mTaskSupervisor).checkStartAnyActivityPermission(
                     any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), any(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 91b9449..71f1914 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -36,6 +36,7 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -70,8 +71,10 @@
 
     class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
         @Override
-        public void onAnimationStart(RemoteAnimationTarget[] apps,
+        public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                RemoteAnimationTarget[] apps,
                 RemoteAnimationTarget[] wallpapers,
+                RemoteAnimationTarget[] nonApps,
                 IRemoteAnimationFinishedCallback finishedCallback) {
             for (RemoteAnimationTarget target : apps) {
                 assertNotNull(target.startBounds);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index f1e3609..83aca5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -265,8 +265,10 @@
     private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
         boolean mCancelled = false;
         @Override
-        public void onAnimationStart(RemoteAnimationTarget[] apps,
+        public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                RemoteAnimationTarget[] apps,
                 RemoteAnimationTarget[] wallpapers,
+                RemoteAnimationTarget[] nonApps,
                 IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 2f4c8e2..d13e4dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -15,7 +15,7 @@
  */
 
 package com.android.server.wm;
-import static android.os.Process.INVALID_UID;
+
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -587,7 +587,7 @@
 
         final WindowToken token = new WindowToken(mWms, mock(IBinder.class),
                 TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, false /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */,
                 false /* fromClientToken */, null /* options */);
         policy.addWindow(token);
 
@@ -621,11 +621,11 @@
 
         final WindowToken token1 = new WindowToken(mWms, mock(IBinder.class),
                 TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, false /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */,
                 false /* fromClientToken */, null /* options */);
         final WindowToken token2 = new WindowToken(mWms, mock(IBinder.class),
                 TYPE_WALLPAPER, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, false /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */,
                 false /* fromClientToken */, null /* options */);
         policy.addWindow(token1);
         policy.addWindow(token2);
@@ -672,15 +672,15 @@
         options2.putInt("HIERARCHY_ROOT_ID", mGroupRoot2.mFeatureId);
         final WindowToken token0 = new WindowToken(mWms, mock(IBinder.class),
                 TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, false /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */,
                 false /* fromClientToken */, null /* options */);
         final WindowToken token1 = new WindowToken(mWms, mock(IBinder.class),
                 TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, false /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */,
                 false /* fromClientToken */, options1);
         final WindowToken token2 = new WindowToken(mWms, mock(IBinder.class),
                 TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, false /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */,
                 false /* fromClientToken */, options2);
 
         policy.addWindow(token0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 5c67db7..89b962b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.content.ActivityInfoProto.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
@@ -50,6 +50,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
@@ -408,6 +409,32 @@
     }
 
     @Test
+    public void testRestrictAppBoundsToOverrideBounds() {
+        final RootDisplayArea root =
+                new DisplayAreaPolicyBuilderTest.SurfacelessDisplayAreaRoot(mWm);
+        final DisplayArea<DisplayArea> da = new DisplayArea<>(mWm, ANY, "Test_DA");
+        root.addChild(da, POSITION_TOP);
+        final Rect displayBounds = new Rect(0, 0, 1800, 2800);
+        final Rect displayAppBounds = new Rect(0, 100, 1800, 2800);
+        final Rect daBounds = new Rect(0, 1400, 1800, 2800);
+        root.setBounds(displayBounds);
+
+        // DA inherit parent app bounds.
+        final Configuration displayConfig = new Configuration();
+        displayConfig.windowConfiguration.setAppBounds(displayAppBounds);
+        root.onRequestedOverrideConfigurationChanged(displayConfig);
+
+        assertEquals(displayAppBounds, da.getConfiguration().windowConfiguration.getAppBounds());
+
+        // Restrict DA appBounds to override Bounds
+        da.setBounds(daBounds);
+
+        final Rect expectedDaAppBounds = new Rect(daBounds);
+        expectedDaAppBounds.intersect(displayAppBounds);
+        assertEquals(expectedDaAppBounds, da.getConfiguration().windowConfiguration.getAppBounds());
+    }
+
+    @Test
     public void testGetOrientation() {
         final DisplayArea.Tokens area = new DisplayArea.Tokens(mWm, ABOVE_TASKS, "test");
         final WindowToken token = createWindowToken(TYPE_APPLICATION_OVERLAY);
@@ -514,7 +541,7 @@
         return new WindowState(mWm, mock(Session.class), new TestIWindow(), token,
                 null /* parentWindow */, 0 /* appOp */, new WindowManager.LayoutParams(),
                 View.VISIBLE, 0 /* ownerId */, 0 /* showUserId */,
-                false /* ownerCanAddInternalSystemWindow */, false /* ownerCanUseBackgroundBlur */);
+                false /* ownerCanAddInternalSystemWindow */);
     }
 
     private WindowToken createWindowToken(int type) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 83282a5..4c2d124 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -27,7 +27,6 @@
 import static android.os.Build.VERSION_CODES.Q;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_PRIVATE;
-import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
 import static android.view.DisplayCutout.fromBoundingRect;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
@@ -96,6 +95,7 @@
 import android.app.WindowConfiguration;
 import android.app.servertransaction.FixedRotationAdjustmentsItem;
 import android.content.res.Configuration;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.metrics.LogMaker;
@@ -558,7 +558,7 @@
         // hence isLetterboxedAppWindow() returns true.
         ws.mActivityRecord.getConfiguration().windowConfiguration.setBounds(new Rect(1, 1, 1, 1));
         assertFalse("matchesRootDisplayAreaBounds() should return false",
-                ws.matchesRootDisplayAreaBounds());
+                ws.matchesDisplayAreaBounds());
         assertTrue("isLetterboxedAppWindow() should return true", ws.isLetterboxedAppWindow());
         assertTrue("IME shouldn't be attached to app",
                 dc.computeImeParent() != dc.getImeTarget(IME_TARGET_LAYERING).getWindow()
@@ -707,6 +707,7 @@
         // same width and height.
         final int displayWidth = dc.mInitialDisplayWidth;
         final int displayHeight = dc.mInitialDisplayHeight;
+        final float density = dc.mInitialDisplayDensity;
         final int cutoutWidth = 40;
         final int cutoutHeight = 10;
         final int left = (displayWidth - cutoutWidth) / 2;
@@ -714,9 +715,13 @@
         final int right = (displayWidth + cutoutWidth) / 2;
         final int bottom = cutoutHeight;
 
-        final Rect r1 = new Rect(left, top, right, bottom);
+        final Rect zeroRect = new Rect();
+        final Rect[] bounds = new Rect[]{zeroRect, new Rect(left, top, right, bottom), zeroRect,
+                zeroRect};
+        final DisplayCutout.CutoutPathParserInfo info = new DisplayCutout.CutoutPathParserInfo(
+                displayWidth, displayHeight, density, "", Surface.ROTATION_0, 1f);
         final DisplayCutout cutout = new WmDisplayCutout(
-                fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom, BOUNDS_POSITION_TOP), null)
+                DisplayCutout.constructDisplayCutout(bounds, Insets.NONE, info), null)
                         .computeSafeInsets(displayWidth, displayHeight).getDisplayCutout();
 
         dc.mInitialDisplayCutout = cutout;
@@ -731,9 +736,12 @@
         // |             |      ---o
         // |             |      |
         // |             |      -------------
-        final Rect r = new Rect(top, left, bottom, right);
+        final Rect[] bounds90 = new Rect[]{new Rect(top, left, bottom, right), zeroRect, zeroRect,
+                zeroRect};
+        final DisplayCutout.CutoutPathParserInfo info90 = new DisplayCutout.CutoutPathParserInfo(
+                displayWidth, displayHeight, density, "", Surface.ROTATION_90, 1f);
         assertEquals(new WmDisplayCutout(
-                fromBoundingRect(r.left, r.top, r.right, r.bottom, BOUNDS_POSITION_LEFT), null)
+                        DisplayCutout.constructDisplayCutout(bounds90, Insets.NONE, info90), null)
                         .computeSafeInsets(displayHeight, displayWidth).getDisplayCutout(),
                 dc.getDisplayInfo().displayCutout);
     }
@@ -1600,6 +1608,16 @@
     }
 
     @Test
+    public void testGetOrCreateRootHomeTask_dontMoveToTop() {
+        DisplayContent display = createNewDisplay();
+        display.mDontMoveToTop = true;
+        TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
+
+        assertNull(taskDisplayArea.getRootHomeTask());
+        assertNull(taskDisplayArea.getOrCreateRootHomeTask());
+    }
+
+    @Test
     public void testValidWindowingLayer() {
         final SurfaceControl windowingLayer = mDisplayContent.getWindowingLayer();
         assertNotNull(windowingLayer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 37fb0e9..b1ea4a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -55,7 +55,6 @@
 import static org.mockito.Mockito.spy;
 import static org.testng.Assert.expectThrows;
 
-import android.app.WindowConfiguration;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -66,6 +65,7 @@
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.InsetsState;
+import android.view.RoundedCorners;
 import android.view.WindowInsets.Side;
 import android.view.WindowInsets.Type;
 import android.view.WindowManager;
@@ -99,6 +99,7 @@
     private int mRotation = ROTATION_0;
     private boolean mHasDisplayCutout;
     private boolean mIsLongEdgeDisplayCutout;
+    private boolean mHasRoundedCorners;
 
     private final Rect mDisplayBounds = new Rect();
 
@@ -153,6 +154,11 @@
         updateDisplayFrames();
     }
 
+    public void addRoundedCorners() {
+        mHasRoundedCorners = true;
+        updateDisplayFrames();
+    }
+
     private void updateDisplayFrames() {
         mFrames = createDisplayFrames(
                 mDisplayContent.getInsetsStateController().getRawInsetsState());
@@ -166,9 +172,11 @@
     private DisplayFrames createDisplayFrames(InsetsState insetsState) {
         final Pair<DisplayInfo, WmDisplayCutout> info = displayInfoAndCutoutForRotation(mRotation,
                 mHasDisplayCutout, mIsLongEdgeDisplayCutout);
+        final RoundedCorners roundedCorners = mHasRoundedCorners
+                ? mDisplayContent.calculateRoundedCornersForRotation(mRotation)
+                : RoundedCorners.NO_ROUNDED_CORNERS;
         return new DisplayFrames(mDisplayContent.getDisplayId(),
-                insetsState,
-                info.first, info.second);
+                insetsState, info.first, info.second, roundedCorners);
     }
 
     @Test
@@ -664,77 +672,13 @@
     public void layoutHint_appWindow() {
         mWindow.mAttrs.setFitInsetsTypes(0);
 
-        final Rect outFrame = new Rect();
         final DisplayCutout.ParcelableWrapper outDisplayCutout =
                 new DisplayCutout.ParcelableWrapper();
         final InsetsState outState = new InsetsState();
 
-        mDisplayPolicy.getLayoutHint(mWindow.mAttrs, null /* windowToken */, outFrame,
-                outState, true /* localClient */);
-
-        assertThat(outFrame, is(outState.getDisplayFrame()));
-        assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
-        assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(),
-                is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT)));
-        assertThat(outState.getSource(ITYPE_NAVIGATION_BAR).getFrame(),
-                is(new Rect(0, DISPLAY_HEIGHT - NAV_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT)));
-    }
-
-    @Test
-    public void layoutHint_appWindowInTask() {
-        mWindow.mAttrs.setFitInsetsTypes(0);
-
-        final Rect taskBounds = new Rect(100, 100, 200, 200);
-        final Task task = mWindow.getTask();
-        // Force the bounds because the task may resolve different bounds from Task#setBounds.
-        task.getWindowConfiguration().setBounds(taskBounds);
-
-        final Rect outFrame = new Rect();
-        final DisplayCutout.ParcelableWrapper outDisplayCutout =
-                new DisplayCutout.ParcelableWrapper();
-        final InsetsState outState = new InsetsState();
-
-        mDisplayPolicy.getLayoutHint(mWindow.mAttrs, mWindow.mToken, outFrame, outState,
+        mDisplayPolicy.getLayoutHint(mWindow.mAttrs, null /* windowToken */, outState,
                 true /* localClient */);
 
-        assertThat(outFrame, is(taskBounds));
-        assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
-        assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(),
-                is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT)));
-        assertThat(outState.getSource(ITYPE_NAVIGATION_BAR).getFrame(),
-                is(new Rect(0, DISPLAY_HEIGHT - NAV_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT)));
-    }
-
-    @Test
-    public void layoutHint_appWindowInTask_outsideContentFrame() {
-        mWindow.mAttrs.setFitInsetsTypes(0);
-
-        final InsetsState state =
-                mDisplayContent.getInsetsStateController().getRawInsetsState();
-        final Rect contentFrame = new Rect(state.getDisplayFrame());
-        contentFrame.inset(state.calculateInsets(contentFrame, Type.systemBars(),
-                false /* ignoreVisibility */));
-
-        // Task is in the nav bar area (usually does not happen, but this is similar enough to
-        // the possible overlap with the IME)
-        final Rect taskBounds = new Rect(100, contentFrame.bottom + 1,
-                200, contentFrame.bottom + 10);
-
-        final Task task = mWindow.getTask();
-        // Make the task floating.
-        task.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
-        // Force the bounds because the task may resolve different bounds from Task#setBounds.
-        task.getWindowConfiguration().setBounds(taskBounds);
-
-        final Rect outFrame = new Rect();
-        final DisplayCutout.ParcelableWrapper outDisplayCutout =
-                new DisplayCutout.ParcelableWrapper();
-        final InsetsState outState = new InsetsState();
-
-        mDisplayPolicy.getLayoutHint(mWindow.mAttrs, mWindow.mToken, outFrame, outState,
-                true /* localClient */);
-
-        assertThat(outFrame, is(taskBounds));
         assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
         assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(),
                 is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT)));
@@ -753,6 +697,8 @@
         assertSimulateLayoutSameDisplayFrames();
         addDisplayCutout();
         assertSimulateLayoutSameDisplayFrames();
+        addRoundedCorners();
+        assertSimulateLayoutSameDisplayFrames();
     }
 
     private void assertSimulateLayoutSameDisplayFrames() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 499507e..2163661 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -302,7 +302,7 @@
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
         mImeWindow.mAboveInsetsState = state;
         mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
-                state, displayInfo, null /* displayCutout */);
+                state, displayInfo, null /* displayCutout */, null /* roundedCorners*/);
 
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
         mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index e1aca55..2321a73 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -121,6 +121,8 @@
         sMockWm = mock(WindowManagerService.class);
         sMockWm.mPowerManagerInternal = mock(PowerManagerInternal.class);
         sMockWm.mPolicy = mock(WindowManagerPolicy.class);
+        sMockWm.mConstants = mock(WindowManagerConstants.class);
+        sMockWm.mConstants.mRawSensorLoggingEnabled = true;
     }
 
     @AfterClass
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 33e8fc0..3e05c86 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -217,6 +217,7 @@
         SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
         overrideSettings.mShouldShowSystemDecors = true;
         overrideSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL;
+        overrideSettings.mDontMoveToTop = true;
         provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
         assertTrue(mOverrideSettingsStorage.wasWriteSuccessful());
 
@@ -227,6 +228,8 @@
                 getStoredDisplayAttributeValue(mOverrideSettingsStorage, "shouldShowSystemDecors"));
         assertEquals("Attribute value must be stored", "0",
                 getStoredDisplayAttributeValue(mOverrideSettingsStorage, "imePolicy"));
+        assertEquals("Attribute value must be stored", "true",
+                getStoredDisplayAttributeValue(mOverrideSettingsStorage, "dontMoveToTop"));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 70d47a5..8703c31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -16,11 +16,13 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.DragEvent.ACTION_DRAG_STARTED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -34,6 +36,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 
 import android.app.PendingIntent;
@@ -278,6 +281,8 @@
 
     @Test
     public void testValidateAppShortcutArguments() {
+        doReturn(PERMISSION_GRANTED).when(mWm.mContext)
+                .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
         final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
             @Override
             public void onAnimatorScaleChanged(float scale) {}
@@ -329,6 +334,8 @@
 
     @Test
     public void testValidateAppTaskArguments() {
+        doReturn(PERMISSION_GRANTED).when(mWm.mContext)
+                .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
         final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
             @Override
             public void onAnimatorScaleChanged(float scale) {}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index e6f24da..f91c9d0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.content.ActivityInfoProto.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index 032edde..325bca4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -18,15 +18,24 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.platform.test.annotations.Presubmit;
+import android.view.Display.Mode;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,16 +49,40 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class FrameRateSelectionPriorityTests extends WindowTestsBase {
+    private static final float FLOAT_TOLERANCE = 0.01f;
+    private static final int LOW_MODE_ID = 3;
+
+    private DisplayPolicy mDisplayPolicy = mock(DisplayPolicy.class);
+    private RefreshRatePolicy mRefreshRatePolicy;
+    private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+
+    @Before
+    public void setUp() {
+        DisplayInfo di = new DisplayInfo(mDisplayInfo);
+        Mode defaultMode = di.getDefaultMode();
+        di.supportedModes = new Mode[] {
+                new Mode(1, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90),
+                new Mode(2, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70),
+                new Mode(LOW_MODE_ID,
+                        defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60),
+        };
+        di.defaultModeId = 1;
+        mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist);
+        when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy);
+    }
 
     @Test
     public void basicTest() {
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         assertNotNull("Window state is created", appWindow);
+
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority doesn't change.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Call the function a few times.
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -57,7 +90,9 @@
 
         // Since nothing changed in the priority state, the transaction should not be updating.
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
-                appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+                any(SurfaceControl.class), anyInt());
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
     }
 
     @Test
@@ -66,10 +101,16 @@
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
                 .getPreferredModeId(appWindow), 0);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
+        assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+                .getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE);
+
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority stays MAX_VALUE.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
 
@@ -78,31 +119,38 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes to 1.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
     }
 
     @Test
     public void testApplicationInFocusWithModeId() {
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Application is in focus.
         appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 0);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Remove the mode ID request.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Verify we called actions on Transactions correctly.
         verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
@@ -111,12 +159,15 @@
                 appWindow.getSurfaceControl(), 0);
         verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 1);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
     }
 
     @Test
     public void testApplicationNotInFocusWithModeId() {
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
         appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -124,23 +175,28 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // The window is not in focus.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Update the mode ID to a requested number.
         appWindow.mAttrs.preferredDisplayModeId = 1;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority changes.
         assertEquals(appWindow.mFrameRateSelectionPriority, 2);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), 2);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
     }
 
     @Test
     public void testApplicationNotInFocusWithoutModeId() {
         final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
         appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -148,14 +204,45 @@
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // The window is not in focus.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         // Make sure that the mode ID is not set.
         appWindow.mAttrs.preferredDisplayModeId = 0;
         appWindow.updateFrameRateSelectionPriorityIfNeeded();
         // Priority doesn't change.
         assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE);
 
         verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
                 appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+        verify(appWindow.getPendingTransaction(), never()).setFrameRate(
+                any(SurfaceControl.class), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testPreferredRefreshRate() {
+        final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+        assertNotNull("Window state is created", appWindow);
+        when(appWindow.getDisplayContent().getDisplayPolicy()).thenReturn(mDisplayPolicy);
+
+        appWindow.mAttrs.packageName = "com.android.test";
+        when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
+
+        assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
+        assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
+
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
+        assertEquals(60, appWindow.mDenyListFrameRate, FLOAT_TOLERANCE);
+
+        // Call the function a few times.
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+        appWindow.updateFrameRateSelectionPriorityIfNeeded();
+
+        // Since nothing changed in the priority state, the transaction should not be updating.
+        verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+                any(SurfaceControl.class), anyInt());
+        verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
+                appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index f75c98f..78074d6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -26,7 +27,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 
@@ -70,13 +70,15 @@
         spyOn(wms);
         doAnswer(invocation -> {
             Object[] args = invocation.getArguments();
-            final IBinder token = (IBinder) args[0];
-            final int windowType = (int) args[1];
-            new WindowToken(mWm, token, windowType, true /* persistOnEmpty */,
-                    mDefaultDisplay, true /* ownerCanManageAppTokens */, 1000 /* ownerUid */,
-                    false /* roundedCornerOverlay */, true /* fromClientToken */);
-            return WindowManagerGlobal.ADD_OKAY;
-        }).when(wms).addWindowTokenWithOptions(any(), anyInt(), anyInt(), any(), anyString());
+            IBinder clientToken = (IBinder) args[0];
+            int displayId = (int) args[2];
+            DisplayContent dc = mWm.mRoot.getDisplayContent(displayId);
+            mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken,
+                    dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
+                    null /* options */);
+            return true;
+        }).when(wms).registerWindowContextListener(any(), eq(TYPE_INPUT_METHOD_DIALOG),
+                anyInt(), any());
 
         mSecondaryDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1000).build();
 
@@ -95,14 +97,12 @@
 
         assertImeSwitchContextMetricsValidity(contextOnDefaultDisplay, mDefaultDisplay);
 
-        // Obtain the context again and check they are the same instance and match the display
-        // metrics of the secondary display.
+        // Obtain the context again and check if the window metrics match the IME container bounds
+        // of the secondary display.
         final Context contextOnSecondaryDisplay = mController.getSettingsContext(
                 mSecondaryDisplay.getDisplayId());
 
         assertImeSwitchContextMetricsValidity(contextOnSecondaryDisplay, mSecondaryDisplay);
-        assertThat(contextOnDefaultDisplay.getWindowContextToken())
-                .isEqualTo(contextOnSecondaryDisplay.getWindowContextToken());
     }
 
     private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 2107ab1e..ee293fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -86,6 +86,7 @@
 
         assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR));
         assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_NAVIGATION_BAR));
+        assertNull(getController().getInsetsForWindow(app).peekSource(ITYPE_IME));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 2f3004b..7714a6c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -17,6 +17,9 @@
 package com.android.server.wm;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.clearInvocations;
@@ -26,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
@@ -47,10 +51,14 @@
     SurfaceControlMocker mSurfaces;
     SurfaceControl.Transaction mTransaction;
 
+    private boolean mAreCornersRounded = false;
+    private int mColor = Color.BLACK;
+
     @Before
     public void setUp() throws Exception {
         mSurfaces = new SurfaceControlMocker();
-        mLetterbox = new Letterbox(mSurfaces, StubTransaction::new);
+        mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
+                () -> mAreCornersRounded, () -> Color.valueOf(mColor));
         mTransaction = spy(StubTransaction.class);
     }
 
@@ -64,6 +72,7 @@
     private static final int BOTTOM_BAR = 0x2;
     private static final int LEFT_BAR = 0x4;
     private static final int RIGHT_BAR = 0x8;
+
     @Test
     public void testNotIntersectsOrFullyContains_usesGlobalCoordinates() {
         final Rect outer = new Rect(0, 0, 10, 50);
@@ -165,6 +174,57 @@
     }
 
     @Test
+    public void testApplySurfaceChanges_setColor() {
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+        mLetterbox.applySurfaceChanges(mTransaction);
+
+        verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 0, 0});
+
+        mColor = Color.GREEN;
+
+        assertTrue(mLetterbox.needsApplySurfaceChanges());
+
+        mLetterbox.applySurfaceChanges(mTransaction);
+
+        verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 1, 0});
+    }
+
+    @Test
+    public void testApplySurfaceChanges_cornersNotRounded_surfaceBehindNotCreated() {
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+        mLetterbox.applySurfaceChanges(mTransaction);
+
+        assertNull(mSurfaces.behind);
+    }
+
+    @Test
+    public void testApplySurfaceChanges_cornersRounded_surfaceBehindCreated() {
+        mAreCornersRounded = true;
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+        mLetterbox.applySurfaceChanges(mTransaction);
+
+        assertNotNull(mSurfaces.behind);
+    }
+
+    @Test
+    public void testIsOverlappingWith_cornersRounded_doesNotCheckSurfaceBehind() {
+        mAreCornersRounded = true;
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
+        mLetterbox.applySurfaceChanges(mTransaction);
+
+        assertFalse(mLetterbox.isOverlappingWith(new Rect(1, 2, 9, 9)));
+    }
+
+    @Test
+    public void testNotIntersectsOrFullyContains_cornersRounded_doesNotCheckSurfaceBehind() {
+        mAreCornersRounded = true;
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
+        mLetterbox.applySurfaceChanges(mTransaction);
+
+        assertTrue(mLetterbox.notIntersectsOrFullyContains(new Rect(1, 2, 9, 9)));
+    }
+
+    @Test
     public void testSurfaceOrigin_changeCausesReapply() {
         mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
         mLetterbox.applySurfaceChanges(mTransaction);
@@ -184,6 +244,8 @@
         public SurfaceControl right;
         private SurfaceControl.Builder mBottomBuilder;
         public SurfaceControl bottom;
+        private SurfaceControl.Builder mBehindBuilder;
+        public SurfaceControl behind;
 
         @Override
         public SurfaceControl.Builder get() {
@@ -198,6 +260,8 @@
                     mRightBuilder = (SurfaceControl.Builder) i.getMock();
                 } else if (((String) i.getArgument(0)).contains("bottom")) {
                     mBottomBuilder = (SurfaceControl.Builder) i.getMock();
+                } else if (((String) i.getArgument(0)).contains("behind")) {
+                    mBehindBuilder = (SurfaceControl.Builder) i.getMock();
                 }
                 return i.getMock();
             });
@@ -212,6 +276,8 @@
                     right = control;
                 } else if (i.getMock() == mBottomBuilder) {
                     bottom = control;
+                } else if (i.getMock() == mBehindBuilder) {
+                    behind = control;
                 }
                 return control;
             }).when(builder).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 673b00f2..21fd04e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -45,7 +45,6 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -58,7 +57,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
-import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
@@ -1109,28 +1107,6 @@
         assertEquals(originalStackCount, mTaskContainer.getRootTaskCount());
     }
 
-    @Test
-    public void testNotRecentsComponent_denyApiAccess() throws Exception {
-        doReturn(PackageManager.PERMISSION_DENIED).when(mAtm)
-                .checkGetTasksPermission(anyString(), anyInt(), anyInt());
-        // Expect the following methods to fail due to recents component not being set
-        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
-        doTestRecentTasksApis(false /* expectNoSecurityException */);
-        // Don't throw for the following tests
-        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY);
-        testGetTasksApis(false /* expectNoSecurityException */);
-    }
-
-    @Test
-    public void testRecentsComponent_allowApiAccessWithoutPermissions() {
-        doReturn(PackageManager.PERMISSION_DENIED).when(mAtm)
-                .checkGetTasksPermission(anyString(), anyInt(), anyInt());
-        // Set the recents component and ensure that the following calls do not fail
-        mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT);
-        doTestRecentTasksApis(true /* expectNoSecurityException */);
-        testGetTasksApis(true /* expectNoSecurityException */);
-    }
-
     private void doTestRecentTasksApis(boolean expectCallable) {
         assertSecurityException(expectCallable, () -> mAtm.removeTask(INVALID_STACK_ID));
         assertSecurityException(expectCallable,
@@ -1295,13 +1271,7 @@
     }
 
     private static class TestRecentTasks extends RecentTasks {
-        static final int GRANT = 0;
-        static final int DENY = 1;
-        static final int DENY_THROW_SECURITY_EXCEPTION = 2;
-
-        private boolean mOverrideIsCallerRecents;
         private boolean mIsTrimmableOverride;
-        private int mIsCallerRecentsPolicy;
 
         public boolean mLastAllowed;
 
@@ -1334,26 +1304,6 @@
             return new int[] { TEST_USER_0_ID, TEST_QUIET_USER_ID };
         }
 
-        @Override
-        boolean isCallerRecents(int callingUid) {
-            if (mOverrideIsCallerRecents) {
-                switch (mIsCallerRecentsPolicy) {
-                    case GRANT:
-                        return true;
-                    case DENY:
-                        return false;
-                    case DENY_THROW_SECURITY_EXCEPTION:
-                        throw new SecurityException();
-                }
-            }
-            return super.isCallerRecents(callingUid);
-        }
-
-        void setIsCallerRecentsOverride(int policy) {
-            mOverrideIsCallerRecents = true;
-            mIsCallerRecentsPolicy = policy;
-        }
-
         /**
          * To simplify the setup for some tests, the caller can request that we only rely on the
          * visible range test to determine what is trimmable. In this case, we don't try to
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 409bad4..c6be987 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -493,7 +493,7 @@
         initializeRecentsAnimationController(mController, homeActivity);
 
         // Verify RecentsAnimationController will animate visible leaf task by default.
-        verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), eq(null));
+        verify(mController).addAnimation(eq(leafTask), anyBoolean(), anyBoolean(), any());
         assertTrue(leafTask.isAnimatingByRecents());
 
         // Make sure isAnimatingByRecents will also return true when it called by the parent task.
@@ -543,6 +543,35 @@
         verify(navBarFadeAnimationController, never()).fadeWindowToken(anyBoolean());
     }
 
+    @Test
+    public void testCleanupAnimation_expectExitAnimationDone() {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityRecord homeActivity = createHomeActivity();
+        final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1");
+        activity.addWindow(win1);
+
+        initializeRecentsAnimationController(mController, homeActivity);
+        mController.startAnimation();
+
+        spyOn(win1);
+        spyOn(win1.mWinAnimator);
+        // Simulate when the window is exiting and cleanupAnimation invoked
+        // (e.g. screen off during RecentsAnimation animating), will expect the window receives
+        // onExitAnimationDone to destroy the surface when the removal is allowed.
+        win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+        win1.mHasSurface = true;
+        win1.mAnimatingExit = true;
+        win1.mRemoveOnExit = true;
+        win1.mWindowRemovalAllowed = true;
+        mController.cleanupAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+        verify(win1).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), any());
+        verify(win1).onExitAnimationDone();
+        verify(win1).destroySurface(eq(false), eq(false));
+        assertFalse(win1.mAnimatingExit);
+        assertFalse(win1.mHasSurface);
+    }
+
     private ActivityRecord createHomeActivity() {
         final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
                 .setParentTask(mRootHomeTask)
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 77a4b05..ef3c7ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -44,7 +44,7 @@
 @RunWith(WindowTestRunner.class)
 @FlakyTest
 public class RefreshRatePolicyTest extends WindowTestsBase {
-
+    private static final float FLOAT_TOLERANCE = 0.01f;
     private static final int LOW_MODE_ID = 3;
 
     private RefreshRatePolicy mPolicy;
@@ -70,28 +70,34 @@
                 "cameraUsingWindow");
         cameraUsingWindow.mAttrs.packageName = "com.android.test";
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeNonHighRefreshRatePackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
 
     @Test
-    public void testBlacklist() {
-        final WindowState blacklistedWindow = createWindow(null, TYPE_BASE_APPLICATION,
-                "blacklistedWindow");
-        blacklistedWindow.mAttrs.packageName = "com.android.test";
+    public void testDenyList() {
+        final WindowState denylistedWindow = createWindow(null, TYPE_BASE_APPLICATION,
+                "denylistedWindow");
+        denylistedWindow.mAttrs.packageName = "com.android.test";
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
-        assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(blacklistedWindow));
+        assertEquals(0, mPolicy.getPreferredModeId(denylistedWindow));
+        assertEquals(60, mPolicy.getPreferredRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
     }
 
     @Test
     public void testAppOverride_blacklist() {
         final WindowState overrideWindow = createWindow(null, TYPE_BASE_APPLICATION,
                 "overrideWindow");
+        overrideWindow.mAttrs.packageName = "com.android.test";
         overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
         when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
+        assertEquals(60, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -102,6 +108,7 @@
         overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -115,6 +122,7 @@
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
 
     @Test
@@ -125,10 +133,12 @@
 
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
 
         cameraUsingWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
                 cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class),
                 false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
+        assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 2efd4b5..15e045c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -17,6 +17,9 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -100,15 +103,18 @@
                     new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
             adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
                     mFinishedCallback);
-            mController.goodToGo();
+            mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonApsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonApsCaptor.capture(),
                     finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
@@ -136,7 +142,7 @@
                 new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
         adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
                 mFinishedCallback);
-        mController.goodToGo();
+        mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
 
         adapter.onAnimationCancelled(mMockLeash);
         verify(mMockRunner).onAnimationCancelled();
@@ -149,7 +155,7 @@
                 new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
         adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
                 mFinishedCallback);
-        mController.goodToGo();
+        mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
 
         mClock.fastForward(2500);
         mHandler.timeAdvance();
@@ -170,7 +176,7 @@
                     null).mAdapter;
             adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
                     mFinishedCallback);
-            mController.goodToGo();
+            mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
 
             mClock.fastForward(2500);
             mHandler.timeAdvance();
@@ -190,7 +196,7 @@
 
     @Test
     public void testZeroAnimations() {
-        mController.goodToGo();
+        mController.goodToGo(TRANSIT_OLD_NONE);
         verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
     }
 
@@ -199,7 +205,7 @@
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         mController.createRemoteAnimationRecord(win.mActivityRecord,
                 new Point(50, 100), null, new Rect(50, 100, 150, 150), null);
-        mController.goodToGo();
+        mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
         verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
     }
 
@@ -213,15 +219,18 @@
                 new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
         adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
                 mFinishedCallback);
-        mController.goodToGo();
+        mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
         mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
         final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                 ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
         final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
                 ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+        final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+                ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
         final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                 ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-        verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+        verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
+                appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
                 finishedCaptor.capture());
         assertEquals(1, appsCaptor.getValue().length);
         assertEquals(mMockLeash, appsCaptor.getValue()[0].leash);
@@ -235,7 +244,7 @@
         adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
                 mFinishedCallback);
         win.mActivityRecord.removeImmediately();
-        mController.goodToGo();
+        mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
         verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
         verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
                 eq(adapter));
@@ -255,15 +264,18 @@
                             mFinishedCallback);
             ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
                     mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
-            mController.goodToGo();
+            mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
                     finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
@@ -305,15 +317,18 @@
                             mFinishedCallback);
             ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
                     mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
-            mController.goodToGo();
+            mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
                     finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
@@ -354,15 +369,18 @@
                     new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
             adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
                     mFinishedCallback);
-            mController.goodToGo();
+            mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
                     finishedCaptor.capture());
             assertEquals(1, wallpapersCaptor.getValue().length);
         } finally {
@@ -383,15 +401,18 @@
                     new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
             adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
                     mFinishedCallback);
-            mController.goodToGo();
+            mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> nonAPpsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+            verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
+                    appsCaptor.capture(), wallpapersCaptor.capture(), nonAPpsCaptor.capture(),
                     finishedCaptor.capture());
             assertEquals(1, wallpapersCaptor.getValue().length);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index db77324..cc4d4ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,15 +16,17 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
-import static android.view.SurfaceProto.ROTATION_180;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -35,6 +37,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.Task.ActivityState.STOPPED;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -49,14 +52,12 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.TaskStackListener;
 import android.app.WindowConfiguration;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
 
@@ -69,8 +70,6 @@
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-
 /**
  * Tests for Size Compatibility mode.
  *
@@ -109,7 +108,7 @@
         final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
         resizeDisplay(mTask.mDisplayContent, 600, 1200);
         // The visible activity should recompute configuration according to the last parent bounds.
-        mAtm.restartActivityProcessIfVisible(mActivity.appToken);
+        mAtm.mActivityClientController.restartActivityProcessIfVisible(mActivity.appToken);
 
         assertEquals(Task.ActivityState.RESTARTING_PROCESS, mActivity.getState());
         assertNotEquals(originalOverrideBounds, mActivity.getBounds());
@@ -476,46 +475,43 @@
     }
 
     /**
-     * Ensures that {@link TaskStackListener} can receive callback about the activity in size
+     * Ensures that {@link TaskOrganizerController} can receive callback about the activity in size
      * compatibility mode.
      */
     @Test
-    public void testHandleActivitySizeCompatMode() {
+    public void testHandleActivitySizeCompatModeChanged() {
         setUpDisplaySizeWithApp(1000, 2000);
+        doReturn(true).when(mTask).isOrganized();
         ActivityRecord activity = mActivity;
-        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
+        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
         prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
         assertFitted();
 
-        final ArrayList<IBinder> compatTokens = new ArrayList<>();
-        mAtm.getTaskChangeNotificationController().registerTaskStackListener(
-                new TaskStackListener() {
-                    @Override
-                    public void onSizeCompatModeActivityChanged(int displayId,
-                            IBinder activityToken) {
-                        compatTokens.add(activityToken);
-                    }
-                });
-
         // Resize the display so that the activity exercises size-compat mode.
         resizeDisplay(mTask.mDisplayContent, 1000, 2500);
 
         // Expect the exact token when the activity is in size compatibility mode.
-        assertEquals(1, compatTokens.size());
-        assertEquals(activity.appToken, compatTokens.get(0));
+        verify(mTask).onSizeCompatActivityChanged();
+        ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo();
 
-        compatTokens.clear();
+        assertEquals(mActivity.appToken, taskInfo.topActivityToken);
+        assertTrue(taskInfo.topActivityInSizeCompat);
+
         // Make the activity resizable again by restarting it
+        clearInvocations(mTask);
         activity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
         activity.mVisibleRequested = true;
         activity.restartProcessIfVisible();
         // The full lifecycle isn't hooked up so manually set state to resumed
-        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatMode");
+        activity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
         mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(activity);
 
         // Expect null token when switching to non-size-compat mode activity.
-        assertEquals(1, compatTokens.size());
-        assertEquals(null, compatTokens.get(0));
+        verify(mTask).onSizeCompatActivityChanged();
+        taskInfo = mTask.getTaskInfo();
+
+        assertEquals(mActivity.appToken, taskInfo.topActivityToken);
+        assertFalse(taskInfo.topActivityInSizeCompat);
     }
 
     @Test
@@ -600,12 +596,11 @@
         assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
                 mActivity.getLetterboxInsets());
 
-        final BarController statusBarController =
-                mActivity.mDisplayContent.getDisplayPolicy().getStatusBarController();
+        final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy();
         // The activity doesn't fill the display, so the letterbox of the rotated activity is
         // overlapped with the rotated content frame of status bar. Hence the status bar shouldn't
         // be transparent.
-        assertFalse(statusBarController.isTransparentAllowed(w));
+        assertFalse(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR));
 
         // Make the activity fill the display.
         prepareUnresizable(mActivity, 10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
@@ -615,7 +610,7 @@
 
         // The letterbox should only cover the notch area, so status bar can be transparent.
         assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets());
-        assertTrue(statusBarController.isTransparentAllowed(w));
+        assertTrue(displayPolicy.isFullyTransparentAllowed(w, TYPE_STATUS_BAR));
     }
 
     @Test
@@ -905,6 +900,57 @@
         assertEquals(1000, activityBounds.height());
     }
 
+    @Test
+    public void testSupportsNonResizableInSplitScreen() {
+        // Support non resizable in multi window
+        mAtm.mSupportsNonResizableMultiWindow = true;
+        setUpDisplaySizeWithApp(1000, 2800);
+        final TestSplitOrganizer organizer =
+                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+        // Non-resizable landscape activity
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+        final Rect originalBounds = new Rect(mActivity.getBounds());
+
+        // Move activity to split screen
+        mTask.reparent(organizer.mPrimary, POSITION_TOP,
+                false /*moveParents*/, "test");
+        assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mTask.getWindowingMode());
+        assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mActivity.getWindowingMode());
+
+        // Non-resizable activity in size compat mode
+        assertScaled();
+        assertEquals(originalBounds,
+                mActivity.getConfiguration().windowConfiguration.getBounds());
+
+        // Recompute the natural configuration of the non-resizable activity and the split screen.
+        mActivity.clearSizeCompatMode();
+
+        // Draw letterbox.
+        mActivity.setVisible(false);
+        mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
+        mActivity.mDisplayContent.mOpeningApps.add(mActivity);
+        addWindowToActivity(mActivity);
+        mActivity.mRootWindowContainer.performSurfacePlacement();
+
+        // Split screen is also in portrait [1000,1400], so Task should be in letterbox, and
+        // activity fills task.
+        assertEquals(ORIENTATION_LANDSCAPE, mTask.getConfiguration().orientation);
+        assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation);
+        assertFitted();
+        assertTrue(mTask.isTaskLetterboxed());
+
+        // Letterbox should fill the gap between the split screen and the letterboxed task.
+        final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds());
+        final Rect letterboxedTaskBounds = new Rect(mTask.getBounds());
+        assertTrue(primarySplitBounds.contains(letterboxedTaskBounds));
+        assertEquals(new Rect(letterboxedTaskBounds.left - primarySplitBounds.left,
+                letterboxedTaskBounds.top - primarySplitBounds.top,
+                primarySplitBounds.right - letterboxedTaskBounds.right,
+                primarySplitBounds.bottom - letterboxedTaskBounds.bottom),
+                mActivity.getLetterboxInsets());
+    }
+
     private static WindowState addWindowToActivity(ActivityRecord activity) {
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
         params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -924,9 +970,9 @@
         displayPolicy.onConfigurationChanged();
 
         final TestWindowToken token = createTestWindowToken(
-                WindowManager.LayoutParams.TYPE_STATUS_BAR, displayContent);
+                TYPE_STATUS_BAR, displayContent);
         final WindowManager.LayoutParams attrs =
-                new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_STATUS_BAR);
+                new WindowManager.LayoutParams(TYPE_STATUS_BAR);
         attrs.gravity = android.view.Gravity.TOP;
         attrs.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index c308fdb..6c72249 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -152,12 +152,6 @@
     }
 
     @Override
-    public SurfaceControl.Transaction reparentChildren(SurfaceControl sc,
-            SurfaceControl newParent) {
-        return this;
-    }
-
-    @Override
     public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) {
         return this;
     }
@@ -245,6 +239,12 @@
     }
 
     @Override
+    public SurfaceControl.Transaction setFrameRate(SurfaceControl sc, float frameRate,
+            int compatibility) {
+        return this;
+    }
+
+    @Override
     public SurfaceControl.Transaction unsetColor(SurfaceControl sc) {
         return this;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index d83e9c2..a3ade51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -156,6 +156,9 @@
         spyOn(mDisplayContent);
         doReturn(true).when(mDisplayContent).isTrusted();
 
+        // Allow child stack to move to top.
+        mDisplayContent.mDontMoveToTop = false;
+
         // The display contains pinned stack that was added in {@link #setUp}.
         final Task stack = createTaskStackOnDisplay(mDisplayContent);
         final Task task = createTaskInStack(stack, 0 /* userId */);
@@ -173,6 +176,65 @@
     }
 
     @Test
+    public void testMovingChildTaskOnTop() {
+        // Make sure the display is trusted display which capable to move the stack to top.
+        spyOn(mDisplayContent);
+        doReturn(true).when(mDisplayContent).isTrusted();
+
+        // Allow child stack to move to top.
+        mDisplayContent.mDontMoveToTop = false;
+
+        // The display contains pinned stack that was added in {@link #setUp}.
+        Task stack = createTaskStackOnDisplay(mDisplayContent);
+        Task task = createTaskInStack(stack, 0 /* userId */);
+
+        // Add another display at top.
+        mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(),
+                false /* includingParents */);
+
+        // Ensure that original display ({@code mDisplayContent}) is not on top.
+        assertEquals("Testing DisplayContent should not be on the top",
+                mWm.mRoot.getChildCount() - 2, mWm.mRoot.mChildren.indexOf(mDisplayContent));
+
+        // Move the task of {@code mDisplayContent} to top.
+        stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
+
+        // Ensure that original display ({@code mDisplayContent}) is now on the top.
+        assertEquals("The testing DisplayContent should be moved to top with task",
+                mWm.mRoot.getChildCount() - 1, mWm.mRoot.mChildren.indexOf(mDisplayContent));
+    }
+
+    @Test
+    public void testDontMovingChildTaskOnTop() {
+        // Make sure the display is trusted display which capable to move the stack to top.
+        spyOn(mDisplayContent);
+        doReturn(true).when(mDisplayContent).isTrusted();
+
+        // Allow child stack to move to top.
+        mDisplayContent.mDontMoveToTop = true;
+
+        // The display contains pinned stack that was added in {@link #setUp}.
+        Task stack = createTaskStackOnDisplay(mDisplayContent);
+        Task task = createTaskInStack(stack, 0 /* userId */);
+
+        // Add another display at top.
+        mWm.mRoot.positionChildAt(WindowContainer.POSITION_TOP, createNewDisplay(),
+                false /* includingParents */);
+
+        // Ensure that original display ({@code mDisplayContent}) is not on top.
+        assertEquals("Testing DisplayContent should not be on the top",
+                mWm.mRoot.getChildCount() - 2, mWm.mRoot.mChildren.indexOf(mDisplayContent));
+
+        // Try moving the task of {@code mDisplayContent} to top.
+        stack.positionChildAt(WindowContainer.POSITION_TOP, task, true /* includingParents */);
+
+        // Ensure that original display ({@code mDisplayContent}) hasn't moved and is not
+        // on the top.
+        assertEquals("The testing DisplayContent should not be moved to top with task",
+                mWm.mRoot.getChildCount() - 2, mWm.mRoot.mChildren.indexOf(mDisplayContent));
+    }
+
+    @Test
     public void testReuseTaskAsRootTask() {
         final Task candidateTask = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index f20513d..0eb8c8d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -1004,6 +1004,26 @@
     }
 
     @Test
+    public void testNotSaveLaunchingStateForNonLeafTask() {
+        LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
+        spyOn(persister);
+
+        final Task task = getTestTask();
+        task.setHasBeenVisible(false);
+        task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+        task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        final Task leafTask = createTaskInStack(task, 0 /* userId */);
+
+        leafTask.setHasBeenVisible(true);
+        task.setHasBeenVisible(true);
+        task.onConfigurationChanged(task.getParent().getConfiguration());
+
+        verify(persister, never()).saveTask(same(task), any());
+        verify(persister).saveTask(same(leafTask), any());
+    }
+
+    @Test
     public void testNotSpecifyOrientationByFloatingTask() {
         final Task task = new TaskBuilder(mSupervisor)
                 .setCreateActivity(true).setCreateParentTask(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index d49956a..9372530 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -145,7 +145,7 @@
 
         assertThat(surface).isNotNull();
         verify(session).addToDisplay(any(), argThat(this::isTrustedOverlay), anyInt(), anyInt(),
-                any(), any(), any(), any(), any());
+                any(), any(), any(), any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 53cea5a..d1d0ac6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -47,6 +47,7 @@
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.InsetsState;
+import android.view.RoundedCorners;
 import android.view.Surface;
 import android.view.WindowManager;
 
@@ -146,7 +147,7 @@
         final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0);
         final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
         final DisplayFrames displayFrames = new DisplayFrames(dc.getDisplayId(), new InsetsState(),
-                info, cutout);
+                info, cutout, RoundedCorners.NO_ROUNDED_CORNERS);
         wallpaperWindowToken.applyFixedRotationTransform(info, displayFrames, config);
 
         // Check that the wallpaper has the same frame in landscape than in portrait
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index b0b8afd..99c96bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,6 +22,8 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
@@ -64,6 +66,7 @@
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.view.WindowManager;
 
 import androidx.test.filters.SmallTest;
 
@@ -903,8 +906,10 @@
         final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
                 new IRemoteAnimationRunner.Stub() {
                     @Override
-                    public void onAnimationStart(RemoteAnimationTarget[] apps,
+                    public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+                            RemoteAnimationTarget[] apps,
                             RemoteAnimationTarget[] wallpapers,
+                            RemoteAnimationTarget[] nonApps,
                             IRemoteAnimationFinishedCallback finishedCallback) {
                         try {
                             finishedCallback.onAnimationFinished();
@@ -978,6 +983,31 @@
         assertEquals(200, listener.mConfiguration.densityDpi);
     }
 
+    @Test
+    public void testFreezeInsetsStateWhenAppTransition() {
+        final Task stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+        final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+        task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
+        spyOn(win);
+        doReturn(true).when(task).okToAnimate();
+        ArrayList<WindowContainer> sources = new ArrayList<>();
+        sources.add(activity);
+
+        // Simulate the task applying the exit transition, verify the main window of the task
+        // will be set the frozen insets state.
+        task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
+                false /* isVoiceInteraction */, sources);
+        verify(win).freezeInsetsState();
+
+        // Simulate the task transition finished, verify the frozen insets state of the window
+        // will be reset.
+        task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
+                task.mSurfaceAnimator.getAnimation());
+        verify(win).clearFrozenInsetsState();
+    }
+
     /* Used so we can gain access to some protected members of the {@link WindowContainer} class */
     private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
         private final int mLayer;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index f8a89c6..6d0e510 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -20,7 +20,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.os.Process.INVALID_UID;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
@@ -37,12 +36,10 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.content.pm.PackageManager;
 import android.os.IBinder;
@@ -75,7 +72,7 @@
     @Test
     public void testAddWindowToken() {
         IBinder token = mock(IBinder.class);
-        mWm.addWindowToken(token, TYPE_TOAST, mDisplayContent.getDisplayId());
+        mWm.addWindowToken(token, TYPE_TOAST, mDisplayContent.getDisplayId(), null /* options */);
 
         WindowToken windowToken = mWm.mRoot.getWindowToken(token);
         assertFalse(windowToken.mRoundedCornerOverlay);
@@ -83,32 +80,6 @@
     }
 
     @Test
-    public void testAddWindowTokenWithOptions() {
-        IBinder token = mock(IBinder.class);
-        mWm.addWindowTokenWithOptions(token, TYPE_TOAST, mDisplayContent.getDisplayId(),
-                null /* options */, null /* options */);
-
-        WindowToken windowToken = mWm.mRoot.getWindowToken(token);
-        assertFalse(windowToken.mRoundedCornerOverlay);
-        assertTrue(windowToken.isFromClient());
-    }
-
-    @Test(expected = SecurityException.class)
-    public void testRemoveWindowToken_ownerUidNotMatch_throwException() {
-        IBinder token = mock(IBinder.class);
-        mWm.addWindowTokenWithOptions(token, TYPE_TOAST, mDisplayContent.getDisplayId(),
-                null /* options */, null /* options */);
-
-        spyOn(mWm);
-        when(mWm.checkCallingPermission(anyString(), anyString())).thenReturn(false);
-        WindowToken windowToken = mWm.mRoot.getWindowToken(token);
-        spyOn(windowToken);
-        when(windowToken.getOwnerUid()).thenReturn(INVALID_UID);
-
-        mWm.removeWindowToken(token, mDisplayContent.getDisplayId());
-    }
-
-    @Test
     public void testTaskFocusChange_stackNotHomeType_focusChanges() throws RemoteException {
         DisplayContent display = createNewDisplay();
         // Current focused window
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 37983b4..77fca3d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -42,6 +42,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
+import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
 
@@ -55,6 +56,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -1244,6 +1246,54 @@
         assertEquals(splitPrimaryRootTask, activity.getRootTask());
     }
 
+    @Test
+    public void testSizeCompatModeChangedOnFirstOrganizedTask() throws RemoteException {
+        final ITaskOrganizer organizer = registerMockOrganizer();
+        final Task rootTask = createStack();
+        final Task task = createTask(rootTask);
+        final ActivityRecord activity = createActivityRecord(rootTask.mDisplayContent, task);
+        final ArgumentCaptor<RunningTaskInfo> infoCaptor =
+                ArgumentCaptor.forClass(RunningTaskInfo.class);
+
+        assertTrue(rootTask.isOrganized());
+
+        spyOn(activity);
+        doReturn(true).when(activity).inSizeCompatMode();
+        doReturn(true).when(activity).isState(RESUMED);
+
+        // Ensure task info show top activity in size compat.
+        rootTask.onSizeCompatActivityChanged();
+        mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+        verify(organizer).onTaskInfoChanged(infoCaptor.capture());
+        RunningTaskInfo info = infoCaptor.getValue();
+        assertEquals(rootTask.mTaskId, info.taskId);
+        assertEquals(activity.appToken, info.topActivityToken);
+        assertTrue(info.topActivityInSizeCompat);
+
+        // Ensure task info show top activity that is not in foreground as not in size compat.
+        clearInvocations(organizer);
+        doReturn(false).when(activity).isState(RESUMED);
+        rootTask.onSizeCompatActivityChanged();
+        mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+        verify(organizer).onTaskInfoChanged(infoCaptor.capture());
+        info = infoCaptor.getValue();
+        assertEquals(rootTask.mTaskId, info.taskId);
+        assertEquals(activity.appToken, info.topActivityToken);
+        assertFalse(info.topActivityInSizeCompat);
+
+        // Ensure task info show non size compat top activity as not in size compat.
+        clearInvocations(organizer);
+        doReturn(true).when(activity).isState(RESUMED);
+        doReturn(false).when(activity).inSizeCompatMode();
+        rootTask.onSizeCompatActivityChanged();
+        mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+        verify(organizer).onTaskInfoChanged(infoCaptor.capture());
+        info = infoCaptor.getValue();
+        assertEquals(rootTask.mTaskId, info.taskId);
+        assertEquals(activity.appToken, info.topActivityToken);
+        assertFalse(info.topActivityInSizeCompat);
+    }
+
     /**
      * Verifies that task vanished is called for a specific task.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 263aa19..3231f8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -810,4 +810,27 @@
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
     }
+
+    @Test
+    public void testSetFreezeInsetsState() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+        spyOn(app);
+        doReturn(true).when(app).isVisible();
+
+        // Set freezing the insets state to make the window ignore to dispatch insets changed.
+        final InsetsState expectedState = new InsetsState(app.getInsetsState(),
+                true /* copySources */);
+        app.freezeInsetsState();
+        assertEquals(expectedState, app.getFrozenInsetsState());
+        assertFalse(app.isReadyToDispatchInsetsState());
+        assertEquals(expectedState, app.getInsetsState());
+        mDisplayContent.getInsetsStateController().notifyInsetsChanged();
+        verify(app, never()).notifyInsetsChanged();
+
+        // Unfreeze the insets state to make the window can dispatch insets changed.
+        app.clearFrozenInsetsState();
+        assertTrue(app.isReadyToDispatchInsetsState());
+        mDisplayContent.getInsetsStateController().notifyInsetsChanged();
+        verify(app).notifyInsetsChanged();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index c85991d..3492d90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -88,8 +88,8 @@
 import android.window.ITaskOrganizer;
 import android.window.StartingWindowInfo;
 
+import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.AttributeCache;
 
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -340,7 +340,7 @@
 
         final WindowState w = new WindowState(service, session, iWindow, token, parent,
                 OP_NONE, attrs, VISIBLE, ownerId, userId,
-                ownerCanAddInternalSystemWindow, false /* ownerCanUseBackgroundBlur */,
+                ownerCanAddInternalSystemWindow,
                 powerManagerWrapper);
         // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
         // adding it to the token...
@@ -1213,8 +1213,7 @@
         TestWindowState(WindowManagerService service, Session session, IWindow window,
                 WindowManager.LayoutParams attrs, WindowToken token) {
             super(service, session, window, token, null, OP_NONE, attrs, 0, 0, 0,
-                    false /* ownerCanAddInternalSystemWindow */,
-                    false /* ownerCanUseBackgroundBlur */);
+                    false /* ownerCanAddInternalSystemWindow */);
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 2d27331..e2585e5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.os.Process.INVALID_UID;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -201,7 +200,7 @@
 
         token = new WindowToken(mDisplayContent.mWmService, mock(IBinder.class), TYPE_TOAST,
                 true /* persistOnEmpty */, mDisplayContent, true /* ownerCanManageAppTokens */,
-                INVALID_UID, true /* roundedCornerOverlay */, true /* fromClientToken */);
+                true /* roundedCornerOverlay */, true /* fromClientToken */, null /* options */);
         assertTrue(token.mRoundedCornerOverlay);
         assertTrue(token.isFromClient());
     }
@@ -215,8 +214,8 @@
     public void testSurfaceCreatedForWindowToken() {
         final WindowToken fromClientToken = new WindowToken(mDisplayContent.mWmService,
                 mock(IBinder.class), TYPE_APPLICATION_OVERLAY, true /* persistOnEmpty */,
-                mDisplayContent, true /* ownerCanManageAppTokens */, INVALID_UID,
-                true /* roundedCornerOverlay */, true /* fromClientToken */);
+                mDisplayContent, true /* ownerCanManageAppTokens */,
+                true /* roundedCornerOverlay */, true /* fromClientToken */, null /* options */);
         assertNull(fromClientToken.mSurfaceControl);
 
         createWindow(null, TYPE_APPLICATION_OVERLAY, fromClientToken, "window");
@@ -224,8 +223,8 @@
 
         final WindowToken nonClientToken = new WindowToken(mDisplayContent.mWmService,
                 mock(IBinder.class), TYPE_TOAST, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, true /* roundedCornerOverlay */,
-                false /* fromClientToken */);
+                true /* ownerCanManageAppTokens */, true /* roundedCornerOverlay */,
+                false /* fromClientToken */, null /* options */);
         assertNotNull(nonClientToken.mSurfaceControl);
     }
 
@@ -238,7 +237,7 @@
 
         final WindowToken token1 = new WindowToken(mDisplayContent.mWmService, mock(IBinder.class),
                 TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, true /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, true /* roundedCornerOverlay */,
                 false /* fromClientToken */, null /* options */);
 
         verify(selectFunc).apply(token1.windowType, null);
@@ -246,7 +245,7 @@
         final Bundle options = new Bundle();
         final WindowToken token2 = new WindowToken(mDisplayContent.mWmService, mock(IBinder.class),
                 TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, INVALID_UID, true /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, true /* roundedCornerOverlay */,
                 false /* fromClientToken */, options /* options */);
 
         verify(selectFunc).apply(token2.windowType, options);
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
index 39976a5..b2646f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
@@ -150,9 +150,9 @@
     @Test
     public void computeSafeInsets_waterfall() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT},
-                        Insets.of(1, 2, 3, 4)),
+                        Insets.of(1, 2, 3, 4), null),
                 200, 400);
 
         assertEquals(new Rect(1, 2, 3, 4), cutout.getDisplayCutout().getSafeInsets());
@@ -161,9 +161,9 @@
     @Test
     public void computeSafeInsets_cutoutTop_greaterThan_waterfallTop() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT},
-                        Insets.of(0, 20, 0, 0)),
+                        Insets.of(0, 20, 0, 0), null),
                 200, 400);
 
         assertEquals(new Rect(0, 30, 0, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -172,9 +172,9 @@
     @Test
     public void computeSafeInsets_cutoutTop_lessThan_waterfallTop() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT},
-                        Insets.of(0, 40, 0, 0)),
+                        Insets.of(0, 40, 0, 0), null),
                 200, 400);
 
         assertEquals(new Rect(0, 40, 0, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -183,9 +183,9 @@
     @Test
     public void computeSafeInsets_cutoutLeft_greaterThan_waterfallLeft() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT},
-                        Insets.of(20, 0, 0, 0)),
+                        Insets.of(20, 0, 0, 0), null),
                 200, 400);
 
         assertEquals(new Rect(30, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -194,9 +194,9 @@
     @Test
     public void computeSafeInsets_cutoutLeft_lessThan_waterfallLeft() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT},
-                        Insets.of(40, 0, 0, 0)),
+                        Insets.of(40, 0, 0, 0), null),
                 200, 400);
 
         assertEquals(new Rect(40, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -205,9 +205,9 @@
     @Test
     public void computeSafeInsets_cutoutBottom_greaterThan_waterfallBottom() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)},
-                        Insets.of(0, 0, 0, 20)),
+                        Insets.of(0, 0, 0, 20), null),
                 200, 400);
 
         assertEquals(new Rect(0, 0, 0, 30), cutout.getDisplayCutout().getSafeInsets());
@@ -216,9 +216,9 @@
     @Test
     public void computeSafeInsets_cutoutBottom_lessThan_waterfallBottom() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)},
-                        Insets.of(0, 0, 0, 40)),
+                        Insets.of(0, 0, 0, 40), null),
                 200, 400);
 
         assertEquals(new Rect(0, 0, 0, 40), cutout.getDisplayCutout().getSafeInsets());
@@ -227,9 +227,9 @@
     @Test
     public void computeSafeInsets_cutoutRight_greaterThan_waterfallRight() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT},
-                        Insets.of(0, 0, 20, 0)),
+                        Insets.of(0, 0, 20, 0), null),
                 200, 400);
 
         assertEquals(new Rect(0, 0, 30, 0), cutout.getDisplayCutout().getSafeInsets());
@@ -238,9 +238,9 @@
     @Test
     public void computeSafeInsets_cutoutRight_lessThan_waterfallRight() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                DisplayCutout.fromBoundsAndWaterfall(
+                DisplayCutout.constructDisplayCutout(
                         new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT},
-                        Insets.of(0, 0, 40, 0)),
+                        Insets.of(0, 0, 40, 0), null),
                 200, 400);
 
         assertEquals(new Rect(0, 0, 40, 0), cutout.getDisplayCutout().getSafeInsets());
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index a4f5249..339249b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -1026,6 +1026,8 @@
             writeLocked(fos, stats, version, packagesTokenData);
             file.finishWrite(fos);
             fos = null;
+        } catch (Exception e) {
+            // Do nothing. Exception has already been handled.
         } finally {
             // When fos is null (successful write), this will no-op
             file.failWrite(fos);
@@ -1033,7 +1035,7 @@
     }
 
     private static void writeLocked(OutputStream out, IntervalStats stats, int version,
-            PackagesTokenData packagesTokenData) throws RuntimeException {
+            PackagesTokenData packagesTokenData) throws Exception {
         switch (version) {
             case 1:
             case 2:
@@ -1045,6 +1047,7 @@
                     UsageStatsProto.write(out, stats);
                 } catch (Exception e) {
                     Slog.e(TAG, "Unable to write interval stats to proto.", e);
+                    throw e;
                 }
                 break;
             case 5:
@@ -1053,6 +1056,7 @@
                     UsageStatsProtoV2.write(out, stats);
                 } catch (Exception e) {
                     Slog.e(TAG, "Unable to write interval stats to proto.", e);
+                    throw e;
                 }
                 break;
             default:
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 3815326..0cb1255 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -79,7 +79,6 @@
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -111,6 +110,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
  * A service that collects, aggregates, and persists application usage data.
@@ -162,7 +162,7 @@
     ShortcutServiceInternal mShortcutServiceInternal;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
-    private final SparseBooleanArray mUserUnlockedStates = new SparseBooleanArray();
+    private final CopyOnWriteArraySet<Integer> mUserUnlockedStates = new CopyOnWriteArraySet<>();
     private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
     int mUsageSource;
 
@@ -333,7 +333,7 @@
 
         synchronized (mLock) {
             // User was started but never unlocked so no need to report a user stopped event
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 persistPendingEventsLocked(userId);
                 return;
             }
@@ -346,7 +346,7 @@
             if (userService != null) {
                 userService.userStopped();
             }
-            mUserUnlockedStates.put(userId, false);
+            mUserUnlockedStates.remove(userId);
             mUserState.put(userId, null); // release the service (mainly for GC)
         }
     }
@@ -360,6 +360,11 @@
             UsageStatsIdleService.scheduleUpdateMappingsJob(getContext());
         }
         synchronized (mLock) {
+            // This should be safe to add this early. Other than reportEventOrAddToQueue, every
+            // other user grabs the lock before accessing
+            // mUserUnlockedStates. reportEventOrAddToQueue does not depend on anything other than
+            // mUserUnlockedStates, and the lock will protect the handler.
+            mUserUnlockedStates.add(userId);
             // Create a user unlocked event to report
             final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
             unlockEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
@@ -377,7 +382,6 @@
 
             initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(),
                     installedPackages);
-            mUserUnlockedStates.put(userId, true);
             final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId);
             if (userService == null) {
                 Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId);
@@ -780,12 +784,11 @@
     }
 
     private void reportEventOrAddToQueue(int userId, Event event) {
+        if (mUserUnlockedStates.contains(userId)) {
+            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            return;
+        }
         synchronized (mLock) {
-            if (mUserUnlockedStates.get(userId)) {
-                mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
-                return;
-            }
-
             LinkedList<Event> events = mReportedEvents.get(userId);
             if (events == null) {
                 events = new LinkedList<>();
@@ -823,7 +826,7 @@
 
         synchronized (mLock) {
             // This should never be called directly when the user is locked
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.wtf(TAG, "Failed to report event for locked user " + userId
                         + " (" + event.mPackage + "/" + event.mClass
                         + " eventType:" + event.mEventType
@@ -1006,7 +1009,7 @@
         final int tokenRemoved;
         synchronized (mLock) {
             final long timeRemoved = System.currentTimeMillis();
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 // If user is not unlocked and a package is removed for them, we will handle it
                 // when the user service is initialized and package manager is queried.
                 return;
@@ -1030,7 +1033,7 @@
      */
     private boolean pruneUninstalledPackagesData(int userId) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 return false; // user is no longer unlocked
             }
 
@@ -1050,7 +1053,7 @@
         // fetch the installed packages outside the lock so it doesn't block package manager.
         final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM);
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(UserHandle.USER_SYSTEM)) {
+            if (!mUserUnlockedStates.contains(UserHandle.USER_SYSTEM)) {
                 return false; // user is no longer unlocked
             }
 
@@ -1069,7 +1072,7 @@
     List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
             boolean obfuscateInstantApps) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query usage stats for locked user " + userId);
                 return null;
             }
@@ -1103,7 +1106,7 @@
     List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime,
             long endTime) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query configuration stats for locked user " + userId);
                 return null;
             }
@@ -1122,7 +1125,7 @@
     List<EventStats> queryEventStats(int userId, int bucketType, long beginTime,
             long endTime) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query event stats for locked user " + userId);
                 return null;
             }
@@ -1140,7 +1143,7 @@
      */
     UsageEvents queryEvents(int userId, long beginTime, long endTime, int flags) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query events for locked user " + userId);
                 return null;
             }
@@ -1159,7 +1162,7 @@
     UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
             String packageName, boolean includeTaskRoot) {
         synchronized (mLock) {
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 Slog.w(TAG, "Failed to query package events for locked user " + userId);
                 return null;
             }
@@ -1203,7 +1206,7 @@
         final int userCount = mUserState.size();
         for (int i = 0; i < userCount; i++) {
             final int userId = mUserState.keyAt(i);
-            if (!mUserUnlockedStates.get(userId)) {
+            if (!mUserUnlockedStates.contains(userId)) {
                 persistPendingEventsLocked(userId);
                 continue;
             }
@@ -1261,7 +1264,7 @@
                             final int numUsers = mUserState.size();
                             for (int user = 0; user < numUsers; user++) {
                                 final int userId = mUserState.keyAt(user);
-                                if (!mUserUnlockedStates.get(userId)) {
+                                if (!mUserUnlockedStates.contains(userId)) {
                                     continue;
                                 }
                                 ipw.println("user=" + userId);
@@ -1288,7 +1291,7 @@
                             final int numUsers = mUserState.size();
                             for (int user = 0; user < numUsers; user++) {
                                 final int userId = mUserState.keyAt(user);
-                                if (!mUserUnlockedStates.get(userId)) {
+                                if (!mUserUnlockedStates.contains(userId)) {
                                     continue;
                                 }
                                 ipw.println("user=" + userId);
@@ -1344,7 +1347,7 @@
                 idpw.printPair("user", userId);
                 idpw.println();
                 idpw.increaseIndent();
-                if (mUserUnlockedStates.get(userId)) {
+                if (mUserUnlockedStates.contains(userId)) {
                     if (checkin) {
                         mUserState.valueAt(i).checkin(idpw);
                     } else {
@@ -1382,7 +1385,7 @@
             ipw.println("the specified user does not exist.");
             return UserHandle.USER_NULL;
         }
-        if (!mUserUnlockedStates.get(userId)) {
+        if (!mUserUnlockedStates.contains(userId)) {
             ipw.println("the specified user is currently in a locked state.");
             return UserHandle.USER_NULL;
         }
@@ -2250,12 +2253,11 @@
 
         @Override
         public byte[] getBackupPayload(int user, String key) {
+            if (!mUserUnlockedStates.contains(user)) {
+                Slog.w(TAG, "Failed to get backup payload for locked user " + user);
+                return null;
+            }
             synchronized (mLock) {
-                if (!mUserUnlockedStates.get(user)) {
-                    Slog.w(TAG, "Failed to get backup payload for locked user " + user);
-                    return null;
-                }
-
                 // Check to ensure that only user 0's data is b/r for now
                 // Note: if backup and restore is enabled for users other than the system user, the
                 // #onUserUnlocked logic, specifically when the update mappings job is scheduled via
@@ -2275,7 +2277,7 @@
         @Override
         public void applyRestoredPayload(int user, String key, byte[] payload) {
             synchronized (mLock) {
-                if (!mUserUnlockedStates.get(user)) {
+                if (!mUserUnlockedStates.contains(user)) {
                     Slog.w(TAG, "Failed to apply restored payload for locked user " + user);
                     return;
                 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c1fb74d..be36f82 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1782,7 +1782,7 @@
             }
 
             @Override
-            public void onPackageModified(String pkgName) {
+            public void onPackageModified(@NonNull String pkgName) {
                 // If the package modified is not in the current user, then don't bother making
                 // any changes as we are going to do any initialization needed when we switch users.
                 if (mCurUser != getChangingUserId()) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 9e8f8eaa..04dea3f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -163,8 +163,13 @@
         mValid = true;
         mSessionComponentName = new ComponentName(service.getPackageName(),
                 mInfo.getSessionService());
-        // TODO : Need to get the hotword detection service from the xml metadata
-        mHotwordDetectionComponentName = null;
+        final String hotwordDetectionServiceName = mInfo.getHotwordDetectionService();
+        if (hotwordDetectionServiceName != null) {
+            mHotwordDetectionComponentName = new ComponentName(service.getPackageName(),
+                    hotwordDetectionServiceName);
+        } else {
+            mHotwordDetectionComponentName = null;
+        }
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         IntentFilter filter = new IntentFilter();
@@ -388,7 +393,11 @@
         if (DEBUG) {
             Slog.d(TAG, "setHotwordDetectionConfigLocked");
         }
-
+        if (mHotwordDetectionComponentName == null) {
+            Slog.e(TAG, "Calling setHotwordDetectionConfigLocked, but hotword detection service"
+                    + " name not found");
+            return VoiceInteractionService.HOTWORD_CONFIG_FAILURE;
+        }
         if (!isIsolatedProcessLocked(mHotwordDetectionComponentName)) {
             return VoiceInteractionService.HOTWORD_CONFIG_FAILURE;
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index cfdc568..84f4f6a 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -165,7 +165,8 @@
                         | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, new UserHandle(mUser));
         if (mBound) {
             try {
-                mIWindowManager.addWindowToken(mToken, TYPE_VOICE_INTERACTION, DEFAULT_DISPLAY);
+                mIWindowManager.addWindowToken(mToken, TYPE_VOICE_INTERACTION, DEFAULT_DISPLAY,
+                        null /* options */);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed adding window token", e);
             }
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 7cc233b..cac7b82 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -84,7 +84,6 @@
     static_libs: [
         "libviewcompiler",
     ],
-    test_suites: ["general-tests"],
 }
 
 cc_binary_host {
diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING
index 5f7d3f9..791e471 100644
--- a/startop/view_compiler/TEST_MAPPING
+++ b/startop/view_compiler/TEST_MAPPING
@@ -10,10 +10,6 @@
           "include-filter": "android.view.cts.PrecompiledLayoutTest"
         }
       ]
-    },
-    {
-      "name": "view-compiler-tests",
-      "host": true
     }
   ]
 }
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index eaa3e04..5cb0c17 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -80,7 +80,7 @@
 }
 
 namespace {
-void CompileApkAssetsLayouts(const std::unique_ptr<const android::ApkAssets>& assets,
+void CompileApkAssetsLayouts(const std::unique_ptr<android::ApkAssets>& assets,
                              CompilationTarget target, std::ostream& target_out) {
   android::AssetManager2 resources;
   resources.SetApkAssets({assets.get()});
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index b73ef9b..be5fae4 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.net.Uri;
@@ -67,7 +68,8 @@
          * Sets the participants for the resulting {@link ConnectionRequest}
          * @param participants The participants to which the {@link Connection} is to connect.
          */
-        public @NonNull Builder setParticipants(@Nullable List<Uri> participants) {
+        public @NonNull Builder setParticipants(
+                @SuppressLint("NullableCollection") @Nullable List<Uri> participants) {
             this.mParticipants = participants;
             return this;
         }
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 5c75a2f..3b06fd3 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -2459,7 +2459,7 @@
     }
 
     private void onCallFilteringCompleted(String callId, boolean isBlocked, boolean isInContacts) {
-        Log.i(this, "onCallFilteringCompleted(%s, %b, %b)", isBlocked, isInContacts);
+        Log.i(this, "onCallFilteringCompleted(%b, %b)", isBlocked, isInContacts);
         Connection connection = findConnectionForAction(callId, "onCallFilteringCompleted");
         if (connection != null) {
             connection.onCallFilteringCompleted(isBlocked, isInContacts);
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 5b03863..472d639 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -314,20 +314,22 @@
     public static final String EXTRA_HAS_PICTURE = "android.telecom.extra.HAS_PICTURE";
 
     /**
-     * A URI representing the picture that was downloaded when a call is received.
+     * A URI representing the picture that was downloaded when a call is received or uploaded
+     * when a call is placed.
+     *
      * This is a content URI within the call log provider which can be used to open a file
      * descriptor. This could be set a short time after a call is added to the Dialer app if the
-     * download is delayed for some reason. The Dialer app will receive a callback via
+     * download/upload is delayed for some reason. The Dialer app will receive a callback via
      * {@link Call.Callback#onDetailsChanged} when this value has changed.
      *
      * Reference: RCC.20 Section 2.4.3.2
      */
-    public static final String EXTRA_INCOMING_PICTURE = "android.telecom.extra.INCOMING_PICTURE";
+    public static final String EXTRA_PICTURE_URI = "android.telecom.extra.PICTURE_URI";
 
-    // TODO(hallliu), This UUID is obtained from TelephonyManager#uploadCallComposerPicture.
     /**
      * A ParcelUuid used as a token to represent a picture that was uploaded prior to the call
-     * being placed.
+     * being placed. The value of this extra should be set using the {@link android.os.ParcelUuid}
+     * obtained from the callback in {@link TelephonyManager#uploadCallComposerPicture}.
      */
     public static final String EXTRA_OUTGOING_PICTURE = "android.telecom.extra.OUTGOING_PICTURE";
 
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 6dc096d..88ef1b0 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -333,6 +333,8 @@
 
     void cleanupStuckCalls();
 
+    void resetCarMode();
+
     void setTestDefaultCallRedirectionApp(String packageName);
 
     void setTestPhoneAcctSuggestionComponent(String flattenedComponentName);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4f44a06..ac584c1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4038,6 +4038,20 @@
         public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL =
                 KEY_PREFIX + "enable_presence_group_subscribe_bool";
 
+        /**
+         * An integer key associated with the period of time in seconds the non-rcs capability
+         * information of each contact is cached on the device.
+         * <p>
+         * The rcs capability cache expiration sec is managed by
+         * {@code android.telephony.ims.ProvisioningManager} but non-rcs capability is managed by
+         * {@link CarrierConfigManager} since non-rcs capability will be provided via ACS or carrier
+         * config.
+         * <p>
+         * The default value is 2592000 secs (30 days), see RCC.07 Annex A.1.9.
+         */
+        public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT =
+                KEY_PREFIX + "non_rcs_capabilities_cache_expiration_sec_int";
+
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
@@ -4048,6 +4062,7 @@
             defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
             defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true);
+            defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
             return defaults;
         }
     }
@@ -4715,6 +4730,12 @@
             "default_rtt_mode_int";
 
     /**
+     * Indicates whether RTT is supported while roaming.
+     */
+    public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL =
+            "rtt_supported_while_roaming_bool";
+
+    /**
      * Indicates if auto-configuration server is used for the RCS config
      * Reference: GSMA RCC.14
      */
@@ -5090,6 +5111,7 @@
         sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false);
         sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true);
         sDefaults.putBoolean(KEY_HIDE_TTY_HCO_VCO_WITH_RTT_BOOL, false);
+        sDefaults.putBoolean(KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true);
         sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 8507d85..706e3cb 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -344,6 +344,7 @@
             // TODO: Instead of doing this, we should create a formal way for cloning cell identity.
             // Cell identity is not an immutable object so we have to deep copy it.
             mCellIdentity = CellIdentity.CREATOR.createFromParcel(p);
+            p.recycle();
         }
 
         if (nri.mVoiceSpecificInfo != null) {
diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java
index b359ebe..fadf0e1 100644
--- a/telephony/java/android/telephony/PhysicalChannelConfig.java
+++ b/telephony/java/android/telephony/PhysicalChannelConfig.java
@@ -554,7 +554,8 @@
         }
 
         public @NonNull Builder setFrequencyRange(int frequencyRange) {
-            if (!ServiceState.isFrequencyRangeValid(frequencyRange)) {
+            if (!ServiceState.isFrequencyRangeValid(frequencyRange)
+                    && frequencyRange != ServiceState.FREQUENCY_RANGE_UNKNOWN) {
                 throw new IllegalArgumentException("Frequency range: " + frequencyRange +
                         " is invalid.");
             }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 2734ad1..1473b7a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -47,6 +47,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.ParcelUuid;
@@ -150,6 +151,22 @@
     public static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
             "cache_key.telephony.get_slot_index";
 
+    /** @hide */
+    public static final String GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME = "getSimSpecificSettings";
+
+    /** @hide */
+    public static final String RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME =
+            "restoreSimSpecificSettings";
+
+    /**
+     * Key to the backup & restore data byte array in the Bundle that is returned by {@link
+     * #getAllSimSpecificSettingsForBackup()} or to be pass in to {@link
+     * #restoreAllSimSpecificSettings()}.
+     *
+     * @hide
+     */
+    public static final String KEY_SIM_SPECIFIC_SETTINGS_DATA = "KEY_SIM_SPECIFIC_SETTINGS_DATA";
+
     private static final int MAX_CACHE_SIZE = 4;
 
     private static class VoidPropertyInvalidatedCache<T>
@@ -372,6 +389,28 @@
     public static final Uri WFC_ROAMING_ENABLED_CONTENT_URI = Uri.withAppendedPath(
             CONTENT_URI, "wfc_roaming_enabled");
 
+
+    /**
+     * A content {@link uri} used to call the appropriate backup or restore method for sim-specific
+     * settings
+     * <p>
+     * See {@link #GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME} and {@link
+     * #RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME} for information on what method to call.
+     * @hide
+     */
+    @NonNull
+    public static final Uri SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI = Uri.withAppendedPath(
+            CONTENT_URI, "backup_and_restore");
+
+    /**
+     * A content {@link uri} used to notify contentobservers listening to siminfo restore during
+     * SuW.
+     * @hide
+     */
+    @NonNull
+    public static final Uri SIM_INFO_SUW_RESTORE_CONTENT_URI = Uri.withAppendedPath(
+            SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI, "suw_restore");
+
     /**
      * A content {@link Uri} used to receive updates on cross sim enabled user setting.
      * <p>
@@ -3455,4 +3494,71 @@
         sSlotIndexCache.clear();
         sPhoneIdCache.clear();
     }
+
+    /**
+     * Called to retrieve SIM-specific settings data to be backed up.
+     *
+     * @return data in byte[] to be backed up.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public byte[] getAllSimSpecificSettingsForBackup() {
+        Bundle bundle =  mContext.getContentResolver().call(
+                SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
+                GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME, null, null);
+        return bundle.getByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA);
+    }
+
+    /**
+     * Called to attempt to restore the backed up sim-specific configs to device for specific sim.
+     * This will try to restore the data that was stored internally when {@link
+     * #restoreAllSimSpecificSettingsFromBackup(byte[] data)} was called during setup wizard.
+     * End result is SimInfoDB is modified to match any backed up configs for the requested
+     * inserted sim.
+     *
+     * <p>
+     * The {@link Uri} {@link #SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is notified if any SimInfoDB
+     * entry is updated as the result of this method call.
+     *
+     * @param iccId of the sim that a restore is requested for.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public void restoreSimSpecificSettingsForIccIdFromBackup(@NonNull String iccId) {
+        mContext.getContentResolver().call(
+                SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
+                RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
+                iccId, null);
+    }
+
+    /**
+     * Called during setup wizard restore flow to attempt to restore the backed up sim-specific
+     * configs to device for all existing SIMs in SimInfoDB. Internally, it will store the backup
+     * data in an internal file. This file will persist on device for device's lifetime and will be
+     * used later on when a SIM is inserted to restore that specific SIM's settings by calling
+     * {@link #restoreSimSpecificSettingsForIccIdFromBackup(String iccId)}. End result is
+     * SimInfoDB is modified to match any backed up configs for the appropriate inserted SIMs.
+     *
+     * <p>
+     * The {@link Uri} {@link #SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is notified if any SimInfoDB
+     * entry is updated as the result of this method call.
+     *
+     * @param data with the sim specific configs to be backed up.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public void restoreAllSimSpecificSettingsFromBackup(@NonNull byte[] data) {
+        Bundle bundle = new Bundle();
+        bundle.putByteArray(KEY_SIM_SPECIFIC_SETTINGS_DATA, data);
+        mContext.getContentResolver().call(
+                SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
+                RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
+                null, bundle);
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyLocalConnection.java b/telephony/java/android/telephony/TelephonyLocalConnection.java
new file mode 100644
index 0000000..1cab267
--- /dev/null
+++ b/telephony/java/android/telephony/TelephonyLocalConnection.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 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 android.telephony;
+
+import java.util.UUID;
+
+/**
+ * Shim used for code in frameworks/opt/telephony to be able to call code in
+ * packages/services/Telephony. A singleton instance of this class is set when the phone process
+ * is brought up.
+ * @hide
+ */
+public class TelephonyLocalConnection {
+    public interface ConnectionImpl {
+        String getCallComposerServerUrlForHandle(int subscriptionId, UUID uuid);
+    }
+    private static ConnectionImpl sInstance;
+
+    public static String getCallComposerServerUrlForHandle(int subscriptionId, UUID uuid) {
+        checkInstance();
+        return sInstance.getCallComposerServerUrlForHandle(subscriptionId, uuid);
+    }
+
+    private static void checkInstance() {
+        if (sInstance == null) {
+            throw new IllegalStateException("Connection impl is null!");
+        }
+    }
+
+    public static void setInstance(ConnectionImpl impl) {
+        sInstance = impl;
+    }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e4386e75..65b8de2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14242,7 +14242,7 @@
      * If this policy is enabled, data will be temporarily enabled on the non-default data SIM
      * during any voice calls.
      *
-     * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabledStatus}.
+     * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabled}.
      * @hide
      */
     @SystemApi
@@ -14256,7 +14256,7 @@
      * will also return true for {@link ApnSetting#TYPE_MMS}.
      * When disabled, the MMS APN will be governed by the same rules as all other APNs.
      *
-     * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabledStatus}.
+     * This policy can be enabled and disabled via {@link #setMobileDataPolicyEnabled}.
      * @hide
      */
     @SystemApi
@@ -14284,11 +14284,11 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public void setMobileDataPolicyEnabledStatus(@MobileDataPolicy int policy, boolean enabled) {
+    public void setMobileDataPolicyEnabled(@MobileDataPolicy int policy, boolean enabled) {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                service.setMobileDataPolicyEnabledStatus(getSubId(), policy, enabled);
+                service.setMobileDataPolicyEnabled(getSubId(), policy, enabled);
             }
         } catch (RemoteException ex) {
             // This could happen if binder process crashes.
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index f48ddb0..bd4bf07 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -136,7 +136,7 @@
     private final @HandoverFailureMode int mHandoverFailureMode;
     private final int mPduSessionId;
     private final Qos mDefaultQos;
-    private final List<QosSession> mQosSessions;
+    private final List<QosBearerSession> mQosBearerSessions;
     private final SliceInfo mSliceInfo;
 
     /**
@@ -187,7 +187,7 @@
         mHandoverFailureMode = HANDOVER_FAILURE_MODE_LEGACY;
         mPduSessionId = PDU_SESSION_ID_NOT_SET;
         mDefaultQos = null;
-        mQosSessions = new ArrayList<>();
+        mQosBearerSessions = new ArrayList<>();
         mSliceInfo = null;
     }
 
@@ -197,7 +197,7 @@
             @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses,
             @Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6,
             @HandoverFailureMode int handoverFailureMode, int pduSessionId,
-            @Nullable Qos defaultQos, @Nullable List<QosSession> qosSessions,
+            @Nullable Qos defaultQos, @Nullable List<QosBearerSession> qosBearerSessions,
             @Nullable SliceInfo sliceInfo) {
         mCause = cause;
         mSuggestedRetryTime = suggestedRetryTime;
@@ -219,7 +219,7 @@
         mHandoverFailureMode = handoverFailureMode;
         mPduSessionId = pduSessionId;
         mDefaultQos = defaultQos;
-        mQosSessions = qosSessions;
+        mQosBearerSessions = qosBearerSessions;
         mSliceInfo = sliceInfo;
     }
 
@@ -246,8 +246,8 @@
         mHandoverFailureMode = source.readInt();
         mPduSessionId = source.readInt();
         mDefaultQos = source.readParcelable(Qos.class.getClassLoader());
-        mQosSessions = new ArrayList<>();
-        source.readList(mQosSessions, QosSession.class.getClassLoader());
+        mQosBearerSessions = new ArrayList<>();
+        source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader());
         mSliceInfo = source.readParcelable(SliceInfo.class.getClassLoader());
     }
 
@@ -394,8 +394,8 @@
      * @hide
      */
     @NonNull
-    public List<QosSession> getQosSessions() {
-        return mQosSessions;
+    public List<QosBearerSession> getQosBearerSessions() {
+        return mQosBearerSessions;
     }
 
     /**
@@ -427,7 +427,7 @@
            .append(" handoverFailureMode=").append(failureModeToString(mHandoverFailureMode))
            .append(" pduSessionId=").append(getPduSessionId())
            .append(" defaultQos=").append(mDefaultQos)
-           .append(" qosSessions=").append(mQosSessions)
+           .append(" qosBearerSessions=").append(mQosBearerSessions)
            .append(" sliceInfo=").append(mSliceInfo)
            .append("}");
         return sb.toString();
@@ -447,10 +447,10 @@
                 mDefaultQos == other.mDefaultQos :
                 mDefaultQos.equals(other.mDefaultQos);
 
-        final boolean isQosSessionsSame = (mQosSessions == null || mQosSessions == null) ?
-                mQosSessions == other.mQosSessions :
-                mQosSessions.size() == other.mQosSessions.size()
-                && mQosSessions.containsAll(other.mQosSessions);
+        final boolean isQosBearerSessionsSame = (mQosBearerSessions == null || mQosBearerSessions == null) ?
+                mQosBearerSessions == other.mQosBearerSessions :
+                mQosBearerSessions.size() == other.mQosBearerSessions.size()
+                && mQosBearerSessions.containsAll(other.mQosBearerSessions);
 
         return mCause == other.mCause
                 && mSuggestedRetryTime == other.mSuggestedRetryTime
@@ -472,7 +472,7 @@
                 && mHandoverFailureMode == other.mHandoverFailureMode
                 && mPduSessionId == other.mPduSessionId
                 && isQosSame
-                && isQosSessionsSame
+                && isQosBearerSessionsSame
                 && Objects.equals(mSliceInfo, other.mSliceInfo);
     }
 
@@ -481,7 +481,7 @@
         return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType,
                 mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses,
                 mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos,
-                mQosSessions, mSliceInfo);
+                mQosBearerSessions, mSliceInfo);
     }
 
     @Override
@@ -515,7 +515,7 @@
         } else {
             dest.writeParcelable(null, flags);
         }
-        dest.writeList(mQosSessions);
+        dest.writeList(mQosBearerSessions);
         dest.writeParcelable(mSliceInfo, flags);
     }
 
@@ -598,7 +598,7 @@
 
         private Qos mDefaultQos;
 
-        private List<QosSession> mQosSessions = new ArrayList<>();
+        private List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
 
         private SliceInfo mSliceInfo;
 
@@ -812,15 +812,16 @@
         /**
          * Set the dedicated bearer QOS sessions for this data connection.
          *
-         * @param qosSessions Dedicated bearer QOS (Quality Of Service) sessions received
+         * @param qosBearerSessions Dedicated bearer QOS (Quality Of Service) sessions received
          * from network.
          *
          * @return The same instance of the builder.
          *
          * @hide
          */
-        public @NonNull Builder setQosSessions(@NonNull List<QosSession> qosSessions) {
-            mQosSessions = qosSessions;
+        public @NonNull Builder setQosBearerSessions(
+                @NonNull List<QosBearerSession> qosBearerSessions) {
+            mQosBearerSessions = qosBearerSessions;
             return this;
         }
 
@@ -848,7 +849,7 @@
             return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus,
                     mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses,
                     mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId,
-                    mDefaultQos, mQosSessions, mSliceInfo);
+                    mDefaultQos, mQosBearerSessions, mSliceInfo);
         }
     }
 }
diff --git a/telephony/java/android/telephony/data/EpsQos.java b/telephony/java/android/telephony/data/EpsQos.java
index ad43068..22c8b0a 100644
--- a/telephony/java/android/telephony/data/EpsQos.java
+++ b/telephony/java/android/telephony/data/EpsQos.java
@@ -48,6 +48,10 @@
         qosClassId = source.readInt();
     }
 
+    public int getQci() {
+        return qosClassId;
+    }
+
     public static @NonNull EpsQos createFromParcelBody(@NonNull Parcel in) {
         return new EpsQos(in);
     }
diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java
index c8bb91e..c286c621 100644
--- a/telephony/java/android/telephony/data/Qos.java
+++ b/telephony/java/android/telephony/data/Qos.java
@@ -56,7 +56,15 @@
         this.uplink = new QosBandwidth(uplink.maxBitrateKbps, uplink.guaranteedBitrateKbps);
     }
 
-    static class QosBandwidth implements Parcelable {
+    public QosBandwidth getDownlinkBandwidth() {
+        return downlink;
+    }
+
+    public QosBandwidth getUplinkBandwidth() {
+        return uplink;
+    }
+
+    public static class QosBandwidth implements Parcelable {
         int maxBitrateKbps;
         int guaranteedBitrateKbps;
 
@@ -73,6 +81,14 @@
             guaranteedBitrateKbps = source.readInt();
         }
 
+        public int getMaxBitrateKbps() {
+            return maxBitrateKbps;
+        }
+
+        public int getGuaranteedBitrateKbps() {
+            return guaranteedBitrateKbps;
+        }
+
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(maxBitrateKbps);
diff --git a/telephony/java/android/telephony/data/QosFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
similarity index 89%
rename from telephony/java/android/telephony/data/QosFilter.java
rename to telephony/java/android/telephony/data/QosBearerFilter.java
index 6927744..6c1c653 100644
--- a/telephony/java/android/telephony/data/QosFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -38,7 +38,7 @@
  *
  * @hide
  */
-public final class QosFilter implements Parcelable {
+public final class QosBearerFilter implements Parcelable {
 
     private List<LinkAddress> localAddresses;
     private List<LinkAddress> remoteAddresses;
@@ -74,7 +74,7 @@
     @IntDef(prefix = "QOS_FILTER_DIRECTION_",
             value = {QOS_FILTER_DIRECTION_DOWNLINK, QOS_FILTER_DIRECTION_UPLINK,
                     QOS_FILTER_DIRECTION_BIDIRECTIONAL})
-    public @interface QosFilterDirection {}
+    public @interface QosBearerFilterDirection {}
 
     public static final int QOS_FILTER_DIRECTION_DOWNLINK =
             android.hardware.radio.V1_6.QosFilterDirection.DOWNLINK;
@@ -83,7 +83,7 @@
     public static final int QOS_FILTER_DIRECTION_BIDIRECTIONAL =
             android.hardware.radio.V1_6.QosFilterDirection.BIDIRECTIONAL;
 
-    @QosFilterDirection
+    @QosBearerFilterDirection
     private int filterDirection;
 
     /**
@@ -92,7 +92,7 @@
      */
     private int precedence;
 
-    QosFilter() {
+    QosBearerFilter() {
         localAddresses = new ArrayList<>();
         remoteAddresses = new ArrayList<>();
         localPort = new PortRange();
@@ -101,7 +101,7 @@
         filterDirection = QOS_FILTER_DIRECTION_BIDIRECTIONAL;
     }
 
-    public QosFilter(List<LinkAddress> localAddresses, List<LinkAddress> remoteAddresses,
+    public QosBearerFilter(List<LinkAddress> localAddresses, List<LinkAddress> remoteAddresses,
             PortRange localPort, PortRange remotePort, int protocol, int tos,
             long flowLabel, long spi, int direction, int precedence) {
         this.localAddresses = localAddresses;
@@ -116,10 +116,30 @@
         this.precedence = precedence;
     }
 
+    public List<LinkAddress> getLocalAddresses() {
+        return localAddresses;
+    }
+
+    public List<LinkAddress> getRemoteAddresses() {
+        return remoteAddresses;
+    }
+
+    public PortRange getLocalPortRange() {
+        return localPort;
+    }
+
+    public PortRange getRemotePortRange() {
+        return remotePort;
+    }
+
+    public int getPrecedence() {
+        return precedence;
+    }
+
     /** @hide */
-    public static @NonNull QosFilter create(
+    public static @NonNull QosBearerFilter create(
             @NonNull android.hardware.radio.V1_6.QosFilter qosFilter) {
-        QosFilter ret = new QosFilter();
+        QosBearerFilter ret = new QosBearerFilter();
 
         String[] localAddresses = qosFilter.localAddresses.stream().toArray(String[]::new);
         if (localAddresses != null) {
@@ -202,6 +222,14 @@
             this.end = end;
         }
 
+        public int getStart() {
+            return start;
+        }
+
+        public int getEnd() {
+            return end;
+        }
+
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(start);
@@ -254,7 +282,7 @@
 
     @Override
     public String toString() {
-        return "QosFilter {"
+        return "QosBearerFilter {"
                 + " localAddresses=" + localAddresses
                 + " remoteAddresses=" + remoteAddresses
                 + " localPort=" + localPort
@@ -278,11 +306,11 @@
     public boolean equals(Object o) {
         if (this == o) return true;
 
-        if (o == null || !(o instanceof QosFilter)) {
+        if (o == null || !(o instanceof QosBearerFilter)) {
             return false;
         }
 
-        QosFilter other = (QosFilter) o;
+        QosBearerFilter other = (QosBearerFilter) o;
 
         return localAddresses.size() == other.localAddresses.size()
                 && localAddresses.containsAll(other.localAddresses)
@@ -324,7 +352,7 @@
                 LinkAddress.LIFETIME_UNKNOWN, LinkAddress.LIFETIME_UNKNOWN);
     }
 
-    private QosFilter(Parcel source) {
+    private QosBearerFilter(Parcel source) {
         localAddresses = new ArrayList<>();
         source.readList(localAddresses, LinkAddress.class.getClassLoader());
         remoteAddresses = new ArrayList<>();
@@ -358,16 +386,16 @@
         return 0;
     }
 
-    public static final @NonNull Parcelable.Creator<QosFilter> CREATOR =
-            new Parcelable.Creator<QosFilter>() {
+    public static final @NonNull Parcelable.Creator<QosBearerFilter> CREATOR =
+            new Parcelable.Creator<QosBearerFilter>() {
                 @Override
-                public QosFilter createFromParcel(Parcel source) {
-                    return new QosFilter(source);
+                public QosBearerFilter createFromParcel(Parcel source) {
+                    return new QosBearerFilter(source);
                 }
 
                 @Override
-                public QosFilter[] newArray(int size) {
-                    return new QosFilter[size];
+                public QosBearerFilter[] newArray(int size) {
+                    return new QosBearerFilter[size];
                 }
             };
 }
diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java
new file mode 100644
index 0000000..30effc9
--- /dev/null
+++ b/telephony/java/android/telephony/data/QosBearerSession.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright 2020 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 android.telephony.data;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+
+/**
+ * Class that stores information specific to QOS session.
+ *
+ * @hide
+ */
+public final class QosBearerSession implements Parcelable{
+
+    final int qosBearerSessionId;
+    final Qos qos;
+    final List<QosBearerFilter> qosBearerFilterList;
+
+    public QosBearerSession(int qosBearerSessionId, @NonNull Qos qos, @NonNull List<QosBearerFilter> qosBearerFilterList) {
+        this.qosBearerSessionId = qosBearerSessionId;
+        this.qos = qos;
+        this.qosBearerFilterList = qosBearerFilterList;
+    }
+
+    private QosBearerSession(Parcel source) {
+        qosBearerSessionId = source.readInt();
+        qos = source.readParcelable(Qos.class.getClassLoader());
+        qosBearerFilterList = new ArrayList<>();
+        source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader());
+    }
+
+    public int getQosBearerSessionId() {
+        return qosBearerSessionId;
+    }
+
+    public Qos getQos() {
+        return qos;
+    }
+
+    public List<QosBearerFilter> getQosBearerFilterList() {
+        return qosBearerFilterList;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(qosBearerSessionId);
+        if (qos.getType() == Qos.QOS_TYPE_EPS) {
+            dest.writeParcelable((EpsQos)qos, flags);
+        } else {
+            dest.writeParcelable((NrQos)qos, flags);
+        }
+        dest.writeList(qosBearerFilterList);
+    }
+
+    public static @NonNull QosBearerSession create(
+            @NonNull android.hardware.radio.V1_6.QosSession qosSession) {
+        List<QosBearerFilter> qosBearerFilters = new ArrayList<>();
+
+        if (qosSession.qosFilters != null) {
+            for (android.hardware.radio.V1_6.QosFilter filter : qosSession.qosFilters) {
+                qosBearerFilters.add(QosBearerFilter.create(filter));
+            }
+        }
+
+        return new QosBearerSession(
+                        qosSession.qosSessionId,
+                        Qos.create(qosSession.qos),
+                        qosBearerFilters);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "QosBearerSession {"
+                + " qosBearerSessionId=" + qosBearerSessionId
+                + " qos=" + qos
+                + " qosBearerFilterList=" + qosBearerFilterList + "}";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(qosBearerSessionId, qos, qosBearerFilterList);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if (o == null || !(o instanceof QosBearerSession)) {
+            return false;
+        }
+
+        QosBearerSession other = (QosBearerSession) o;
+        return this.qosBearerSessionId == other.qosBearerSessionId
+                && this.qos.equals(other.qos)
+                && this.qosBearerFilterList.size() == other.qosBearerFilterList.size()
+                && this.qosBearerFilterList.containsAll(other.qosBearerFilterList);
+    }
+
+
+    public static final @NonNull Parcelable.Creator<QosBearerSession> CREATOR =
+            new Parcelable.Creator<QosBearerSession>() {
+                @Override
+                public QosBearerSession createFromParcel(Parcel source) {
+                    return new QosBearerSession(source);
+                }
+
+                @Override
+                public QosBearerSession[] newArray(int size) {
+                    return new QosBearerSession[size];
+                }
+            };
+}
diff --git a/telephony/java/android/telephony/data/QosSession.java b/telephony/java/android/telephony/data/QosSession.java
deleted file mode 100644
index f07b6a9..0000000
--- a/telephony/java/android/telephony/data/QosSession.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/**
- * Copyright 2020 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 android.telephony.data;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-
-/**
- * Class that stores information specific to QOS session.
- *
- * @hide
- */
-public final class QosSession implements Parcelable{
-
-    final int qosSessionId;
-    final Qos qos;
-    final List<QosFilter> qosFilterList;
-
-    public QosSession(int qosSessionId, @NonNull Qos qos, @NonNull List<QosFilter> qosFilterList) {
-        this.qosSessionId = qosSessionId;
-        this.qos = qos;
-        this.qosFilterList = qosFilterList;
-    }
-
-    private QosSession(Parcel source) {
-        qosSessionId = source.readInt();
-        qos = source.readParcelable(Qos.class.getClassLoader());
-        qosFilterList = new ArrayList<>();
-        source.readList(qosFilterList, QosFilter.class.getClassLoader());
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(qosSessionId);
-        if (qos.getType() == Qos.QOS_TYPE_EPS) {
-            dest.writeParcelable((EpsQos)qos, flags);
-        } else {
-            dest.writeParcelable((NrQos)qos, flags);
-        }
-        dest.writeList(qosFilterList);
-    }
-
-    public static @NonNull QosSession create(
-            @NonNull android.hardware.radio.V1_6.QosSession qosSession) {
-        List<QosFilter> qosFilters = new ArrayList<>();
-
-        if (qosSession.qosFilters != null) {
-            for (android.hardware.radio.V1_6.QosFilter filter : qosSession.qosFilters) {
-                qosFilters.add(QosFilter.create(filter));
-            }
-        }
-
-        return new QosSession(
-                        qosSession.qosSessionId,
-                        Qos.create(qosSession.qos),
-                        qosFilters);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public String toString() {
-        return "QosSession {"
-                + " qosSessionId=" + qosSessionId
-                + " qos=" + qos
-                + " qosFilterList=" + qosFilterList + "}";
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(qosSessionId, qos, qosFilterList);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-
-        if (o == null || !(o instanceof QosSession)) {
-            return false;
-        }
-
-        QosSession other = (QosSession) o;
-        return this.qosSessionId == other.qosSessionId
-                && this.qos.equals(other.qos)
-                && this.qosFilterList.size() == other.qosFilterList.size()
-                && this.qosFilterList.containsAll(other.qosFilterList);
-    }
-
-
-    public static final @NonNull Parcelable.Creator<QosSession> CREATOR =
-            new Parcelable.Creator<QosSession>() {
-                @Override
-                public QosSession createFromParcel(Parcel source) {
-                    return new QosSession(source);
-                }
-
-                @Override
-                public QosSession[] newArray(int size) {
-                    return new QosSession[size];
-                }
-            };
-}
diff --git a/telephony/java/android/telephony/ims/DelegateStateCallback.java b/telephony/java/android/telephony/ims/DelegateStateCallback.java
index fb65949..6bf992e 100644
--- a/telephony/java/android/telephony/ims/DelegateStateCallback.java
+++ b/telephony/java/android/telephony/ims/DelegateStateCallback.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.telephony.ims.stub.SipDelegate;
 import android.telephony.ims.stub.SipTransportImplBase;
@@ -52,7 +53,9 @@
      *    implementing this feature elsewhere. If all features of this {@link SipDelegate} are
      *    denied, this method should still be called.
      */
-    void onCreated(@NonNull SipDelegate delegate, @Nullable Set<FeatureTagState> deniedTags);
+    void onCreated(@NonNull SipDelegate delegate,
+            @SuppressLint("NullableCollection")  // TODO(b/154763999): Mark deniedTags @Nonnull
+            @Nullable Set<FeatureTagState> deniedTags);
 
     /**
      * This must be called by the ImsService after the framework calls
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 1faae42..a9dae89 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -138,6 +138,8 @@
      * Indicates if the session is for a conference call or not. If not defined, should be
      * considered {@code false}.
      * Boolean extra properties - {@code true} / {@code false}.
+     *
+     * This extra is set on an instance of {@link ImsCallProfile} via {@link #setCallExtraBoolean}.
      * @hide
      */
     @SystemApi
@@ -174,6 +176,8 @@
      * Indicates if the session can be extended to a conference call. If not defined, should be
      * considered {@code false}.
      * Boolean extra properties - {@code true} / {@code false}.
+     *
+     * This extra is set on an instance of {@link ImsCallProfile} via {@link #setCallExtraBoolean}.
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/ims/ImsUtListener.java b/telephony/java/android/telephony/ims/ImsUtListener.java
index baa0576..754814f 100644
--- a/telephony/java/android/telephony/ims/ImsUtListener.java
+++ b/telephony/java/android/telephony/ims/ImsUtListener.java
@@ -178,4 +178,11 @@
     public ImsUtListener(IImsUtListener serviceInterface) {
         mServiceInterface = serviceInterface;
     }
+
+    /**
+     * @hide
+     */
+    public IImsUtListener getListenerInterface() {
+        return mServiceInterface;
+    }
 }
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index f444c62..31ba853 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,6 +25,7 @@
 import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.annotation.WorkerThread;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
@@ -1325,7 +1326,7 @@
      * the RCS VoLTE single registration feature. Only default messaging application may receive
      * the intent.
      *
-     * <p>Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to specify the subscription index for which
+     * <p>Contains {@link #EXTRA_SUBSCRIPTION_ID} to specify the subscription index for which
      * the intent is valid. and {@link #EXTRA_STATUS} to specify RCS VoLTE single registration
      * status.
      */
@@ -1371,7 +1372,7 @@
      * provisioning is done using autoconfiguration, then these parameters shall be
      * sent in the HTTP get request to fetch the RCS provisioning. RCS client
      * configuration must be provided by the application before registering for the
-     * provisioning status events {@link #registerRcsProvisioningChangedCallback()}
+     * provisioning status events {@link #registerRcsProvisioningChangedCallback}
      * @param rcc RCS client configuration {@link RcsClientConfiguration}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1387,13 +1388,15 @@
     }
 
     /**
-     * Returns a flag to indicate if the device software and the carrier
-     * have the capability to support RCS Volte single IMS registration.
-     * @return true if this single registration is capable, false otherwise
+     * Returns a flag to indicate whether or not the device supports IMS single registration for
+     * MMTEL and RCS features as well as if the carrier has provisioned the feature.
+     * @return true if IMS single registration is capable at this time, or false otherwise
      * @throws ImsException If the remote ImsService is not available for
      * any reason or the subscription associated with this instance is no
      * longer active. See {@link ImsException#getCode()} for more
      * information.
+     * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION for whether or not this
+     * device supports IMS single registration.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isRcsVolteSingleRegistrationCapable() throws ImsException {
@@ -1430,7 +1433,7 @@
      * available. This can happen if the service crashed, for example.
      * It shall also throw this exception when the RCS client parameters for the
      * application are not valid. In that case application must set the client
-     * params (See {@link #setRcsClientConfiguration()}) and re register the
+     * params (See {@link #setRcsClientConfiguration}) and re register the
      * callback.
      * See {@link ImsException#getCode()} for a more detailed reason.
      */
@@ -1458,9 +1461,9 @@
      * will result in a no-op.
      * @param callback The existing {@link RcsProvisioningCallback} to be
      * removed.
-     * @see #registerRcsProvisioningChangedCallback(RcsClientConfiguration,
-     * Executor, RcsProvisioningCallback) @throws IllegalArgumentException
-     * if the subscription associated with this callback is invalid.
+     * @see #registerRcsProvisioningChangedCallback
+     * @throws IllegalArgumentException if the subscription associated with this callback is
+     * invalid.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void unregisterRcsProvisioningChangedCallback(
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
index 519d016..5eb75e7 100644
--- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -34,14 +35,135 @@
  * network during a SUBSCRIBE request. See RFC3863 for more information.
  * @hide
  */
+@SystemApi
 public final class RcsContactPresenceTuple implements Parcelable {
 
-    /** The service id of the MMTEL */
+    /**
+     * The service ID used to indicate that MMTEL service is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
     public static final String SERVICE_ID_MMTEL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.mmtel";
 
-    /** The service id of the Call Composer */
+    /**
+     * The service ID used to indicate that the chat(v1.0) is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_CHAT_V1 = "org.openmobilealliance:IM-session";
+
+    /**
+     * The service ID used to indicate that the chat(v2.0) is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_CHAT_V2 = "org.openmobilealliance:ChatSession";
+
+    /**
+     * The service ID used to indicate that the File Transfer is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_FT = "org.openmobilealliance:File-Transfer-HTTP";
+
+    /**
+     * The service ID used to indicate that the File Transfer over SMS is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_FT_OVER_SMS =
+            "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.ftsms";
+
+    /**
+     * The service ID used to indicate that the Geolocation Push is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_GEO_PUSH =
+            "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.geopush";
+
+    /**
+     * The service ID used to indicate that the Geolocation Push via SMS is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_GEO_PUSH_VIA_SMS =
+            "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.geosms";
+
+    /**
+     * The service ID used to indicate that the Call Composer is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
     public static final String SERVICE_ID_CALL_COMPOSER =
-            "org.3gpp.urn:urn-7:3gppservice.ims.icsi.gsma.callcomposer";
+            "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callcomposer";
+
+    /**
+     * The service ID used to indicate that the Post Call is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_POST_CALL =
+            "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.callunanswered";
+
+    /**
+     * The service ID used to indicate that the Shared Map is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_SHARED_MAP =
+            "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.sharedmap";
+
+    /**
+     * The service ID used to indicate that the Shared Sketch is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_SHARED_SKETCH =
+            "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.gsma.sharedsketch";
+
+    /**
+     * The service ID used to indicate that the Chatbot using Session is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_CHATBOT =
+            "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.chatbot";
+
+    /**
+     * The service ID used to indicate that the Standalone Messaging is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_CHATBOT_STANDALONE =
+            " org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcs.chatbot.sa";
+
+    /**
+     * The service ID used to indicate that the Chatbot Role is available.
+     * <p>
+     * See the GSMA RCC.07 specification for more information.
+     */
+    public static final String SERVICE_ID_CHATBOT_ROLE = "org.gsma.rcs.isbot";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = "SERVICE_ID_", value = {
+            SERVICE_ID_MMTEL,
+            SERVICE_ID_CHAT_V1,
+            SERVICE_ID_CHAT_V2,
+            SERVICE_ID_FT,
+            SERVICE_ID_FT_OVER_SMS,
+            SERVICE_ID_GEO_PUSH,
+            SERVICE_ID_GEO_PUSH_VIA_SMS,
+            SERVICE_ID_CALL_COMPOSER,
+            SERVICE_ID_POST_CALL,
+            SERVICE_ID_SHARED_MAP,
+            SERVICE_ID_SHARED_SKETCH,
+            SERVICE_ID_CHATBOT,
+            SERVICE_ID_CHATBOT_STANDALONE,
+            SERVICE_ID_CHATBOT_ROLE
+    })
+    public @interface ServiceId {}
 
     /** The service capabilities is available. */
     public static final String TUPLE_BASIC_STATUS_OPEN = "open";
@@ -149,6 +271,7 @@
             in.readStringList(mSupportedDuplexModeList);
             in.readStringList(mUnsupportedDuplexModeList);
         }
+
         @Override
         public void writeToParcel(@NonNull Parcel out, int flags) {
             out.writeBoolean(mIsAudioCapable);
@@ -217,12 +340,14 @@
 
         /**
          * Builds a RcsContactPresenceTuple instance.
+         * @param status The status associated with the service capability. See RFC3865 for more
+         * information.
          * @param serviceId The OMA Presence service-id associated with this capability. See the
          * OMA Presence SIMPLE specification v1.1, section 10.5.1.
          * @param serviceVersion The OMA Presence version associated with the service capability.
          * See the OMA Presence SIMPLE specification v1.1, section 10.5.1.
          */
-        public Builder(@NonNull @BasicStatus String status, @NonNull String serviceId,
+        public Builder(@NonNull @BasicStatus String status, @NonNull @ServiceId String serviceId,
                 @NonNull String serviceVersion) {
             mPresenceTuple = new RcsContactPresenceTuple(status, serviceId, serviceVersion);
         }
@@ -230,16 +355,17 @@
         /**
          * The optional SIP Contact URI associated with the PIDF tuple element.
          */
-        public @NonNull Builder addContactUri(@NonNull Uri contactUri) {
+        public @NonNull Builder setContactUri(@NonNull Uri contactUri) {
             mPresenceTuple.mContactUri = contactUri;
             return this;
         }
 
         /**
          * The optional timestamp indicating the data and time of the status change of this tuple.
-         * See RFC3863, section 4.1.7 for more information on the expected format.
+         * Per RFC3863 section 4.1.7, the timestamp is formatted as an IMPP datetime format
+         * string per RFC3339.
          */
-        public @NonNull Builder addTimeStamp(@NonNull String timestamp) {
+        public @NonNull Builder setTimestamp(@NonNull String timestamp) {
             mPresenceTuple.mTimestamp = timestamp;
             return this;
         }
@@ -248,7 +374,7 @@
          * An optional parameter containing the description element of the service-description. See
          * OMA Presence SIMPLE specification v1.1
          */
-        public @NonNull Builder addDescription(@NonNull String description) {
+        public @NonNull Builder setServiceDescription(@NonNull String description) {
             mPresenceTuple.mServiceDescription = description;
             return this;
         }
@@ -257,7 +383,7 @@
          * An optional parameter containing the service capabilities of the presence tuple if they
          * are present in the servcaps element.
          */
-        public @NonNull Builder addServiceCapabilities(@NonNull ServiceCapabilities caps) {
+        public @NonNull Builder setServiceCapabilities(@NonNull ServiceCapabilities caps) {
             mPresenceTuple.mServiceCapabilities = caps;
             return this;
         }
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index d4715bf..fe85502 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -33,6 +34,7 @@
  * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
  * @hide
  */
+@SystemApi
 public final class RcsContactUceCapability implements Parcelable {
 
     /** Contains presence information associated with the contact */
@@ -70,52 +72,46 @@
     public @interface SourceType {}
 
     /**
+     * Capability information for the requested contact has expired and can not be refreshed due to
+     * a temporary network error. This is a temporary error and the capabilities of the contact
+     * should be queried again at a later time.
+     */
+    public static final int REQUEST_RESULT_UNKNOWN = 0;
+
+    /**
      * The requested contact was found to be offline when queried. This is only applicable to
      * contact capabilities that were queried via OPTIONS requests and the network returned a
      * 408/480 response.
      */
-    public static final int REQUEST_RESULT_NOT_ONLINE = 0;
+    public static final int REQUEST_RESULT_NOT_ONLINE = 1;
 
     /**
      * Capability information for the requested contact was not found. The contact should not be
      * considered an RCS user.
      */
-    public static final int REQUEST_RESULT_NOT_FOUND = 1;
+    public static final int REQUEST_RESULT_NOT_FOUND = 2;
 
     /**
      * Capability information for the requested contact was found successfully.
      */
-    public static final int REQUEST_RESULT_FOUND = 2;
-
-    /**
-     * Capability information for the requested contact has expired and can not be refreshed due to
-     * a temporary network error. This is a temporary error and the capabilities of the contact
-     * should be queried again at a later time.
-     */
-    public static final int REQUEST_RESULT_UNKNOWN = 3;
+    public static final int REQUEST_RESULT_FOUND = 3;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "REQUEST_RESULT_", value = {
+        REQUEST_RESULT_UNKNOWN,
         REQUEST_RESULT_NOT_ONLINE,
         REQUEST_RESULT_NOT_FOUND,
-        REQUEST_RESULT_FOUND,
-        REQUEST_RESULT_UNKNOWN
+        REQUEST_RESULT_FOUND
     })
     public @interface RequestResult {}
 
     /**
-     * The base class of {@link OptionsBuilder} and {@link PresenceBuilder}
-     */
-    public static abstract class RcsUcsCapabilityBuilder {
-        public abstract @NonNull RcsContactUceCapability build();
-    }
-
-    /**
      * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
      * queried through SIP OPTIONS.
+     * @hide
      */
-    public static class OptionsBuilder extends RcsUcsCapabilityBuilder {
+    public static final class OptionsBuilder {
 
         private final RcsContactUceCapability mCapabilities;
 
@@ -162,7 +158,6 @@
         /**
          * @return the constructed instance.
          */
-        @Override
         public @NonNull RcsContactUceCapability build() {
             return mCapabilities;
         }
@@ -172,7 +167,7 @@
      * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
      * queried through a presence server.
      */
-    public static class PresenceBuilder extends RcsUcsCapabilityBuilder {
+    public static final class PresenceBuilder {
 
         private final RcsContactUceCapability mCapabilities;
 
@@ -214,7 +209,6 @@
         /**
          * @return the RcsContactUceCapability instance.
          */
-        @Override
         public @NonNull RcsContactUceCapability build() {
             return mCapabilities;
         }
@@ -284,6 +278,7 @@
      * <p>
      * Note: this is only populated if {@link #getCapabilityMechanism} is
      * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS}
+     * @hide
      */
     public @NonNull List<String> getOptionsFeatureTags() {
         if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
@@ -299,7 +294,7 @@
      * Note: this is only populated if {@link #getCapabilityMechanism} is
      * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_PRESENCE}
      */
-    public @NonNull List<RcsContactPresenceTuple> getPresenceTuples() {
+    public @NonNull List<RcsContactPresenceTuple> getCapabilityTuples() {
         if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) {
             return Collections.emptyList();
         }
@@ -309,13 +304,14 @@
     /**
      * Get the RcsContactPresenceTuple associated with the given service id.
      * @param serviceId The service id to get the presence tuple.
-     * @return The RcsContactPresenceTuple which has the given service id.
+     * @return The RcsContactPresenceTuple which has the given service id or {@code null} if the
+     * service id does not exist in the list of presence tuples returned from the network.
      *
      * <p>
      * Note: this is only populated if {@link #getCapabilityMechanism} is
      * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_PRESENCE}
      */
-    public @Nullable RcsContactPresenceTuple getPresenceTuple(@NonNull String serviceId) {
+    public @Nullable RcsContactPresenceTuple getCapabilityTuple(@NonNull String serviceId) {
         if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) {
             return null;
         }
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 6c31466..070fd799 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -63,6 +63,7 @@
      * RcsFeature should not publish capabilities or service capability requests.
      * @hide
      */
+    @SystemApi
     public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1;
 
     /**@hide*/
@@ -77,12 +78,14 @@
      * An unknown error has caused the request to fail.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_GENERIC_FAILURE = 1;
 
     /**
      * The carrier network does not have UCE support enabled for this subscriber.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_NOT_ENABLED = 2;
 
     /**
@@ -90,12 +93,14 @@
      * 1x only currently).
      * @hide
      */
+    @SystemApi
     public static final int ERROR_NOT_AVAILABLE = 3;
 
     /**
      * The network has responded with SIP 403 error and a reason "User not registered."
      * @hide
      */
+    @SystemApi
     public static final int ERROR_NOT_REGISTERED = 4;
 
     /**
@@ -103,12 +108,14 @@
      * presence" for this subscriber.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_NOT_AUTHORIZED = 5;
 
     /**
      * The network has responded to this request with a SIP 403 error and no reason.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_FORBIDDEN = 6;
 
     /**
@@ -116,6 +123,7 @@
      * subscriber to the carrier network.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_NOT_FOUND = 7;
 
     /**
@@ -123,6 +131,7 @@
      * with a lower number of contact numbers. The number varies per carrier.
      * @hide
      */
+    @SystemApi
     // TODO: Try to integrate this into the API so that the service will split based on carrier.
     public static final int ERROR_REQUEST_TOO_LARGE = 8;
 
@@ -130,18 +139,21 @@
      * The network did not respond to the capabilities request before the request timed out.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_REQUEST_TIMEOUT = 9;
 
     /**
      * The request failed due to the service having insufficient memory.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_INSUFFICIENT_MEMORY = 10;
 
     /**
      * The network was lost while trying to complete the request.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_LOST_NETWORK = 11;
 
     /**
@@ -149,6 +161,7 @@
      * time returned in {@link CapabilitiesCallback#onError} has elapsed.
      * @hide
      */
+    @SystemApi
     public static final int ERROR_SERVER_UNAVAILABLE = 12;
 
     /**@hide*/
@@ -405,6 +418,7 @@
      * @see #requestCapabilities(Executor, List, CapabilitiesCallback)
      * @hide
      */
+    @SystemApi
     public interface CapabilitiesCallback {
 
         /**
@@ -424,10 +438,10 @@
          * The pending request has resulted in an error and may need to be retried, depending on the
          * error code.
          * @param errorCode The reason for the framework being unable to process the request.
-         * @param retryAfterMilliseconds The time in milliseconds the requesting application should
+         * @param retryIntervalMillis The time in milliseconds the requesting application should
          * wait before retrying, if non-zero.
          */
-        void onError(@ErrorCode int errorCode, long retryAfterMilliseconds);
+        void onError(@ErrorCode int errorCode, long retryIntervalMillis);
     }
 
     private final Context mContext;
@@ -458,9 +472,9 @@
      * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
      * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
      *
+     * @param contactNumbers A list of numbers that the capabilities are being requested for.
      * @param executor The executor that will be used when the request is completed and the
      *         {@link CapabilitiesCallback} is called.
-     * @param contactNumbers A list of numbers that the capabilities are being requested for.
      * @param c A one-time callback for when the request for capabilities completes or there is an
      *         error processing the request.
      * @throws ImsException if the subscription associated with this instance of
@@ -469,9 +483,10 @@
      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public void requestCapabilities(@NonNull @CallbackExecutor Executor executor,
-            @NonNull List<Uri> contactNumbers,
+    public void requestCapabilities(@NonNull List<Uri> contactNumbers,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull CapabilitiesCallback c) throws ImsException {
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null CapabilitiesCallback.");
@@ -495,8 +510,7 @@
             public void onCapabilitiesReceived(List<RcsContactUceCapability> contactCapabilities) {
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    executor.execute(() ->
-                            c.onCapabilitiesReceived(contactCapabilities));
+                    executor.execute(() -> c.onCapabilitiesReceived(contactCapabilities));
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
@@ -550,13 +564,17 @@
      * {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
      *
      * @param contactNumber The contact of the capabilities is being requested for.
+     * @param executor The executor that will be used when the request is completed and the
+     * {@link CapabilitiesCallback} is called.
      * @param c A one-time callback for when the request for capabilities completes or there is
      * an error processing the request.
      * @hide
      */
+    @SystemApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
-    public void requestNetworkAvailability(@NonNull @CallbackExecutor Executor executor,
-            @NonNull Uri contactNumber, @NonNull CapabilitiesCallback c) throws ImsException {
+    public void requestAvailability(@NonNull Uri contactNumber,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CapabilitiesCallback c) throws ImsException {
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
@@ -569,7 +587,7 @@
 
         IImsRcsController imsRcsController = getIImsRcsController();
         if (imsRcsController == null) {
-            Log.e(TAG, "requestNetworkAvailability: IImsRcsController is null");
+            Log.e(TAG, "requestAvailability: IImsRcsController is null");
             throw new ImsException("Cannot find remote IMS service",
                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
@@ -579,8 +597,7 @@
             public void onCapabilitiesReceived(List<RcsContactUceCapability> contactCapabilities) {
                 final long callingIdentity = Binder.clearCallingIdentity();
                 try {
-                    executor.execute(() ->
-                            c.onCapabilitiesReceived(contactCapabilities));
+                    executor.execute(() -> c.onCapabilitiesReceived(contactCapabilities));
                 } finally {
                     restoreCallingIdentity(callingIdentity);
                 }
@@ -606,12 +623,12 @@
         };
 
         try {
-            imsRcsController.requestNetworkAvailability(mSubId, mContext.getOpPackageName(),
+            imsRcsController.requestAvailability(mSubId, mContext.getOpPackageName(),
                     mContext.getAttributionTag(), contactNumber, internalCallback);
         } catch (ServiceSpecificException e) {
             throw new ImsException(e.toString(), e.errorCode);
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling IImsRcsController#requestNetworkAvailability", e);
+            Log.e(TAG, "Error calling IImsRcsController#requestAvailability", e);
             throw new ImsException("Remote IMS Service is not available",
                     ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
@@ -683,7 +700,7 @@
         if (imsRcsController == null) {
             Log.e(TAG, "addOnPublishStateChangedListener : IImsRcsController is null");
             throw new ImsException("Cannot find remote IMS service",
-                ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
 
         PublishStateCallbackAdapter stateCallback = addPublishStateCallback(executor, listener);
@@ -694,7 +711,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling IImsRcsController#registerUcePublishStateCallback", e);
             throw new ImsException("Remote IMS Service is not available",
-                ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
diff --git a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
index eddbb10..8762b6a 100644
--- a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
+++ b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java
@@ -280,6 +280,12 @@
             "sip_config_path_header_string";
 
     /**
+     * The SIP User-Agent header value used by the IMS stack during IMS registration.
+     */
+    public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING =
+            "sip_config_sip_user_agent_header_string";
+
+    /**
      * SIP User part string in contact header
      */
     public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING =
@@ -292,12 +298,20 @@
             "sip_config_p_access_network_info_header_string";
 
     /**
-     * SIP P-last-access-network-info header string
+     * The SIP P-last-access-network-info header value, populated for networks that require this
+     * information to be provided in outgoing SIP messages.
      */
     public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING =
             "sip_config_p_last_access_network_info_header_string";
 
     /**
+     * The Cellular-Network-Info header value (See 3GPP 24.229, section 7.2.15), populated for
+     * networks that require this information to be provided as part of outgoing SIP messages.
+     */
+    public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING =
+            "sip_config_cellular_network_info_header_string";
+
+    /**
      * SIP P-associated-uri header string
      */
     public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING =
@@ -320,9 +334,11 @@
             KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING,
             KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING,
             KEY_SIP_CONFIG_PATH_HEADER_STRING,
+            KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING,
             KEY_SIP_CONFIG_URI_USER_PART_STRING,
             KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING,
             KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING,
+            KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING,
             KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING
     })
     @Retention(RetentionPolicy.SOURCE)
diff --git a/telephony/java/android/telephony/ims/SipDelegateManager.java b/telephony/java/android/telephony/ims/SipDelegateManager.java
index 2e9eb94..04421c9 100644
--- a/telephony/java/android/telephony/ims/SipDelegateManager.java
+++ b/telephony/java/android/telephony/ims/SipDelegateManager.java
@@ -24,6 +24,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.telephony.BinderCacheManager;
@@ -47,6 +48,9 @@
  * This allows multiple IMS applications to forward SIP messages to/from their application for the
  * purposes of providing a single IMS registration to the carrier's IMS network from potentially
  * many IMS stacks implementing a subset of the supported MMTEL/RCS features.
+ * <p>
+ * This API is only supported if the device supports the
+ * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION} feature.
  * @hide
  */
 @SystemApi
@@ -269,6 +273,7 @@
      * {@link ImsException#getCode()} for more information.
      *
      * @see CarrierConfigManager.Ims#KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL
+     * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isSupported() throws ImsException {
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index 3634989..7a6c28b 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -52,7 +52,7 @@
     // ImsUceAdapter specific
     void requestCapabilities(int subId, String callingPackage, String callingFeatureId,
             in List<Uri> contactNumbers, IRcsUceControllerCallback c);
-    void requestNetworkAvailability(int subId, String callingPackage,
+    void requestAvailability(int subId, String callingPackage,
             String callingFeatureId, in Uri contactNumber,
             IRcsUceControllerCallback c);
     int getUcePublishState(int subId);
diff --git a/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl b/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl
index 481e7f8..b99d8a7d 100644
--- a/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl
@@ -26,4 +26,5 @@
 oneway interface IPublishResponseCallback {
     void onCommandError(int code);
     void onNetworkResponse(int code, String reason);
+    void onNetworkRespHeader(int code, String reasonPhrase, int reasonHeaderCause, String reasonHeaderText);
 }
diff --git a/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl b/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl
index a141993..8cc8020 100644
--- a/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl
@@ -30,6 +30,7 @@
 oneway interface ISubscribeResponseCallback {
     void onCommandError(int code);
     void onNetworkResponse(int code, in String reason);
+    void onNetworkRespHeader(int code, String reasonPhrase, int reasonHeaderCause, String reasonHeaderText);
     void onNotifyCapabilitiesUpdate(in List<String> pidfXmls);
     void onResourceTerminated(in List<RcsContactTerminatedReason> uriTerminatedReason);
     void onTerminated(in String reason, long retryAfterMilliseconds);
diff --git a/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java
index 22985d0..65415ea 100644
--- a/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java
@@ -34,10 +34,11 @@
     }
 
     @Override
-    public void onCommandError(int code) {
+    public void onCommandError(int code) throws ImsException {
         try {
             mResponseBinder.onCommandError(code);
         } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -46,6 +47,18 @@
         try {
             mResponseBinder.onNetworkResponse(code, reason);
         } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    @Override
+    public void onNetworkResponse(int code, String reasonPhrase, int reasonHeaderCause,
+            String reasonHeaderText) throws ImsException {
+        try {
+            mResponseBinder.onNetworkRespHeader(code, reasonPhrase, reasonHeaderCause,
+                    reasonHeaderText);
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 }
diff --git a/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java
index 1fb339c..11118c0 100644
--- a/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java
@@ -40,10 +40,11 @@
     }
 
     @Override
-    public void onCommandError(int code) {
+    public void onCommandError(int code) throws ImsException {
         try {
             mResponseBinder.onCommandError(code);
         } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -52,6 +53,18 @@
         try {
             mResponseBinder.onNetworkResponse(code, reason);
         } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    @Override
+    public void onNetworkResponse(int code, String reasonPhrase, int reasonHeaderCause,
+            String reasonHeaderText) throws ImsException {
+        try {
+            mResponseBinder.onNetworkRespHeader(code, reasonPhrase, reasonHeaderCause,
+                    reasonHeaderText);
+        } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -60,6 +73,7 @@
         try {
             mResponseBinder.onNotifyCapabilitiesUpdate(pidfXmls);
         } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -69,6 +83,7 @@
         try {
             mResponseBinder.onResourceTerminated(getTerminatedReasonList(uriTerminatedReason));
         } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 
@@ -90,6 +105,7 @@
         try {
             mResponseBinder.onTerminated(reason, retryAfterMilliseconds);
         } catch (RemoteException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
         }
     }
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
index 06c35ea..5f8e93d 100644
--- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
@@ -23,6 +23,8 @@
 import com.android.ims.internal.IImsEcbm;
 import com.android.ims.internal.IImsEcbmListener;
 
+import java.util.Objects;
+
 /**
  * Base implementation of ImsEcbm, which implements stub versions of the methods
  * in the IImsEcbm AIDL. Override the methods that your implementation of ImsEcbm supports.
@@ -36,11 +38,31 @@
 public class ImsEcbmImplBase {
     private static final String TAG = "ImsEcbmImplBase";
 
+    private final Object mLock = new Object();
     private IImsEcbmListener mListener;
-    private IImsEcbm mImsEcbm = new IImsEcbm.Stub() {
+    private final IImsEcbm mImsEcbm = new IImsEcbm.Stub() {
         @Override
         public void setListener(IImsEcbmListener listener) {
-            mListener = listener;
+            synchronized (mLock) {
+                if (mListener != null && !mListener.asBinder().isBinderAlive()) {
+                    Log.w(TAG, "setListener: discarding dead Binder");
+                    mListener = null;
+                }
+                if (mListener != null && listener != null && Objects.equals(
+                        mListener.asBinder(), listener.asBinder())) {
+                    return;
+                }
+                if (listener == null) {
+                    mListener = null;
+                } else if (listener != null && mListener == null) {
+                    mListener = listener;
+                } else {
+                    // Fail fast here instead of silently overwriting the listener to another
+                    // listener due to another connection connecting.
+                    throw new IllegalStateException("ImsEcbmImplBase: Listener already set by "
+                            + "another connection.");
+                }
+            }
         }
 
         @Override
@@ -69,9 +91,13 @@
      */
     public final void enteredEcbm() {
         Log.d(TAG, "Entered ECBM.");
-        if (mListener != null) {
+        IImsEcbmListener listener;
+        synchronized (mLock) {
+            listener = mListener;
+        }
+        if (listener != null) {
             try {
-                mListener.enteredECBM();
+                listener.enteredECBM();
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -85,9 +111,13 @@
      */
     public final void exitedEcbm() {
         Log.d(TAG, "Exited ECBM.");
-        if (mListener != null) {
+        IImsEcbmListener listener;
+        synchronized (mLock) {
+            listener = mListener;
+        }
+        if (listener != null) {
             try {
-                mListener.exitedECBM();
+                listener.exitedECBM();
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
index d002903..8e961ac 100644
--- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
@@ -25,6 +25,7 @@
 import com.android.ims.internal.IImsMultiEndpoint;
 
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Base implementation of ImsMultiEndpoint, which implements stub versions of the methods
@@ -41,10 +42,32 @@
     private static final String TAG = "MultiEndpointImplBase";
 
     private IImsExternalCallStateListener mListener;
-    private IImsMultiEndpoint mImsMultiEndpoint = new IImsMultiEndpoint.Stub() {
+    private final Object mLock = new Object();
+    private final IImsMultiEndpoint mImsMultiEndpoint = new IImsMultiEndpoint.Stub() {
+
         @Override
         public void setListener(IImsExternalCallStateListener listener) throws RemoteException {
-            mListener = listener;
+            synchronized (mLock) {
+                if (mListener != null && !mListener.asBinder().isBinderAlive()) {
+                    Log.w(TAG, "setListener: discarding dead Binder");
+                    mListener = null;
+                }
+                if (mListener != null && listener != null && Objects.equals(
+                        mListener.asBinder(), listener.asBinder())) {
+                    return;
+                }
+
+                if (listener == null) {
+                    mListener = null;
+                } else if (listener != null && mListener == null) {
+                    mListener = listener;
+                } else {
+                    // Fail fast here instead of silently overwriting the listener to another
+                    // listener due to another connection connecting.
+                    throw new IllegalStateException("ImsMultiEndpointImplBase: Listener already"
+                            + " set by another connection.");
+                }
+            }
         }
 
         @Override
@@ -65,9 +88,13 @@
      */
     public final void onImsExternalCallStateUpdate(List<ImsExternalCallState> externalCallDialogs) {
         Log.d(TAG, "ims external call state update triggered.");
-        if (mListener != null) {
+        IImsExternalCallStateListener listener;
+        synchronized (mLock) {
+            listener = mListener;
+        }
+        if (listener != null) {
             try {
-                mListener.onImsExternalCallStateUpdate(externalCallDialogs);
+                listener.onImsExternalCallStateUpdate(externalCallDialogs);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
index f5219d5..83b89aa 100644
--- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
@@ -23,12 +23,14 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.telephony.ims.ImsUtListener;
+import android.util.Log;
 
 import com.android.ims.internal.IImsUt;
 import com.android.ims.internal.IImsUtListener;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * Base implementation of IMS UT interface, which implements stubs. Override these methods to
@@ -40,6 +42,7 @@
 // will break other implementations of ImsUt maintained by other ImsServices.
 @SystemApi
 public class ImsUtImplBase {
+    private static final String TAG = "ImsUtImplBase";
     /**
      * Bar all incoming calls. (See 3GPP TS 24.611)
      * @hide
@@ -116,7 +119,10 @@
      */
     public static final int INVALID_RESULT = -1;
 
-    private IImsUt.Stub mServiceImpl = new IImsUt.Stub() {
+    private final IImsUt.Stub mServiceImpl = new IImsUt.Stub() {
+        private final Object mLock = new Object();
+        private ImsUtListener mUtListener;
+
         @Override
         public void close() throws RemoteException {
             ImsUtImplBase.this.close();
@@ -202,7 +208,31 @@
 
         @Override
         public void setListener(IImsUtListener listener) throws RemoteException {
-            ImsUtImplBase.this.setListener(new ImsUtListener(listener));
+            synchronized (mLock) {
+                if (mUtListener != null
+                        && !mUtListener.getListenerInterface().asBinder().isBinderAlive()) {
+                    Log.w(TAG, "setListener: discarding dead Binder");
+                    mUtListener = null;
+                }
+                if (mUtListener != null && listener != null && Objects.equals(
+                        mUtListener.getListenerInterface().asBinder(), listener.asBinder())) {
+                    return;
+                }
+
+                if (listener == null) {
+                    mUtListener = null;
+                } else if (listener != null && mUtListener == null) {
+                    mUtListener = new ImsUtListener(listener);
+                } else {
+                    // This is a limitation of the current API surface, there can only be one
+                    // listener connected. Fail fast instead of silently overwriting the other
+                    // listener.
+                    throw new IllegalStateException("ImsUtImplBase#setListener: listener already "
+                            + "set by another connected interface!");
+                }
+            }
+
+            ImsUtImplBase.this.setListener(mUtListener);
         }
 
         @Override
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index c84e23c..ec98be6 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -24,6 +24,7 @@
 import android.annotation.SystemApi;
 import android.net.Uri;
 import android.telephony.ims.ImsException;
+import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.RcsFeature;
 import android.util.Log;
@@ -139,19 +140,48 @@
          * Provide the framework with a subsequent network response update to
          * {@link #publishCapabilities(String, PublishResponseCallback)}.
          *
-         * @param code The SIP response code sent from the network for the operation
+         * If this network response also contains a “Reason” header, then the
+         * {@link onNetworkResponse(int, String, int, String)} method should be used instead.
+         *
+         * @param sipCode The SIP response code sent from the network for the operation
          * token specified.
          * @param reason The optional reason response from the network. If there is a reason header
          * included in the response, that should take precedence over the reason provided in the
-         * status line. If the network provided no reason with the code, the string should be empty.
+         * status line. If the network provided no reason with the sip code, the string should be
+         * empty.
          * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is
          * not currently connected to the framework. This can happen if the {@link RcsFeature}
          * is not {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received
          * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases
          * when the Telephony stack has crashed.
          */
-        void onNetworkResponse(@IntRange(from = 100, to = 699) int code,
+        void onNetworkResponse(@IntRange(from = 100, to = 699) int sipCode,
                 @NonNull String reason) throws ImsException;
+
+        /**
+         * Provide the framework with a subsequent network response update to
+         * {@link #publishCapabilities(RcsContactUceCapability, int)} that also
+         * includes a reason provided in the “reason” header. See RFC3326 for more
+         * information.
+         *
+         * @param sipCode The SIP response code sent from the network.
+         * @param reasonPhrase The optional reason response from the network. If the
+         * network provided no reason with the sip code, the string should be empty.
+         * @param reasonHeaderCause The “cause” parameter of the “reason” header
+         * included in the SIP message.
+         * @param reasonHeaderText The “text” parameter of the “reason” header
+         * included in the SIP message.
+         * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is
+         * not currently connected to the framework. This can happen if the
+         * {@link RcsFeature} is not
+         * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received
+         * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in
+         * rare cases when the Telephony stack has crashed.
+         */
+        void onNetworkResponse(@IntRange(from = 100, to = 699) int sipCode,
+                @NonNull String reasonPhrase,
+                @IntRange(from = 100, to = 699) int reasonHeaderCause,
+                @NonNull String reasonHeaderText) throws ImsException;
     }
 
     /**
@@ -173,7 +203,7 @@
 
         /**
          * Send the response of a SIP OPTIONS capability exchange to the framework.
-         * @param code The SIP response code that was sent by the network in response
+         * @param sipCode The SIP response code that was sent by the network in response
          * to the request sent by {@link #sendOptionsCapabilityRequest}.
          * @param reason The optional SIP response reason sent by the network.
          * If none was sent, this should be an empty string.
@@ -186,17 +216,20 @@
          * {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare
          * cases when the Telephony stack has crashed.
          */
-        void onNetworkResponse(int code, @NonNull String reason,
+        void onNetworkResponse(int sipCode, @NonNull String reason,
                 @Nullable List<String> theirCaps) throws ImsException;
     }
 
     /**
      * Interface used by the framework to receive the response of the subscribe request.
-     * @hide
      */
     public interface SubscribeResponseCallback {
         /**
          * Notify the framework that the command associated with this callback has failed.
+         * <p>
+         * Must only be called when there was an error generating a SUBSCRIBE request due to an
+         * IMS stack error. This is a terminating event, so no other callback event will be
+         * expected after this callback.
          *
          * @param code The reason why the associated command has failed.
          * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is
@@ -211,27 +244,66 @@
         /**
          * Notify the framework of the response to the SUBSCRIBE request from
          * {@link #subscribeForCapabilities(List<Uri>, SubscribeResponseCallback)}.
+         * <p>
+         * If the carrier network responds to the SUBSCRIBE request with a 2XX response, then the
+         * framework will expect the IMS stack to call {@link #onNotifyCapabilitiesUpdate},
+         * {@link #onResourceTerminated}, and {@link #onTerminated} as required for the
+         * subsequent NOTIFY responses to the subscription.
          *
-         * @param code The SIP response code sent from the network for the operation
+         * If this network response also contains a “Reason” header, then the
+         * {@link onNetworkResponse(int, String, int, String)} method should be used instead.
+         *
+         * @param sipCode The SIP response code sent from the network for the operation
          * token specified.
          * @param reason The optional reason response from the network. If the network
-         *  provided no reason with the code, the string should be empty.
+         *  provided no reason with the sip code, the string should be empty.
          * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is
          * not currently connected to the framework. This can happen if the
          * {@link RcsFeature} is not {@link ImsFeature#STATE_READY} and the
          * {@link RcsFeature} has not received the {@link ImsFeature#onFeatureReady()} callback.
          * This may also happen in rare cases when the Telephony stack has crashed.
          */
-        void onNetworkResponse(@IntRange(from = 100, to = 699) int code,
+        void onNetworkResponse(@IntRange(from = 100, to = 699) int sipCode,
                 @NonNull String reason) throws ImsException;
 
         /**
-         * Provides the framework with latest XML PIDF documents included in the
-         * network response for the requested  contacts' capabilities requested by the
-         * Framework using {@link #requestCapabilities(List, int)}. This should be
-         * called every time a new NOTIFY event is received with new capability
+         * Notify the framework  of the response to the SUBSCRIBE request from
+         * {@link #subscribeForCapabilities(RcsContactUceCapability, int)} that also
+         * includes a reason provided in the “reason” header. See RFC3326 for more
          * information.
          *
+         * @param sipCode The SIP response code sent from the network,
+         * @param reasonPhrase The optional reason response from the network. If the
+         * network provided no reason with the sip code, the string should be empty.
+         * @param reasonHeaderCause The “cause” parameter of the “reason” header
+         * included in the SIP message.
+         * @param reasonHeaderText The “text” parameter of the “reason” header
+         * included in the SIP message.
+         * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is
+         * not currently connected to the framework. This can happen if the
+         * {@link RcsFeature} is not
+         * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received
+         * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in
+         * rare cases when the Telephony stack has crashed.
+         */
+        void onNetworkResponse(@IntRange(from = 100, to = 699) int sipCode,
+                @NonNull String reasonPhrase,
+                @IntRange(from = 100, to = 699) int reasonHeaderCause,
+                @NonNull String reasonHeaderText) throws ImsException;
+
+        /**
+         * Notify the framework of the latest XML PIDF documents included in the network response
+         * for the requested contacts' capabilities requested by the Framework using
+         * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)}.
+         * <p>
+         * The expected format for the PIDF XML is defined in RFC3861. Each XML document must be a
+         * "application/pidf+xml" object and start with a root <presence> element. For NOTIFY
+         * responses that contain RLMI information and potentially multiple PIDF XMLs, each
+         * PIDF XML should be separated and added as a separate item in the List. This should be
+         * called every time a new NOTIFY event is received with new capability information.
+         *
+         * @param pidfXmls The list of the PIDF XML data for the contact URIs that it subscribed
+         * for.
          * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is
          * not currently connected to the framework.
          * This can happen if the {@link RcsFeature} is not {@link ImsFeature#STATE_READY} and the
@@ -242,21 +314,42 @@
         void onNotifyCapabilitiesUpdate(@NonNull List<String> pidfXmls) throws ImsException;
 
         /**
-         * A resource in the resource list for the presence subscribe event has been terminated.
+         * Notify the framework that a resource in the RLMI XML contained in the NOTIFY response
+         * for the ongoing SUBSCRIBE dialog has been terminated.
          * <p>
-         * This allows the framework to know that there will not be any capability information for
-         * a specific contact URI that they subscribed for.
+         * This will be used to notify the framework that a contact URI that the IMS stack has
+         * subscribed to on the Resource List Server has been terminated as well as the reason why.
+         * Usually this means that there will not be any capability information for the contact URI
+         * that they subscribed for. See RFC 4662 for more information.
+         *
+         * @param uriTerminatedReason The contact URIs which have been terminated. Each pair in the
+         * list is the contact URI and its terminated reason.
+         * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is
+         * not currently connected to the framework.
+         * This can happen if the {@link RcsFeature} is not {@link ImsFeature#STATE_READY} and the
+         * {@link RcsFeature} {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not
+         * received the {@link ImsFeature#onFeatureReady()} callback. This may also happen in
+         * rare cases when the Telephony stack has crashed.
          */
         void onResourceTerminated(
                 @NonNull List<Pair<Uri, String>> uriTerminatedReason) throws ImsException;
 
         /**
-         * The subscription associated with a previous #requestCapabilities operation
-         * has been terminated. This will mostly be due to the subscription expiring,
-         * but may also happen due to an error.
-         * <p>
-         * This allows the framework to know that there will no longer be any
-         * capability updates for the requested operationToken.
+         * The subscription associated with a previous
+         * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)}
+         * operation has been terminated. This will mostly be due to the network sending a final
+         * NOTIFY response due to the subscription expiring, but this may also happen due to a
+         * network error.
+         *
+         * @param reason The reason for the request being unable to process.
+         * @param retryAfterMilliseconds The time in milliseconds the requesting application should
+         * wait before retrying, if non-zero.
+         * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is
+         * not currently connected to the framework.
+         * This can happen if the {@link RcsFeature} is not {@link ImsFeature#STATE_READY} and the
+         * {@link RcsFeature} {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not
+         * received the {@link ImsFeature#onFeatureReady()} callback. This may also happen in
+         * rare cases when the Telephony stack has crashed.
          */
         void onTerminated(@NonNull String reason, long retryAfterMilliseconds) throws ImsException;
     }
@@ -278,18 +371,23 @@
     /**
      * The user capabilities of one or multiple contacts have been requested by the framework.
      * <p>
+     * The implementer must follow up this call with an
+     * {@link SubscribeResponseCallback#onCommandError} call to indicate this operation has failed.
      * The response from the network to the SUBSCRIBE request must be sent back to the framework
-     * using {@link #onSubscribeNetworkResponse(int, String, int)}. As NOTIFY requests come in from
-     * the network, the requested contact’s capabilities should be sent back to the framework using
-     * {@link #onSubscribeNotifyRequest} and {@link onSubscribeResourceTerminated}
+     * using {@link SubscribeResponseCallback#onNetworkResponse(int, String)}.
+     * As NOTIFY requests come in from the network, the requested contact’s capabilities should be
+     * sent back to the framework using
+     * {@link SubscribeResponseCallback#onNotifyCapabilitiesUpdate(List<String>}) and
+     * {@link SubscribeResponseCallback#onResourceTerminated(List<Pair<Uri, String>>)}
      * should be called with the presence information for the contacts specified.
      * <p>
-     * Once the subscription is terminated, {@link #onSubscriptionTerminated} must be called for
-     * the framework to finish listening for NOTIFY responses.
+     * Once the subscription is terminated,
+     * {@link SubscribeResponseCallback#onTerminated(String, long)} must be called for the
+     * framework to finish listening for NOTIFY responses.
+     *
      * @param uris A {@link List} of the {@link Uri}s that the framework is requesting the UCE
      * capabilities for.
      * @param cb The callback of the subscribe request.
-     * @hide
      */
     // executor used is defined in the constructor.
     @SuppressLint("ExecutorRegistration")
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 2d3c8f2..0cd17da3 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2172,7 +2172,7 @@
      */
     String getMmsUAProfUrl(int subId);
 
-    void setMobileDataPolicyEnabledStatus(int subscriptionId, int policy, boolean enabled);
+    void setMobileDataPolicyEnabled(int subscriptionId, int policy, boolean enabled);
 
     boolean isMobileDataPolicyEnabled(int subscriptionId, int policy);
 
@@ -2350,6 +2350,26 @@
      */
     String getMobileProvisioningUrl();
 
+    /*
+     * Remove the EAB contacts from the EAB database.
+     */
+    int removeContactFromEab(int subId, String contacts);
+
+    /**
+     * Get the EAB contact from the EAB database.
+     */
+    String getContactFromEab(String contact);
+
+    /*
+     * Check whether the device supports RCS User Capability Exchange or not.
+     */
+    boolean getDeviceUceEnabled();
+
+    /*
+     * Set the device supports RCS User Capability Exchange.
+     */
+     void setDeviceUceEnabled(boolean isEnabled);
+
     /**
      * Set a SignalStrengthUpdateRequest to receive notification when Signal Strength breach the
      * specified thresholds.
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 02c75ed..39dc9c2 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -16,6 +16,7 @@
     name: "ApkVerityTest",
     srcs: ["src/**/*.java"],
     libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+    static_libs: ["frameworks-base-hostutils"],
     test_suites: ["general-tests", "vts"],
     target_required: [
         "block_device_writer_module",
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 629b6c7..d0eb9be 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -21,10 +21,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.RootPermissionTest;
 
+import com.android.fsverity.AddFsVerityCertRule;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -35,6 +35,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -85,40 +86,25 @@
     private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";
     private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der";
 
-    private static final String APK_VERITY_STANDARD_MODE = "2";
-
     /** Only 4K page is supported by fs-verity currently. */
     private static final int FSVERITY_PAGE_SIZE = 4096;
 
+    @Rule
+    public final AddFsVerityCertRule mAddFsVerityCertRule =
+            new AddFsVerityCertRule(this, CERT_PATH);
+
     private ITestDevice mDevice;
-    private String mKeyId;
 
     @Before
     public void setUp() throws DeviceNotAvailableException {
         mDevice = getDevice();
 
-        String apkVerityMode = mDevice.getProperty("ro.apk_verity.mode");
-        assumeTrue(mDevice.getLaunchApiLevel() >= 30
-                || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
-
-        mKeyId = expectRemoteCommandToSucceed(
-                "mini-keyctl padd asymmetric fsv_test .fs-verity < " + CERT_PATH).trim();
-        if (!mKeyId.matches("^\\d+$")) {
-            String keyId = mKeyId;
-            mKeyId = null;
-            fail("Key ID is not decimal: " + keyId);
-        }
-
         uninstallPackage(TARGET_PACKAGE);
     }
 
     @After
     public void tearDown() throws DeviceNotAvailableException {
         uninstallPackage(TARGET_PACKAGE);
-
-        if (mKeyId != null) {
-            expectRemoteCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
-        }
     }
 
     @Test
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index c945aea..0792e8b 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -18,28 +18,7 @@
     name: "FlickerTests",
     srcs: ["src/**/*.java", "src/**/*.kt"],
     manifest: "AndroidManifest.xml",
-    test_config: "AndroidTestPhysicalDevices.xml",
-    platform_apis: true,
-    certificate: "platform",
-    test_suites: ["device-tests"],
-    libs: ["android.test.runner"],
-    static_libs: [
-        "androidx.test.ext.junit",
-        "flickertestapplib",
-        "flickerlib",
-        "truth-prebuilt",
-        "launcher-helper-lib",
-        "launcher-aosp-tapl",
-        "platform-test-annotations",
-    ],
-}
-
-
-android_test {
-    name: "FlickerTestsVirtual",
-    srcs: ["src/**/*.java", "src/**/*.kt"],
-    manifest: "AndroidManifest.xml",
-    test_config: "AndroidTestVirtualDevices.xml",
+    test_config: "AndroidTest.xml",
     platform_apis: true,
     certificate: "platform",
     test_suites: ["device-tests"],
diff --git a/tests/FlickerTests/AndroidTestPhysicalDevices.xml b/tests/FlickerTests/AndroidTest.xml
similarity index 95%
rename from tests/FlickerTests/AndroidTestPhysicalDevices.xml
rename to tests/FlickerTests/AndroidTest.xml
index b1cee5c..e68fbd8 100644
--- a/tests/FlickerTests/AndroidTestPhysicalDevices.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -25,7 +25,6 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.server.wm.flicker"/>
-        <option name="include-annotation" value="androidx.test.filters.RequiresDevice" />
         <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
         <option name="shell-timeout" value="6600s" />
         <option name="test-timeout" value="6600s" />
diff --git a/tests/FlickerTests/AndroidTestVirtualDevices.xml b/tests/FlickerTests/AndroidTestVirtualDevices.xml
deleted file mode 100644
index 9a5413a..0000000
--- a/tests/FlickerTests/AndroidTestVirtualDevices.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2018 Google Inc. All Rights Reserved.
- -->
-<configuration description="Runs WindowManager Flicker Tests">
-    <option name="test-tag" value="FlickerTests" />
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <!-- keeps the screen on during tests -->
-        <option name="screen-always-on" value="on" />
-        <!-- prevents the phone from restarting -->
-        <option name="force-skip-system-props" value="true" />
-        <!-- set WM tracing verbose level to all -->
-        <option name="run-command" value="cmd window tracing level all" />
-        <!-- inform WM to log all transactions -->
-        <option name="run-command" value="cmd window tracing transaction" />
-        <!-- restart launcher to activate TAPL -->
-        <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner">
-        <!-- reboot the device to teardown any crashed tests -->
-        <option name="cleanup-action" value="REBOOT" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="FlickerTests.apk"/>
-        <option name="test-file-name" value="FlickerTestApp.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.server.wm.flicker"/>
-        <option name="exclude-annotation" value="androidx.test.filters.RequiresDevice" />
-        <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
-        <option name="shell-timeout" value="6600s" />
-        <option name="test-timeout" value="6000s" />
-        <option name="hidden-api-checks" value="false" />
-    </test>
-    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
-        <option name="directory-keys" value="/sdcard/flicker" />
-        <option name="collect-on-run-ended-only" value="true" />
-        <option name="clean-up" value="true" />
-    </metrics_collector>
-</configuration>
diff --git a/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING
deleted file mode 100644
index db251b9..0000000
--- a/tests/FlickerTests/TEST_MAPPING
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "postsubmit": [
-    // Run tests on real device
-    {
-      "name": "FlickerTests",
-      "keywords": ["primary-device"]
-    },
-    // Also run the tests in the cloud
-    {
-      "name": "FlickerTests"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index ba12fbe..89c6663 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -17,37 +17,40 @@
 package com.android.server.wm.flicker
 
 import android.platform.helpers.IAppHelper
-import com.android.server.wm.flicker.dsl.EventLogAssertionBuilder
-import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
-import com.android.server.wm.flicker.dsl.WmAssertionBuilder
+import com.android.server.wm.flicker.dsl.EventLogAssertionBuilderLegacy
+import com.android.server.wm.flicker.dsl.LayersAssertionBuilderLegacy
+import com.android.server.wm.flicker.dsl.WmAssertionBuilderLegacy
 import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_LAYER_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_WINDOW_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_LAYER_NAME
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME
 
-const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
-const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
+const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
 const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
 const val WALLPAPER_TITLE = "Wallpaper"
 
 @JvmOverloads
-fun WmAssertionBuilder.statusBarWindowIsAlwaysVisible(
+fun WmAssertionBuilderLegacy.statusBarWindowIsAlwaysVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     all("statusBarWindowIsAlwaysVisible", bugId, enabled) {
-        this.showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE)
+        this.showsAboveAppWindow(STATUS_BAR_WINDOW_NAME)
     }
 }
 
 @JvmOverloads
-fun WmAssertionBuilder.navBarWindowIsAlwaysVisible(
+fun WmAssertionBuilderLegacy.navBarWindowIsAlwaysVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     all("navBarWindowIsAlwaysVisible", bugId, enabled) {
-        this.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE)
+        this.showsAboveAppWindow(NAV_BAR_WINDOW_NAME)
     }
 }
 
-fun WmAssertionBuilder.visibleWindowsShownMoreThanOneConsecutiveEntry(
+fun WmAssertionBuilderLegacy.visibleWindowsShownMoreThanOneConsecutiveEntry(
     ignoreWindows: List<String> = emptyList(),
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -57,7 +60,7 @@
     }
 }
 
-fun WmAssertionBuilder.launcherReplacesAppWindowAsTopWindow(
+fun WmAssertionBuilderLegacy.launcherReplacesAppWindowAsTopWindow(
     testApp: IAppHelper,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -69,7 +72,7 @@
     }
 }
 
-fun WmAssertionBuilder.wallpaperWindowBecomesVisible(
+fun WmAssertionBuilderLegacy.wallpaperWindowBecomesVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -80,7 +83,7 @@
     }
 }
 
-fun WmAssertionBuilder.wallpaperWindowBecomesInvisible(
+fun WmAssertionBuilderLegacy.wallpaperWindowBecomesInvisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -91,7 +94,7 @@
     }
 }
 
-fun WmAssertionBuilder.appWindowAlwaysVisibleOnTop(
+fun WmAssertionBuilderLegacy.appWindowAlwaysVisibleOnTop(
     packageName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -101,7 +104,7 @@
     }
 }
 
-fun WmAssertionBuilder.appWindowBecomesVisible(
+fun WmAssertionBuilderLegacy.appWindowBecomesVisible(
     appName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -113,8 +116,20 @@
     }
 }
 
+fun WmAssertionBuilderLegacy.appWindowBecomesInVisible(
+    appName: String,
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("appWindowBecomesInVisible", bugId, enabled) {
+        this.showsAppWindow(appName)
+                .then()
+                .hidesAppWindow(appName)
+    }
+}
+
 @JvmOverloads
-fun LayersAssertionBuilder.noUncoveredRegions(
+fun LayersAssertionBuilderLegacy.noUncoveredRegions(
     beginRotation: Int,
     endRotation: Int = beginRotation,
     allStates: Boolean = true,
@@ -144,49 +159,49 @@
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.navBarLayerIsAlwaysVisible(
+fun LayersAssertionBuilderLegacy.navBarLayerIsAlwaysVisible(
     rotatesScreen: Boolean = false,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     if (rotatesScreen) {
         all("navBarLayerIsAlwaysVisible", bugId, enabled) {
-            this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+            this.showsLayer(NAV_BAR_LAYER_NAME)
                     .then()
-                    .hidesLayer(NAVIGATION_BAR_WINDOW_TITLE)
+                    .hidesLayer(NAV_BAR_LAYER_NAME)
                     .then()
-                    .showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+                    .showsLayer(NAV_BAR_LAYER_NAME)
         }
     } else {
         all("navBarLayerIsAlwaysVisible", bugId, enabled) {
-            this.showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
+            this.showsLayer(NAV_BAR_LAYER_NAME)
         }
     }
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.statusBarLayerIsAlwaysVisible(
+fun LayersAssertionBuilderLegacy.statusBarLayerIsAlwaysVisible(
     rotatesScreen: Boolean = false,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
     if (rotatesScreen) {
         all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
-            this.showsLayer(STATUS_BAR_WINDOW_TITLE)
+            this.showsLayer(STATUS_BAR_LAYER_NAME)
                     .then()
-                    hidesLayer(STATUS_BAR_WINDOW_TITLE)
+                    hidesLayer(STATUS_BAR_LAYER_NAME)
                     .then()
-                    .showsLayer(STATUS_BAR_WINDOW_TITLE)
+                    .showsLayer(STATUS_BAR_LAYER_NAME)
         }
     } else {
         all("statusBarLayerIsAlwaysVisible", bugId, enabled) {
-            this.showsLayer(STATUS_BAR_WINDOW_TITLE)
+            this.showsLayer(STATUS_BAR_LAYER_NAME)
         }
     }
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.navBarLayerRotatesAndScales(
+fun LayersAssertionBuilderLegacy.navBarLayerRotatesAndScales(
     beginRotation: Int,
     endRotation: Int = beginRotation,
     bugId: Int = 0,
@@ -196,21 +211,21 @@
     val endingPos = WindowUtils.getNavigationBarPosition(endRotation)
 
     start("navBarLayerRotatesAndScales_StartingPos", bugId, enabled) {
-        this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+        this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos)
     }
     end("navBarLayerRotatesAndScales_EndingPost", bugId, enabled) {
-        this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, endingPos)
+        this.hasVisibleRegion(NAV_BAR_LAYER_NAME, endingPos)
     }
 
     if (startingPos == endingPos) {
         all("navBarLayerRotatesAndScales", enabled = false, bugId = 167747321) {
-            this.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
+            this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos)
         }
     }
 }
 
 @JvmOverloads
-fun LayersAssertionBuilder.statusBarLayerRotatesScales(
+fun LayersAssertionBuilderLegacy.statusBarLayerRotatesScales(
     beginRotation: Int,
     endRotation: Int = beginRotation,
     bugId: Int = 0,
@@ -220,14 +235,14 @@
     val endingPos = WindowUtils.getStatusBarPosition(endRotation)
 
     start("statusBarLayerRotatesScales_StartingPos", bugId, enabled) {
-        this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
+        this.hasVisibleRegion(STATUS_BAR_LAYER_NAME, startingPos)
     }
     end("statusBarLayerRotatesScales_EndingPos", bugId, enabled) {
-        this.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, endingPos)
+        this.hasVisibleRegion(STATUS_BAR_LAYER_NAME, endingPos)
     }
 }
 
-fun LayersAssertionBuilder.visibleLayersShownMoreThanOneConsecutiveEntry(
+fun LayersAssertionBuilderLegacy.visibleLayersShownMoreThanOneConsecutiveEntry(
     ignoreLayers: List<String> = emptyList(),
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -237,7 +252,7 @@
     }
 }
 
-fun LayersAssertionBuilder.appLayerReplacesWallpaperLayer(
+fun LayersAssertionBuilderLegacy.appLayerReplacesWallpaperLayer(
     appName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -249,7 +264,7 @@
     }
 }
 
-fun LayersAssertionBuilder.wallpaperLayerReplacesAppLayer(
+fun LayersAssertionBuilderLegacy.wallpaperLayerReplacesAppLayer(
     testApp: IAppHelper,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -261,7 +276,7 @@
     }
 }
 
-fun LayersAssertionBuilder.layerAlwaysVisible(
+fun LayersAssertionBuilderLegacy.layerAlwaysVisible(
     packageName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -271,7 +286,7 @@
     }
 }
 
-fun LayersAssertionBuilder.layerBecomesVisible(
+fun LayersAssertionBuilderLegacy.layerBecomesVisible(
     packageName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -283,7 +298,7 @@
     }
 }
 
-fun LayersAssertionBuilder.layerBecomesInvisible(
+fun LayersAssertionBuilderLegacy.layerBecomesInvisible(
     packageName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -295,7 +310,7 @@
     }
 }
 
-fun EventLogAssertionBuilder.focusChanges(
+fun EventLogAssertionBuilderLegacy.focusChanges(
     vararg windows: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -305,7 +320,7 @@
     }
 }
 
-fun EventLogAssertionBuilder.focusDoesNotChange(
+fun EventLogAssertionBuilderLegacy.focusDoesNotChange(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 1d69fe4..b5fd4a5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -20,7 +20,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -56,18 +55,16 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class CloseAppBackButtonTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = SimpleAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 5) { configuration ->
                     withTestName { buildTestTag("closeAppBackButton", configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index f1d08c2..584e4b1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -20,7 +20,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
@@ -55,18 +54,16 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class CloseAppHomeButtonTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = SimpleAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 5) { configuration ->
                     withTestName { buildTestTag("closeAppHomeButton", configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index b569eda..1a47449 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -41,6 +41,9 @@
         wmHelper.waitForRotation(rotation)
         wmHelper.waitForNavBarStatusBarVisible()
         wmHelper.waitForAppTransitionIdle()
+
+        // Ensure WindowManagerService wait until all animations have completed
+        instrumentation.getUiAutomation().syncInputTransactions()
     } catch (e: RemoteException) {
         throw RuntimeException(e)
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index d3eefd0..fde97ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -21,7 +21,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
@@ -55,18 +54,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 178015460)
 class CloseImeAutoOpenWindowToAppTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 5) { configuration ->
                     val testApp = ImeAppAutoFocusHelper(instrumentation,
                         configuration.startRotation)
                     withTestName { buildTestTag("imeToAppAutoOpen", configuration) }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index f5bb8e1..ab7c08f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -20,7 +20,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
@@ -53,18 +52,16 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class CloseImeAutoOpenWindowToHomeTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 5) { configuration ->
                     val testApp = ImeAppAutoFocusHelper(instrumentation,
                         configuration.startRotation)
                     withTestName {
@@ -114,7 +111,8 @@
                                 enabled = !configuration.startRotation.isRotated())
                             statusBarLayerIsAlwaysVisible(
                                 enabled = !configuration.startRotation.isRotated())
-                            visibleLayersShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
+                            visibleLayersShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE),
+                                enabled = !configuration.startRotation.isRotated())
 
                             imeLayerBecomesInvisible()
                             imeAppLayerBecomesInvisible(testApp)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index a363192..0503cce0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -21,7 +21,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -54,10 +53,8 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 178015460)
 class CloseImeWindowToAppTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
@@ -65,8 +62,8 @@
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = ImeAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 5) { configuration ->
                     withTestName { buildTestTag("imeToApp", configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 59e375f..9cb075b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -21,7 +21,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -54,18 +53,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 178015460)
 class CloseImeWindowToHomeTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = ImeAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 5) { configuration ->
                     withTestName { buildTestTag("imeToHome", configuration) }
                     repeat { configuration.repetitions }
                     setup {
@@ -114,7 +111,8 @@
                                 enabled = !configuration.startRotation.isRotated())
                             navBarLayerIsAlwaysVisible()
                             statusBarLayerIsAlwaysVisible()
-                            visibleLayersShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE))
+                            visibleLayersShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE),
+                                enabled = false)
 
                             imeLayerBecomesInvisible()
                             imeAppLayerBecomesInvisible(testApp)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index 96c2009..c775cb8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -17,13 +17,13 @@
 package com.android.server.wm.flicker.ime
 
 import android.platform.helpers.IAppHelper
-import com.android.server.wm.flicker.dsl.LayersAssertionBuilder
-import com.android.server.wm.flicker.dsl.WmAssertionBuilder
+import com.android.server.wm.flicker.dsl.LayersAssertionBuilderLegacy
+import com.android.server.wm.flicker.dsl.WmAssertionBuilderLegacy
 
 const val IME_WINDOW_TITLE = "InputMethod"
 
 @JvmOverloads
-fun LayersAssertionBuilder.imeLayerBecomesVisible(
+fun LayersAssertionBuilderLegacy.imeLayerBecomesVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -34,7 +34,7 @@
     }
 }
 
-fun LayersAssertionBuilder.imeLayerBecomesInvisible(
+fun LayersAssertionBuilderLegacy.imeLayerBecomesInvisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -45,7 +45,7 @@
     }
 }
 
-fun LayersAssertionBuilder.imeAppLayerIsAlwaysVisible(
+fun LayersAssertionBuilderLegacy.imeAppLayerIsAlwaysVisible(
     testApp: IAppHelper,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -55,7 +55,7 @@
     }
 }
 
-fun WmAssertionBuilder.imeAppWindowIsAlwaysVisible(
+fun WmAssertionBuilderLegacy.imeAppWindowIsAlwaysVisible(
     testApp: IAppHelper,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -65,7 +65,7 @@
     }
 }
 
-fun WmAssertionBuilder.imeWindowBecomesVisible(
+fun WmAssertionBuilderLegacy.imeWindowBecomesVisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -76,7 +76,7 @@
     }
 }
 
-fun WmAssertionBuilder.imeWindowBecomesInvisible(
+fun WmAssertionBuilderLegacy.imeWindowBecomesInvisible(
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
@@ -87,7 +87,7 @@
     }
 }
 
-fun WmAssertionBuilder.imeAppWindowBecomesVisible(
+fun WmAssertionBuilderLegacy.imeAppWindowBecomesVisible(
     windowName: String,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -99,7 +99,7 @@
     }
 }
 
-fun WmAssertionBuilder.imeAppWindowBecomesInvisible(
+fun WmAssertionBuilderLegacy.imeAppWindowBecomesInvisible(
     testApp: IAppHelper,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
@@ -111,7 +111,7 @@
     }
 }
 
-fun LayersAssertionBuilder.imeAppLayerBecomesInvisible(
+fun LayersAssertionBuilderLegacy.imeAppLayerBecomesInvisible(
     testApp: IAppHelper,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index ff07398..13c6cd7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -21,7 +21,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -57,18 +56,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @FlakyTest(bugId = 178015460)
 class OpenImeWindowTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = ImeAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 5) { configuration ->
                     withTestName { buildTestTag("openIme", configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index d2d5960..6cc64df 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -20,7 +20,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
@@ -58,18 +57,16 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class ReOpenImeWindowTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testAppComponentName = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 1)
-                    .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 1) { configuration ->
                         val testApp = ImeAppAutoFocusHelper(instrumentation,
                             configuration.startRotation)
                         withTestName { buildTestTag("reOpenImeAutoFocus", configuration) }
@@ -90,7 +87,6 @@
                         transitions {
                             device.reopenAppFromOverview()
                             wmHelper.waitImeWindowShown()
-                            // wmHelper.waitForFullScreenApp(testAppComponentName)
                         }
                         teardown {
                             test {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
index ba2ee5f..1bd1190 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
@@ -17,9 +17,9 @@
 package com.android.server.wm.flicker.launch
 
 import android.platform.helpers.IAppHelper
-import com.android.server.wm.flicker.dsl.WmAssertionBuilder
+import com.android.server.wm.flicker.dsl.WmAssertionBuilderLegacy
 
-fun WmAssertionBuilder.appWindowReplacesLauncherAsTopWindow(
+fun WmAssertionBuilderLegacy.appWindowReplacesLauncherAsTopWindow(
     testApp: IAppHelper,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index cf25987..010ebf2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -20,7 +20,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.endRotation
@@ -57,18 +56,16 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class OpenAppColdTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = SimpleAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation) { configuration ->
                     withTestName { buildTestTag("openAppCold", testApp, configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index c957b3f..5e08921 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -20,7 +20,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.endRotation
@@ -58,18 +57,16 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class OpenAppFromOverviewTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = SimpleAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation, repetitions = 5) { configuration ->
                     withTestName { buildTestTag("openAppFromOverview", configuration) }
                     repeat { configuration.repetitions }
                     setup {
@@ -118,7 +115,7 @@
                             navBarLayerIsAlwaysVisible(
                                 enabled = Surface.ROTATION_0 == configuration.endRotation)
                             visibleLayersShownMoreThanOneConsecutiveEntry(
-                                enabled = Surface.ROTATION_0 == configuration.endRotation)
+                                enabled = false)
 
                             appLayerReplacesWallpaperLayer(testApp.`package`)
                         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index fd99be2..092cd4d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -20,7 +20,6 @@
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.endRotation
@@ -56,19 +55,15 @@
 @RequiresDevice
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppWarmTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+class OpenAppWarmTest(testSpec: FlickerTestRunnerFactory.TestSpec) : FlickerTestRunner(testSpec) {
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): List<Array<Any>> {
             val instrumentation = InstrumentationRegistry.getInstrumentation()
             val testApp = SimpleAppHelper(instrumentation)
-            return FlickerTestRunnerFactory(instrumentation)
-                .buildTest { configuration ->
+            return FlickerTestRunnerFactory.getInstance()
+                .buildTest(instrumentation) { configuration ->
                     withTestName { buildTestTag("openAppWarm", testApp, configuration) }
                     repeat { configuration.repetitions }
                     setup {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index fe3ab04..1c44b21 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -20,7 +20,6 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
@@ -54,10 +53,8 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class ChangeAppRotationTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object : RotationTransition(InstrumentationRegistry.getInstrumentation()) {
         override val testApp: StandardAppHelper
             get() = SimpleAppHelper(instrumentation)
@@ -66,7 +63,7 @@
 
         private const val SCREENSHOT_LAYER = "RotationLayer"
 
-        @Parameterized.Parameters(name = "{0}")
+        @Parameterized.Parameters(name = "{0}1}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
             val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
@@ -119,8 +116,8 @@
                 }
             }
 
-            return FlickerTestRunnerFactory(instrumentation, repetitions = 5)
-                .buildRotationTest(transition, testSpec)
+            return FlickerTestRunnerFactory.getInstance()
+                .buildRotationTest(instrumentation, transition, testSpec, repetitions = 5)
         }
     }
 }
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index e25c734..f04131b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -20,7 +20,6 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.RequiresDevice
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
 import com.android.server.wm.flicker.FlickerTestRunner
 import com.android.server.wm.flicker.FlickerTestRunnerFactory
 import com.android.server.wm.flicker.endRotation
@@ -32,7 +31,6 @@
 import com.android.server.wm.flicker.layerAlwaysVisible
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.isRotated
 import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -58,10 +56,8 @@
 @RunWith(Parameterized::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 class SeamlessAppRotationTest(
-    testName: String,
-    flickerProvider: () -> Flicker,
-    cleanUp: Boolean
-) : FlickerTestRunner(testName, flickerProvider, cleanUp) {
+    testSpec: FlickerTestRunnerFactory.TestSpec
+) : FlickerTestRunner(testSpec) {
     companion object : RotationTransition(InstrumentationRegistry.getInstrumentation()) {
         override val testApp: StandardAppHelper
             get() = SeamlessRotationAppHelper(instrumentation)
@@ -70,6 +66,8 @@
             ActivityOptions.EXTRA_STARVE_UI_THREAD to configuration.starveUiThread.toString()
         )
 
+        private val testFactory = FlickerTestRunnerFactory.getInstance()
+
         private val Bundle.starveUiThread
             get() = this.getBoolean(ActivityOptions.EXTRA_STARVE_UI_THREAD, false)
 
@@ -80,8 +78,8 @@
         }
 
         @JvmStatic
-        private fun FlickerTestRunnerFactory.getConfigurations(): List<Bundle> {
-            return this.getConfigRotationTests().flatMap {
+        private fun getConfigurations(): List<Bundle> {
+            return testFactory.getConfigRotationTests().flatMap {
                 val defaultRun = it.createConfig(starveUiThread = false)
                 val busyUiRun = it.createConfig(starveUiThread = true)
                 listOf(defaultRun, busyUiRun)
@@ -91,8 +89,7 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
-            val factory = FlickerTestRunnerFactory(instrumentation, repetitions = 2)
-            val configurations = factory.getConfigurations()
+            val configurations = getConfigurations()
             val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration ->
                 withTestName {
                     val extra = if (configuration.starveUiThread) {
@@ -161,7 +158,8 @@
                 }
             }
 
-            return factory.buildRotationTest(transition, testSpec, configurations)
+            return testFactory.buildRotationTest(instrumentation, transition, testSpec,
+                deviceConfigurations = configurations, repetitions = 2)
         }
     }
 }
\ No newline at end of file
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 104758d..5381009 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -98,6 +98,8 @@
     private final TestClock mTestClock = new TestClock();
     private TestLooper mTestLooper;
     private Context mSpyContext;
+    // Keep track of all created watchdogs to apply device config changes
+    private List<PackageWatchdog> mAllocatedWatchdogs;
     @Mock
     private ConnectivityModuleConnector mConnectivityModuleConnector;
     @Mock
@@ -112,7 +114,8 @@
         MockitoAnnotations.initMocks(this);
         new File(InstrumentationRegistry.getContext().getFilesDir(),
                 "package-watchdog.xml").delete();
-        adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG);
+        adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG,
+                Manifest.permission.WRITE_DEVICE_CONFIG);
         mTestLooper = new TestLooper();
         mSpyContext = spy(InstrumentationRegistry.getContext());
         when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
@@ -157,12 +160,23 @@
                     return storedValue == null ? defaultValue : Long.parseLong(storedValue);
                 }
         ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
+                Boolean.toString(true), false);
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT), false);
+
+        mAllocatedWatchdogs = new ArrayList<>();
     }
 
     @After
     public void tearDown() throws Exception {
         dropShellPermissions();
         mSession.finishMocking();
+        mAllocatedWatchdogs.clear();
     }
 
     @Test
@@ -611,10 +625,6 @@
      */
     @Test
     public void testExplicitHealthCheckStateChanges() throws Exception {
-        adoptShellPermissions(
-                Manifest.permission.WRITE_DEVICE_CONFIG,
-                Manifest.permission.READ_DEVICE_CONFIG);
-
         TestController controller = new TestController();
         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
@@ -807,9 +817,6 @@
     /** Test default values are used when device property is invalid. */
     @Test
     public void testInvalidConfig_watchdogTriggerFailureCount() {
-        adoptShellPermissions(
-                Manifest.permission.WRITE_DEVICE_CONFIG,
-                Manifest.permission.READ_DEVICE_CONFIG);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
                 Integer.toString(-1), /*makeDefault*/false);
@@ -835,9 +842,6 @@
     /** Test default values are used when device property is invalid. */
     @Test
     public void testInvalidConfig_watchdogTriggerDurationMillis() {
-        adoptShellPermissions(
-                Manifest.permission.WRITE_DEVICE_CONFIG,
-                Manifest.permission.READ_DEVICE_CONFIG);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
                 Integer.toString(2), /*makeDefault*/false);
@@ -850,7 +854,6 @@
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
-        mTestLooper.dispatchAll();
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -862,7 +865,6 @@
 
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
-        mTestLooper.dispatchAll();
         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -917,9 +919,6 @@
     /** Test we are notified when enough failures are triggered within any window. */
     @Test
     public void testFailureTriggerWindow() {
-        adoptShellPermissions(
-                Manifest.permission.WRITE_DEVICE_CONFIG,
-                Manifest.permission.READ_DEVICE_CONFIG);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
                 Integer.toString(3), /*makeDefault*/false);
@@ -933,11 +932,9 @@
         // Raise 2 failures at t=0 and t=900 respectively
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
-        mTestLooper.dispatchAll();
         moveTimeForwardAndDispatch(900);
         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
-        mTestLooper.dispatchAll();
 
         // Raise 2 failures at t=1100
         moveTimeForwardAndDispatch(200);
@@ -1303,15 +1300,15 @@
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
                 PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
                 Boolean.toString(enabled), /*makeDefault*/false);
-        //give time for DeviceConfig to broadcast the property value change
-        try {
-            Thread.sleep(SHORT_DURATION);
-        } catch (InterruptedException e) {
-            fail("Thread.sleep unexpectedly failed!");
+        // Call updateConfigs() so device config changes take effect immediately
+        for (PackageWatchdog watchdog : mAllocatedWatchdogs) {
+            watchdog.updateConfigs();
         }
     }
 
     private void moveTimeForwardAndDispatch(long milliSeconds) {
+        // Exhaust all due runnables now which shouldn't be executed after time-leap
+        mTestLooper.dispatchAll();
         mTestClock.moveTimeForward(milliSeconds);
         mTestLooper.moveTimeForward(milliSeconds);
         mTestLooper.dispatchAll();
@@ -1354,6 +1351,7 @@
             verify(mConnectivityModuleConnector).registerHealthListener(
                     mConnectivityModuleCallbackCaptor.capture());
         }
+        mAllocatedWatchdogs.add(watchdog);
         return watchdog;
     }
 
diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
index e9227e94..eb04f69 100644
--- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
+++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt
@@ -131,6 +131,10 @@
         assertThat(platformCompat.isChangeEnabled(TEST_CHANGE_ID, appInfo)).isEqualTo(params.result)
     }
 
-    private fun command(command: String) =
-            FileReader(uiAutomation.executeShellCommand(command).fileDescriptor).readText()
+    private fun command(command: String): String {
+        val fileDescriptor = uiAutomation.executeShellCommand(command)
+        return String(ParcelFileDescriptor.AutoCloseInputStream(fileDescriptor).use {
+            inputStream -> inputStream.readBytes()
+        })
+    }
 }
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index 0db2b2a..7b2a07f 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -1225,4 +1225,44 @@
             InstallUtils.dropShellPermissionIdentity();
         }
     }
+
+    /**
+     * Tests an app can be rolled back to the previous signing key.
+     *
+     * <p>The rollback capability in the signing lineage allows an app to be updated to an APK
+     * signed with a previous signing key in the lineage; however this often defeats the purpose
+     * of key rotation as a compromised key could then be used to roll an app back to the previous
+     * key. To avoid requiring the rollback capability to support app rollbacks the PackageManager
+     * allows an app to be rolled back to the previous signing key if the rollback install reason
+     * is set.
+     */
+    @Test
+    public void testRollbackAfterKeyRotation() throws Exception {
+        try {
+            InstallUtils.adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES,
+                    Manifest.permission.DELETE_PACKAGES,
+                    Manifest.permission.TEST_MANAGE_ROLLBACKS,
+                    Manifest.permission.MANAGE_ROLLBACKS);
+
+            // Uninstall TestApp.A
+            Uninstall.packages(TestApp.A);
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+
+            // Install v1 of the app with the original signing key (without rollbacks enabled).
+            Install.single(TestApp.AOriginal1).commit();
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+            // Upgrade from v1 to v2 with the rotated signing key, with rollbacks enabled.
+            Install.single(TestApp.ARotated2).setEnableRollback().commit();
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+            // Roll back the app.
+            RollbackInfo available = waitForAvailableRollback(TestApp.A);
+            RollbackUtils.rollback(available.getRollbackId());
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        } finally {
+            InstallUtils.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
new file mode 100644
index 0000000..d809fe8
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2021 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.
+
+java_test_host {
+    name: "UpdatableSystemFontTest",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+    static_libs: ["frameworks-base-hostutils"],
+    test_suites: ["general-tests", "vts"],
+    data: [
+        ":NotoColorEmojiTtf",
+        ":UpdatableSystemFontTestCertDer",
+        ":UpdatableSystemFontTestNotoColorEmojiTtfFsvSig",
+        ":UpdatableSystemFontTestNotoColorEmojiV1Ttf",
+        ":UpdatableSystemFontTestNotoColorEmojiV1TtfFsvSig",
+        ":UpdatableSystemFontTestNotoColorEmojiV2Ttf",
+        ":UpdatableSystemFontTestNotoColorEmojiV2TtfFsvSig",
+    ],
+}
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
new file mode 100644
index 0000000..efe5d70
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Updatable system font integration/regression test">
+    <option name="test-suite-tag" value="apct" />
+
+    <!-- This test requires root to side load fs-verity cert. -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
+        <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV1.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV2.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf" />
+        <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="UpdatableSystemFontTest.jar" />
+    </test>
+</configuration>
diff --git a/tests/UpdatableSystemFontTest/OWNERS b/tests/UpdatableSystemFontTest/OWNERS
new file mode 100644
index 0000000..34ac813
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 24939
+
+include /graphics/java/android/graphics/fonts/OWNERS
diff --git a/tests/UpdatableSystemFontTest/TEST_MAPPING b/tests/UpdatableSystemFontTest/TEST_MAPPING
new file mode 100644
index 0000000..a5c4479
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "UpdatableSystemFontTest"
+    }
+  ]
+}
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
new file mode 100644
index 0000000..6d161a5
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 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.updatablesystemfont;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.platform.test.annotations.RootPermissionTest;
+
+import com.android.fsverity.AddFsVerityCertRule;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tests if fonts can be updated by 'cmd font'.
+ */
+@RootPermissionTest
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
+
+    private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der";
+
+    private static final Pattern PATTERN_FONT = Pattern.compile("path = ([^, \n]*)");
+    private static final String NOTO_COLOR_EMOJI_TTF = "NotoColorEmoji.ttf";
+    private static final String TEST_NOTO_COLOR_EMOJI_V1_TTF =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf";
+    private static final String TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig";
+    private static final String TEST_NOTO_COLOR_EMOJI_V2_TTF =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf";
+    private static final String TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig";
+    private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF =
+            "/data/local/tmp/NotoColorEmoji.ttf";
+    private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG =
+            "/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig";
+
+    @Rule
+    public final AddFsVerityCertRule mAddFsverityCertRule =
+            new AddFsVerityCertRule(this, CERT_PATH);
+
+    @Before
+    public void setUp() throws Exception {
+        expectRemoteCommandToSucceed("cmd font clear");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        expectRemoteCommandToSucceed("cmd font clear");
+    }
+
+    @Test
+    public void updateFont() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith("/data/fonts/files/");
+    }
+
+    @Test
+    public void updateFont_twice() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V2_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+        String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath2).startsWith("/data/fonts/files/");
+        assertThat(fontPath2).isNotEqualTo(fontPath);
+    }
+
+    @Test
+    public void updatedFont_dataFileIsImmutableAndReadable() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+        String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+        assertThat(fontPath).startsWith("/data");
+
+        expectRemoteCommandToFail("echo -n '' >> " + fontPath);
+        expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
+    }
+
+    @Test
+    public void updateFont_invalidCert() throws Exception {
+        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+    }
+
+    @Test
+    public void updateFont_downgradeFromSystem() throws Exception {
+        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+                ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG));
+    }
+
+    @Test
+    public void updateFont_downgradeFromData() throws Exception {
+        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V2_TTF, TEST_NOTO_COLOR_EMOJI_V2_TTF_FSV_SIG));
+        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+                TEST_NOTO_COLOR_EMOJI_V1_TTF, TEST_NOTO_COLOR_EMOJI_V1_TTF_FSV_SIG));
+    }
+
+    private String getFontPath(String fontFileName) throws Exception {
+        // TODO: add a dedicated command for testing.
+        String lines = expectRemoteCommandToSucceed("cmd font dump");
+        for (String line : lines.split("\n")) {
+            Matcher m = PATTERN_FONT.matcher(line);
+            if (m.find() && m.group(1).endsWith(fontFileName)) {
+                return m.group(1);
+            }
+        }
+        CLog.e("Font not found: " + fontFileName);
+        return null;
+    }
+
+    private String expectRemoteCommandToSucceed(String cmd) throws Exception {
+        CommandResult result = getDevice().executeShellV2Command(cmd);
+        assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
+                .that(result.getStatus())
+                .isEqualTo(CommandStatus.SUCCESS);
+        return result.getStdout();
+    }
+
+    private void expectRemoteCommandToFail(String cmd) throws Exception {
+        CommandResult result = getDevice().executeShellV2Command(cmd);
+        assertWithMessage("Unexpected success from `" + cmd + "`: " + result.getStderr())
+                .that(result.getStatus())
+                .isNotEqualTo(CommandStatus.SUCCESS);
+    }
+}
diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp
new file mode 100644
index 0000000..1296699
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/Android.bp
@@ -0,0 +1,82 @@
+// Copyright (C) 2021 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.
+
+filegroup {
+    name: "UpdatableSystemFontTestKeyPem",
+    srcs: ["UpdatableSystemFontTestKey.pem"],
+}
+
+filegroup {
+    name: "UpdatableSystemFontTestCertPem",
+    srcs: ["UpdatableSystemFontTestCert.pem"],
+}
+
+filegroup {
+    name: "UpdatableSystemFontTestCertDer",
+    srcs: ["UpdatableSystemFontTestCert.der"],
+}
+
+genrule_defaults {
+    name: "updatable_system_font_increment_font_revision_default",
+    tools: ["update_font_metadata"],
+    cmd: "$(location update_font_metadata) " +
+        "--input=$(in) " +
+        "--output=$(out) " +
+        "--revision=+1",
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiV1Ttf",
+    defaults: ["updatable_system_font_increment_font_revision_default"],
+    srcs: [":NotoColorEmojiTtf"],
+    out: ["UpdatableSystemFontTestNotoColorEmojiV1.ttf"],
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiV2Ttf",
+    defaults: ["updatable_system_font_increment_font_revision_default"],
+    srcs: [":UpdatableSystemFontTestNotoColorEmojiV1Ttf"],
+    out: ["UpdatableSystemFontTestNotoColorEmojiV2.ttf"],
+}
+
+genrule_defaults {
+    name: "updatable_system_font_sig_gen_default",
+    tools: ["fsverity"],
+    tool_files: [":UpdatableSystemFontTestKeyPem", ":UpdatableSystemFontTestCertPem"],
+    cmd: "$(location fsverity) sign $(in) $(out) " +
+        "--key=$(location :UpdatableSystemFontTestKeyPem) " +
+        "--cert=$(location :UpdatableSystemFontTestCertPem) " +
+        "> /dev/null",
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiTtfFsvSig",
+    defaults: ["updatable_system_font_sig_gen_default"],
+    srcs: [":NotoColorEmojiTtf"],
+    out: ["UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig"],
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiV1TtfFsvSig",
+    defaults: ["updatable_system_font_sig_gen_default"],
+    srcs: [":UpdatableSystemFontTestNotoColorEmojiV1Ttf"],
+    out: ["UpdatableSystemFontTestNotoColorEmojiV1.ttf.fsv_sig"],
+}
+
+genrule {
+    name: "UpdatableSystemFontTestNotoColorEmojiV2TtfFsvSig",
+    defaults: ["updatable_system_font_sig_gen_default"],
+    srcs: [":UpdatableSystemFontTestNotoColorEmojiV2Ttf"],
+    out: ["UpdatableSystemFontTestNotoColorEmojiV2.ttf.fsv_sig"],
+}
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der
new file mode 100644
index 0000000..f7aa15f
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.der
Binary files differ
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem
new file mode 100644
index 0000000..0cd1f66
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestCert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFOTCCAyGgAwIBAgIUFaI1D5NtwkCVM3G4bFZ6sQSb598wDQYJKoZIhvcNAQEL
+BQAwLDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdBbmRyb2lk
+MB4XDTIxMDIwMTA3MzAyNFoXDTIxMDMwMzA3MzAyNFowLDELMAkGA1UEBhMCVVMx
+CzAJBgNVBAgMAkNBMRAwDgYDVQQKDAdBbmRyb2lkMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA0U1zptc41E65ooeBPD33Mjgp6cYPydyj2Acq80Xy7lP7
+d6/2t6w7nNNl2x3n8dAhOl3de3IxTp6JI2SdoRb7obfcp+hWJoo/cxnHpr3q/u4R
+KED0rmWaOVHpGbajSTFZgN+cTbTKJbgtXm/H65x1QfO18ep/vj5fRiu1xPpJqDv/
+xuvuko2U3eC2+NayxzCWXVFrKPLx8GvzSQ3Utaug17vs7/5GqkRJgq3lk4DvmjNA
+vY8YA4RAkII1sSaceAWFEG6ztENLu2kjcxAI9qHxxBwQZit/NtFVlFGqSN3MEYjS
+M9Fz04RsUxF672QJpAgwCJDZ41rdB3hkHvOUK9PcepBsHdZq9cQ+E64+TX+jsJLu
+VouViKlYr6WYjvhfqZeRhwbj7CoEZ2DyEZKrl27fgWaidUT5LGEQLVxg90ymbimI
+6UwXRUwmRBQJBdRO4RGvngtqxRuamyjAKDDHx5YccXCX4FWLUypyQlz0asojvbJZ
+Og7DFa1qsRdGrGIRoQJ8pYnAjBJfSudr1l0mR7fZSfZc0W9ZmuROWx9Ip7aJWQnQ
+8JLtbNPuFLD2qbmg9Y1lcXJp1FvI9FcM8JsBqZNEANQwwsdTaa8gw+3W6J2SXKQP
+H+yZI/fJWWWRFADtqmpxtvXK9K+Cy1HQmg7D0IIxVPp8rrbz6TGrg+R3/N9FBWMC
+AwEAAaNTMFEwHQYDVR0OBBYEFDS48o2UAstoOyLMcgamHKrZdl3cMB8GA1UdIwQY
+MBaAFDS48o2UAstoOyLMcgamHKrZdl3cMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
+hvcNAQELBQADggIBAF7taBYAe20tWZu0pY9d4Z8il4LoJcRrKF4YiA02UizErgCF
+h4iECy6+pcu7DJUfvCh3dCWE7CDG+OnfUWTwEHVG9n8XI/ydetBUG76PZwTadI7B
+gzJ1y7/vWqJo5U6ki+sXNmq3hkgNsNZgza3LpdovkWJYeRdffM6m/bimzwYx9id8
+5mKw2PcbVZcb25r+0dCoLVJsqqCoRjdYUy/MKPutWG2bPzmaIv8KsKFN+mzlwhJH
+lpJ/LR+3NoaHrOCFG7CW/2Ihe501vmdQ2m/VKosyk0igw8WmTsY6xMbw2t77yKkD
+hnJr1NbhKeEV9gAB2BFX8nRWI7NTgp8fG78YLVz1UcbIHmYLgFoc3ezyma+CoR86
+ER20lKd4+TNnz4RtaPdZlBa0Ba3bsMtEneqlrHvcPrZ5tgGsQR9+cy3ZtTZ/LUQX
++Xuj/EoJXuuB3hkhg52zawN5n7WUe8efWHcv1jHqeIj0phcgbZ6u4fFBPsYjzDKe
+VuYHXglNOchmoBQwEaJI/TCiEgI8dcSJXSquLAXrtznVnxzT46ZMEt5LaW1/1NLx
+q//yoPdolCI0lpunh5jvIZJpUl5XMjxVSyaveQDNVqJkITWzWqIxAT5yTLtkCNlW
+c1XyzeHkpMItiJtBruExmnaTmNjlVKsXP8wQFOYbDGgXY5iHIMbgovptRyH/
+-----END CERTIFICATE-----
diff --git a/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem
new file mode 100644
index 0000000..09bb104
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/testdata/UpdatableSystemFontTestKey.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDRTXOm1zjUTrmi
+h4E8PfcyOCnpxg/J3KPYByrzRfLuU/t3r/a3rDuc02XbHefx0CE6Xd17cjFOnokj
+ZJ2hFvuht9yn6FYmij9zGcemver+7hEoQPSuZZo5UekZtqNJMVmA35xNtMoluC1e
+b8frnHVB87Xx6n++Pl9GK7XE+kmoO//G6+6SjZTd4Lb41rLHMJZdUWso8vHwa/NJ
+DdS1q6DXu+zv/kaqREmCreWTgO+aM0C9jxgDhECQgjWxJpx4BYUQbrO0Q0u7aSNz
+EAj2ofHEHBBmK3820VWUUapI3cwRiNIz0XPThGxTEXrvZAmkCDAIkNnjWt0HeGQe
+85Qr09x6kGwd1mr1xD4Trj5Nf6Owku5Wi5WIqVivpZiO+F+pl5GHBuPsKgRnYPIR
+kquXbt+BZqJ1RPksYRAtXGD3TKZuKYjpTBdFTCZEFAkF1E7hEa+eC2rFG5qbKMAo
+MMfHlhxxcJfgVYtTKnJCXPRqyiO9slk6DsMVrWqxF0asYhGhAnylicCMEl9K52vW
+XSZHt9lJ9lzRb1ma5E5bH0intolZCdDwku1s0+4UsPapuaD1jWVxcmnUW8j0Vwzw
+mwGpk0QA1DDCx1NpryDD7dbonZJcpA8f7Jkj98lZZZEUAO2qanG29cr0r4LLUdCa
+DsPQgjFU+nyutvPpMauD5Hf830UFYwIDAQABAoICAQCTVTcFCdl6MdSg4UwK0P/S
+fRCb/A0fJs67Agis6N9h/wI0NUyx7G6mLXU0si+U29KYGH0RKcgltJmKrYf8XoZR
+R3DvTTBfvs99QXd2G5hxTboMIPVcUi8nDE7PB+6XVkLP4hhP5uSpeqWNJZiQdTlh
+bKH2IgE8NQGyDpDMkPcKkvmw2GG/DiTtrwJ91fxRFRWzqN2LHMFMYWEHWtIR9Der
+xSC7q72om5s3fxvtIkUHwe5fwXvA9fbRAqezBR/9qL0LXTHowbpsuUz38SCuJD9g
+sfSlRxcsyly4pGf/FQpSiYKWcWlcSopKSzLDkyLqMc1GKlkGnu6aFJg95W63D1LS
+OaOXuYShHxLkqyhT8uQGRqDCu3E2ivb6fMxAPzJxxs3JrZvumNsqyxbp+HVF0idj
+NijMN/8Kb4KmNHG9I3SHG61tQFYDtxoMMNiHzq3fafBJnVcf6iThQdE5pGLN2OdF
+3rcSTeHI2HxhTrXtuiHmWXNk9aZ2TOhrssNZZjDkFL/KYh6G8guy+tn66YWy77VC
+id+6PBzYXTXsUauo4NWW1rLUfzT/y93IwVGpoXs7GHUUduZ6Q3PxsMTMF9IjBvqR
+JvfP84CUfGXoebVJmWyGhtW6N5ParvQxbonDitA0TPZcRyX3N8yqIGO/mb8MWA9M
+7s/xMZuksOpw5LSCoTAW6QKCAQEA9Z1WM/5XT+5HkwPBvS5aRz0SqMqPxAkZ0dNz
+O06zpXv6e2IPy40UzFWCIkyq3vWKQ5bqU1fnejUdmjvtnP+KhH6fxnQCgiunnrDq
+j1Sk2Y4gb1KyZY/C8IejOexM2qX7sfDTLI8XEvxJxVFCNmvYfnv4AX5QD8VOsjrg
+bvodLAgDSo2FVDP+mkpW7zAIoRV02l6QdZA+YcG940eqxB9sPR3/1KUHe9wUTTbJ
+FV5ahEPuXCyvRJkZ/rD5CPPZoQHfjKHxDlu7yfoatzCSYj10R+RInfqOFGVY4D/C
+2csXjymwTN4CFUnJcP410YhPFn5ekmc/E3xqPKgIDQhQ2+oivwKCAQEA2icN1iEo
+YuBJwB3pX3jrwk+1bpUXASueWhyAhSeNMrTrJ/lSgEAy9FBxJNnK1PjkABRnFhS+
+uxbC2hQdfAkNDS21PQOk6hOhebUVuBdfmKY1CL+P9y4Af0fjV1rgRGHihnZB5lKU
+1R/wFb8c4QgPwduiqDoZ5QP3dgxpZ4R3SCzuoyVelUSlgyWPoslg+yPddcnV8bTf
+BUlEOOyXgVudkSFlRpWZ+/ZAkLTj8rnrtKp8/+GtTQvJvPJHPfuSsDhI+EQVWqab
+HelMhxvIi9xOyN/OCc3Ex6JlEVa+X8EyNhQsV2sAKnld92hdEozW2uxRniIkxvL4
+CuBp/p3fWxEaXQKCAQEAw2j7RYCMnNZZ8Zhiko4HW3g2mT4XpYMMHMlbe4sBGJ8L
+yRBaurqzGmLJl1ph8+NsrpuqMMbWLn+F3sjhIjCZVxKbMbvopwHuaS4eYAya30/Z
+dFhaAL2g/dccQSBEgQzftFGC4YeydvNsCeW9hSjGZNNinGWPcwyqsNhw6Tpq7TYu
+0CjKNBTt8nlEsyYHJ4m3n2jvC+nIB+Spm+LP9Rt+9R0iBl+KFbwiFtCIqUyZPXQC
+dylCBJS+fsj0SXAg7J1d6ziIXcEUJfyrNqYZQLneAriYIcBPO+DqFfgEoVyYkNk9
+H9rd02wSLajCzsLhEWdW/KnSIEGzEDErvpqoIl8kZwKCAQEAjNO3T+sZyjKmCXqF
+xBcogsi4BAoEzsGcuOk7Yjn1Ia2/PI/r3VUUT7l6QOLD2JZPgWmqXovH0LjR0rw3
+iHHDViWSoS+wD1fa3tmyiqO0F7P7+ojHZDbzJTeAIE1PB3X1KP5AbnITGD5E25UD
+DJYKrgeeSmEvhDL6Vd+PT78ozZQL/Y/LLishebcOsXS0wYsWlMpV7XHoot34R5Mb
+/urond7kJRvASvJeHcxYdsHk0j1Y8kp6eIlKk0oICZBU0qOTH4m8C0gQTM/lkjay
+UO9IgM5RkOyfwowoGHhZ7zClvFlrgodVlRXCPkvGAYqfzLXPvnimKzSAQW07n53E
+qWIyFQKCAQAWRNG6hPCOzkNxMJ/RK7dwxZW6b4a1L3PMXTT/xrBKRIS1WNjbpkYO
+/FLIufOqJT6FQN2obM5uso3TI+R7MwH8DnTSnDDy0Hvs3CdHdtn2tapZOViF/UVv
+uCQa+/jMVKFCZ8k7pPFMIG6tB6WBA5MmJrW+8s0ouxLbRF5rZyzqOeyPdVBYYYDb
+68nGNA6GAtTQs9h7xV2tsQ1bXNP+6gqG3BgZYo+76xKITddT6s9aaC++LVBnPOdq
+LHV7gUvoBkVLjIp4L3Fb/DGMCcOVMCxmFlRBn+RBlV7slehvgq+Ywz2GHWLr+O/z
+V2NAtwvCfZE2Do/4f2mpHnamhS6AvrDe
+-----END PRIVATE KEY-----
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index ef973ac..861d221 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -182,6 +182,24 @@
 
     @SmallTest
     @Test
+    public void setFunctionsNcmAndRndis() {
+        final long rndisPlusNcm = UsbManager.FUNCTION_RNDIS | UsbManager.FUNCTION_NCM;
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_NCM));
+        assertEquals(UsbManager.FUNCTION_NCM, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                rndisPlusNcm));
+        assertEquals(rndisPlusNcm, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+
+        mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
+                UsbManager.FUNCTION_NCM));
+        assertEquals(UsbManager.FUNCTION_NCM, mUsbHandler.getEnabledFunctions() & rndisPlusNcm);
+    }
+
+    @SmallTest
+    @Test
     public void enableAdb() {
         sendBootCompleteMessages(mUsbHandler);
         Message msg = mUsbHandler.obtainMessage(MSG_ENABLE_ADB);
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java b/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java
index a0fd9d4..b8bd98e 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.usb;
 
+import static org.junit.Assert.assertEquals;
+
 import android.content.Context;
 import android.hardware.usb.UsbManager;
 
@@ -23,12 +25,12 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.usblib.UsbManagerTestLib;
+
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import com.android.server.usblib.UsbManagerTestLib;
-
 /**
  * Unit tests for {@link android.hardware.usb.UsbManager}.
  * Note: NOT claimed MANAGE_USB permission in Manifest
@@ -78,4 +80,35 @@
     public void testUsbApi_SetCurrentFunctions_OnSecurityException() throws Exception {
         mUsbManagerTestLib.testSetCurrentFunctionsEx(UsbManager.FUNCTION_NONE);
     }
+
+    public void assertSettableFunctions(boolean settable, long functions) {
+        assertEquals(
+                "areSettableFunctions(" + UsbManager.usbFunctionsToString(functions) + "):",
+                settable, UsbManager.areSettableFunctions(functions));
+    }
+
+    /**
+     * Tests the behaviour of the static areSettableFunctions method. This method performs no IPCs
+     * and requires no permissions.
+     */
+    @Test
+    public void testUsbManager_AreSettableFunctions() {
+        // NONE is settable.
+        assertSettableFunctions(true, UsbManager.FUNCTION_NONE);
+
+        // MTP, PTP, RNDIS, MIDI, NCM are all settable by themselves.
+        assertSettableFunctions(true, UsbManager.FUNCTION_MTP);
+        assertSettableFunctions(true, UsbManager.FUNCTION_PTP);
+        assertSettableFunctions(true, UsbManager.FUNCTION_RNDIS);
+        assertSettableFunctions(true, UsbManager.FUNCTION_MIDI);
+        assertSettableFunctions(true, UsbManager.FUNCTION_NCM);
+
+        // Setting two functions at the same time is not allowed...
+        assertSettableFunctions(false, UsbManager.FUNCTION_MTP | UsbManager.FUNCTION_PTP);
+        assertSettableFunctions(false, UsbManager.FUNCTION_PTP | UsbManager.FUNCTION_RNDIS);
+        assertSettableFunctions(false, UsbManager.FUNCTION_MIDI | UsbManager.FUNCTION_NCM);
+
+        // ... except in the special case of RNDIS and NCM.
+        assertSettableFunctions(true, UsbManager.FUNCTION_RNDIS | UsbManager.FUNCTION_NCM);
+    }
 }
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index f6a2846..ffde68e 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -36,7 +36,7 @@
         "libvndksupport",
         "libziparchive",
         "libz",
-        "netd_aidl_interface-cpp",
+        "netd_aidl_interface-V5-cpp",
     ],
 }
 
@@ -53,6 +53,7 @@
     jarjar_rules: "jarjar-rules.txt",
     static_libs: [
         "androidx.test.rules",
+        "bouncycastle-repackaged-unbundled",
         "FrameworksNetCommonTests",
         "frameworks-base-testutils",
         "frameworks-net-integration-testutils",
diff --git a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
index cade5ba..d232a507 100644
--- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
+++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -20,22 +20,20 @@
 import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.os.Build;
-import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
 import com.android.testutils.DevSdkIgnoreRunner;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Map;
 
 @IgnoreUpTo(Build.VERSION_CODES.R)
 @RunWith(DevSdkIgnoreRunner.class)
@@ -45,51 +43,51 @@
     private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_DEFAULT;
     private static final String TEST_PACKAGE = "com.google.apps.contacts";
 
-    private final List<String> mPackages = new ArrayList<>();
     private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder();
 
-    @Before
-    public void beforeEachTestMethod() {
-        mPackages.add(TEST_PACKAGE);
-    }
-
     @Test
-    public void builderAddNetworkPreferenceRequiresNonNullPackages() {
+    public void testBuilderAddNetworkPreferenceRequiresNonNullPackageName() {
         assertThrows(NullPointerException.class,
-                () -> mBuilder.addNetworkPreference(TEST_PREF, null));
+                () -> mBuilder.addNetworkPreference(null, TEST_PREF));
     }
 
     @Test
-    public void getNetworkPreferencesReturnsCorrectValue() {
-        final int expectedNumberOfMappings = 1;
-        mBuilder.addNetworkPreference(TEST_PREF, mPackages);
+    public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() {
+        assertThrows(NullPointerException.class,
+                () -> mBuilder.removeNetworkPreference(null));
+    }
 
-        final SparseArray<List<String>> networkPreferences =
+    @Test
+    public void testGetNetworkPreferenceReturnsCorrectValue() {
+        final int expectedNumberOfMappings = 1;
+        mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF);
+
+        final Map<String, Integer> networkPreferences =
                 mBuilder.build().getNetworkPreferences();
 
         assertEquals(expectedNumberOfMappings, networkPreferences.size());
-        assertEquals(mPackages.size(), networkPreferences.get(TEST_PREF).size());
-        assertEquals(mPackages.get(0), networkPreferences.get(TEST_PREF).get(0));
+        assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
     }
 
     @Test
-    public void getNetworkPreferencesReturnsUnmodifiableValue() {
+    public void testGetNetworkPreferenceReturnsUnmodifiableValue() {
         final String newPackage = "new.com.google.apps.contacts";
-        mBuilder.addNetworkPreference(TEST_PREF, mPackages);
+        mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF);
 
-        final SparseArray<List<String>> networkPreferences =
+        final Map<String, Integer> networkPreferences =
                 mBuilder.build().getNetworkPreferences();
 
         assertThrows(UnsupportedOperationException.class,
-                () -> networkPreferences.get(TEST_PREF).set(mPackages.size() - 1, newPackage));
+                () -> networkPreferences.put(newPackage, TEST_PREF));
 
         assertThrows(UnsupportedOperationException.class,
-                () -> networkPreferences.get(TEST_PREF).add(newPackage));
+                () -> networkPreferences.remove(TEST_PACKAGE));
+
     }
 
     @Test
-    public void toStringReturnsCorrectValue() {
-        mBuilder.addNetworkPreference(TEST_PREF, mPackages);
+    public void testToStringReturnsCorrectValue() {
+        mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF);
 
         final String networkPreferencesString = mBuilder.build().getNetworkPreferences().toString();
 
@@ -99,10 +97,56 @@
 
     @Test
     public void testOemNetworkPreferencesParcelable() {
-        mBuilder.addNetworkPreference(TEST_PREF, mPackages);
+        mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF);
 
         final OemNetworkPreferences prefs = mBuilder.build();
 
         assertParcelSane(prefs, 1 /* fieldCount */);
     }
+
+    @Test
+    public void testAddNetworkPreferenceOverwritesPriorPreference() {
+        final int newPref = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+        mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF);
+        Map<String, Integer> networkPreferences =
+                mBuilder.build().getNetworkPreferences();
+
+        assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
+        assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), TEST_PREF);
+
+        mBuilder.addNetworkPreference(TEST_PACKAGE, newPref);
+        networkPreferences = mBuilder.build().getNetworkPreferences();
+
+        assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
+        assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), newPref);
+    }
+
+    @Test
+    public void testRemoveNetworkPreferenceRemovesValue() {
+        mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF);
+        Map<String, Integer> networkPreferences =
+                mBuilder.build().getNetworkPreferences();
+
+        assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
+
+        mBuilder.removeNetworkPreference(TEST_PACKAGE);
+        networkPreferences = mBuilder.build().getNetworkPreferences();
+
+        assertFalse(networkPreferences.containsKey(TEST_PACKAGE));
+    }
+
+    @Test
+    public void testConstructorByOemNetworkPreferencesSetsValue() {
+        mBuilder.addNetworkPreference(TEST_PACKAGE, TEST_PREF);
+        OemNetworkPreferences networkPreference = mBuilder.build();
+
+        final Map<String, Integer> networkPreferences =
+                new OemNetworkPreferences
+                        .Builder(networkPreference)
+                        .build()
+                        .getNetworkPreferences();
+
+        assertTrue(networkPreferences.containsKey(TEST_PACKAGE));
+        assertEquals(networkPreferences.get(TEST_PACKAGE).intValue(), TEST_PREF);
+    }
 }
diff --git a/tests/net/common/java/android/net/UnderlyingNetworkInfoTest.kt b/tests/net/common/java/android/net/UnderlyingNetworkInfoTest.kt
new file mode 100644
index 0000000..87cfb34
--- /dev/null
+++ b/tests/net/common/java/android/net/UnderlyingNetworkInfoTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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 android.net
+
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.assertParcelSane
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+
+private const val TEST_OWNER_UID = 123
+private const val TEST_IFACE = "test_tun0"
+private val TEST_IFACE_LIST = listOf("wlan0", "rmnet_data0", "eth0")
+
+@SmallTest
+@RunWith(DevSdkIgnoreRunner::class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class UnderlyingNetworkInfoTest {
+    @Test
+    fun testParcelUnparcel() {
+        val testInfo = UnderlyingNetworkInfo(TEST_OWNER_UID, TEST_IFACE, TEST_IFACE_LIST)
+        assertEquals(TEST_OWNER_UID, testInfo.ownerUid)
+        assertEquals(TEST_IFACE, testInfo.iface)
+        assertEquals(TEST_IFACE_LIST, testInfo.underlyingIfaces)
+        assertParcelSane(testInfo, 3)
+
+        val emptyInfo = UnderlyingNetworkInfo(0, String(), listOf())
+        assertEquals(0, emptyInfo.ownerUid)
+        assertEquals(String(), emptyInfo.iface)
+        assertEquals(listOf(), emptyInfo.underlyingIfaces)
+        assertParcelSane(emptyInfo, 3)
+    }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 16c4865..083c8c8 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -38,6 +38,7 @@
 import android.os.ConditionVariable
 import android.os.IBinder
 import android.os.INetworkManagementService
+import android.os.UserHandle
 import android.testing.TestableContext
 import android.util.Log
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -55,10 +56,13 @@
 import org.junit.BeforeClass
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.AdditionalAnswers
 import org.mockito.Mock
 import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
 import org.mockito.MockitoAnnotations
@@ -143,7 +147,10 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        doNothing().`when`(context).sendStickyBroadcastAsUser(any(), any(), any())
+        val asUserCtx = mock(Context::class.java, AdditionalAnswers.delegatesTo<Context>(context))
+        doReturn(UserHandle.ALL).`when`(asUserCtx).user
+        doReturn(asUserCtx).`when`(context).createContextAsUser(eq(UserHandle.ALL), anyInt())
+        doNothing().`when`(context).sendStickyBroadcast(any(), any())
 
         networkStackClient = TestNetworkStackClient(realContext)
         networkStackClient.init()
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index f2dd27e..c2fddf3 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -32,6 +32,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
 import static android.net.NetworkRequest.Type.REQUEST;
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
 
@@ -368,6 +369,12 @@
                 eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
                 eq(testPkgName), eq(null));
         reset(mService);
+
+        manager.requestBackgroundNetwork(request, null, callback);
+        verify(mService).requestNetwork(eq(request.networkCapabilities),
+                eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE),
+                eq(testPkgName), eq(null));
+        reset(mService);
     }
 
     static Message makeMessage(NetworkRequest req, int messageType) {
diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java
index 076e41d..1abd39a 100644
--- a/tests/net/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java
@@ -30,7 +30,7 @@
 
 import com.android.internal.net.VpnProfile;
 import com.android.net.module.util.ProxyUtils;
-import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/net/java/android/net/NetworkTemplateTest.kt b/tests/net/java/android/net/NetworkTemplateTest.kt
index 9ba56e4..91fcbc0 100644
--- a/tests/net/java/android/net/NetworkTemplateTest.kt
+++ b/tests/net/java/android/net/NetworkTemplateTest.kt
@@ -67,6 +67,7 @@
         val caps = NetworkCapabilities().apply {
             setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false)
             setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true)
+            setSSID(ssid)
         }
         return NetworkState(info, lp, caps, mock(Network::class.java), subscriberId, ssid)
     }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index c5e6c35..bcbc9e0 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -64,6 +64,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
@@ -131,6 +132,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
@@ -200,6 +202,7 @@
 import android.net.SocketKeepalive;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
+import android.net.UnderlyingNetworkInfo;
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.metrics.IpConnectivityLog;
@@ -245,12 +248,12 @@
 
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.net.VpnConfig;
-import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.net.module.util.ArrayTrackRecord;
 import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
 import com.android.server.connectivity.ConnectivityConstants;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -328,11 +331,12 @@
     private static final String TAG = "ConnectivityServiceTest";
 
     private static final int TIMEOUT_MS = 500;
-    private static final int TEST_LINGER_DELAY_MS = 300;
-    // Chosen to be less than the linger timeout. This ensures that we can distinguish between a
-    // LOST callback that arrives immediately and a LOST callback that arrives after the linger
-    // timeout. For this, our assertions should run fast enough to leave less than
-    // (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are
+    private static final int TEST_LINGER_DELAY_MS = 400;
+    private static final int TEST_NASCENT_DELAY_MS = 300;
+    // Chosen to be less than the linger and nascent timeout. This ensures that we can distinguish
+    // between a LOST callback that arrives immediately and a LOST callback that arrives after
+    // the linger/nascent timeout. For this, our assertions should run fast enough to leave
+    // less than (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are
     // supposedly fired, and the time we call expectCallback.
     private static final int TEST_CALLBACK_TIMEOUT_MS = 250;
     // Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to
@@ -379,6 +383,10 @@
     private QosCallbackMockHelper mQosCallbackMockHelper;
     private QosCallbackTracker mQosCallbackTracker;
 
+    // State variables required to emulate NetworkPolicyManagerService behaviour.
+    private int mUidRules = RULE_NONE;
+    private boolean mRestrictBackground = false;
+
     @Mock DeviceIdleInternal mDeviceIdleInternal;
     @Mock INetworkManagementService mNetworkManagementService;
     @Mock INetworkStatsService mStatsService;
@@ -901,28 +909,69 @@
     }
 
     /**
-     * A NetworkFactory that allows tests to wait until any in-flight NetworkRequest add or remove
-     * operations have been processed. Before ConnectivityService can add or remove any requests,
-     * the factory must be told to expect those operations by calling expectAddRequestsWithScores or
-     * expectRemoveRequests.
+     * A NetworkFactory that allows to wait until any in-flight NetworkRequest add or remove
+     * operations have been processed and test for them.
      */
     private static class MockNetworkFactory extends NetworkFactory {
         private final ConditionVariable mNetworkStartedCV = new ConditionVariable();
         private final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
         private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
 
-        // Used to expect that requests be removed or added on a separate thread, without sleeping.
-        // Callers can call either expectAddRequestsWithScores() or expectRemoveRequests() exactly
-        // once, then cause some other thread to add or remove requests, then call
-        // waitForRequests().
-        // It is not possible to wait for both add and remove requests. When adding, the queue
-        // contains the expected score. When removing, the value is unused, all matters is the
-        // number of objects in the queue.
-        private final LinkedBlockingQueue<Integer> mExpectations;
+        static class RequestEntry {
+            @NonNull
+            public final NetworkRequest request;
 
-        // Whether we are currently expecting requests to be added or removed. Valid only if
-        // mExpectations is non-empty.
-        private boolean mExpectingAdditions;
+            RequestEntry(@NonNull final NetworkRequest request) {
+                this.request = request;
+            }
+
+            static final class Add extends RequestEntry {
+                public final int factorySerialNumber;
+
+                Add(@NonNull final NetworkRequest request, final int factorySerialNumber) {
+                    super(request);
+                    this.factorySerialNumber = factorySerialNumber;
+                }
+            }
+
+            static final class Remove extends RequestEntry {
+                Remove(@NonNull final NetworkRequest request) {
+                    super(request);
+                }
+            }
+        }
+
+        // History of received requests adds and removes.
+        private final ArrayTrackRecord<RequestEntry>.ReadHead mRequestHistory =
+                new ArrayTrackRecord<RequestEntry>().newReadHead();
+
+        private static <T> T failIfNull(@Nullable final T obj, @Nullable final String message) {
+            if (null == obj) fail(null != message ? message : "Must not be null");
+            return obj;
+        }
+
+
+        public RequestEntry.Add expectRequestAdd() {
+            return failIfNull((RequestEntry.Add) mRequestHistory.poll(TIMEOUT_MS,
+                    it -> it instanceof RequestEntry.Add), "Expected request add");
+        }
+
+        public void expectRequestAdds(final int count) {
+            for (int i = count; i > 0; --i) {
+                expectRequestAdd();
+            }
+        }
+
+        public RequestEntry.Remove expectRequestRemove() {
+            return failIfNull((RequestEntry.Remove) mRequestHistory.poll(TIMEOUT_MS,
+                    it -> it instanceof RequestEntry.Remove), "Expected request remove");
+        }
+
+        public void expectRequestRemoves(final int count) {
+            for (int i = count; i > 0; --i) {
+                expectRequestRemove();
+            }
+        }
 
         // Used to collect the networks requests managed by this factory. This is a duplicate of
         // the internal information stored in the NetworkFactory (which is private).
@@ -931,7 +980,6 @@
         public MockNetworkFactory(Looper looper, Context context, String logTag,
                 NetworkCapabilities filter) {
             super(looper, context, logTag, filter);
-            mExpectations = new LinkedBlockingQueue<>();
         }
 
         public int getMyRequestCount() {
@@ -965,95 +1013,33 @@
         @Override
         protected void handleAddRequest(NetworkRequest request, int score,
                 int factorySerialNumber) {
-            synchronized (mExpectations) {
-                final Integer expectedScore = mExpectations.poll(); // null if the queue is empty
-
-                assertNotNull("Added more requests than expected (" + request + " score : "
-                        + score + ")", expectedScore);
-                // If we're expecting anything, we must be expecting additions.
-                if (!mExpectingAdditions) {
-                    fail("Can't add requests while expecting requests to be removed");
-                }
-                if (expectedScore != score) {
-                    fail("Expected score was " + expectedScore + " but actual was " + score
-                            + " in added request");
-                }
-
-                // Add the request.
-                mNetworkRequests.put(request.requestId, request);
-                super.handleAddRequest(request, score, factorySerialNumber);
-                mExpectations.notify();
-            }
+            mNetworkRequests.put(request.requestId, request);
+            super.handleAddRequest(request, score, factorySerialNumber);
+            mRequestHistory.add(new RequestEntry.Add(request, factorySerialNumber));
         }
 
         @Override
         protected void handleRemoveRequest(NetworkRequest request) {
-            synchronized (mExpectations) {
-                final Integer expectedScore = mExpectations.poll(); // null if the queue is empty
+            mNetworkRequests.remove(request.requestId);
+            super.handleRemoveRequest(request);
+            mRequestHistory.add(new RequestEntry.Remove(request));
+        }
 
-                assertTrue("Removed more requests than expected", expectedScore != null);
-                // If we're expecting anything, we must be expecting removals.
-                if (mExpectingAdditions) {
-                    fail("Can't remove requests while expecting requests to be added");
-                }
+        public void assertRequestCountEquals(final int count) {
+            assertEquals(count, getMyRequestCount());
+        }
 
-                // Remove the request.
-                mNetworkRequests.remove(request.requestId);
-                super.handleRemoveRequest(request);
-                mExpectations.notify();
-            }
+        @Override
+        public void terminate() {
+            super.terminate();
+            // Make sure there are no remaining requests unaccounted for.
+            assertNull(mRequestHistory.poll(TIMEOUT_MS, r -> true));
         }
 
         // Trigger releasing the request as unfulfillable
         public void triggerUnfulfillable(NetworkRequest r) {
             super.releaseRequestAsUnfulfillableByAnyFactory(r);
         }
-
-        private void assertNoExpectations() {
-            if (mExpectations.size() != 0) {
-                fail("Can't add expectation, " + mExpectations.size() + " already pending");
-            }
-        }
-
-        // Expects that requests with the specified scores will be added.
-        public void expectAddRequestsWithScores(final int... scores) {
-            assertNoExpectations();
-            mExpectingAdditions = true;
-            for (int score : scores) {
-                mExpectations.add(score);
-            }
-        }
-
-        // Expects that count requests will be removed.
-        public void expectRemoveRequests(final int count) {
-            assertNoExpectations();
-            mExpectingAdditions = false;
-            for (int i = 0; i < count; ++i) {
-                mExpectations.add(0); // For removals the score is ignored so any value will do.
-            }
-        }
-
-        // Waits for the expected request additions or removals to happen within a timeout.
-        public void waitForRequests() throws InterruptedException {
-            final long deadline = SystemClock.elapsedRealtime() + TIMEOUT_MS;
-            synchronized (mExpectations) {
-                while (mExpectations.size() > 0 && SystemClock.elapsedRealtime() < deadline) {
-                    mExpectations.wait(deadline - SystemClock.elapsedRealtime());
-                }
-            }
-            final long count = mExpectations.size();
-            final String msg = count + " requests still not " +
-                    (mExpectingAdditions ? "added" : "removed") +
-                    " after " + TIMEOUT_MS + " ms";
-            assertEquals(msg, 0, count);
-        }
-
-        public SparseArray<NetworkRequest> waitForNetworkRequests(final int count)
-                throws InterruptedException {
-            waitForRequests();
-            assertEquals(count, getMyRequestCount());
-            return mNetworkRequests;
-        }
     }
 
     private Set<UidRange> uidRangesForUid(int uid) {
@@ -1075,7 +1061,7 @@
         private boolean mAgentRegistered = false;
 
         private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
-        private VpnInfo mVpnInfo;
+        private UnderlyingNetworkInfo mUnderlyingNetworkInfo;
 
         // These ConditionVariables allow tests to wait for LegacyVpnRunner to be stopped/started.
         // TODO: this scheme is ad-hoc and error-prone because it does not fail if, for example, the
@@ -1249,14 +1235,15 @@
         }
 
         @Override
-        public synchronized VpnInfo getVpnInfo() {
-            if (mVpnInfo != null) return mVpnInfo;
+        public synchronized UnderlyingNetworkInfo getUnderlyingNetworkInfo() {
+            if (mUnderlyingNetworkInfo != null) return mUnderlyingNetworkInfo;
 
-            return super.getVpnInfo();
+            return super.getUnderlyingNetworkInfo();
         }
 
-        private synchronized void setVpnInfo(VpnInfo vpnInfo) {
-            mVpnInfo = vpnInfo;
+        private synchronized void setUnderlyingNetworkInfo(
+                UnderlyingNetworkInfo underlyingNetworkInfo) {
+            mUnderlyingNetworkInfo = underlyingNetworkInfo;
         }
     }
 
@@ -1276,12 +1263,36 @@
         }
     }
 
+    private void processBroadcastForVpn(Intent intent) {
+        // The BroadcastReceiver for this broadcast checks it is being run on the handler thread.
+        final Handler handler = new Handler(mCsHandlerThread.getLooper());
+        handler.post(() -> mServiceContext.sendBroadcast(intent));
+        waitForIdle();
+    }
+
+    private void mockUidNetworkingBlocked() {
+        doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class)
+                .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules,
+                        i.getArgument(1) /* metered */, mRestrictBackground)
+        ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
+
+        doAnswer(inv -> mContext.getSystemService(NetworkPolicyManager.class)
+                .checkUidNetworkingBlocked(inv.getArgument(0) /* uid */,
+                        inv.getArgument(1) /* uidRules */,
+                        inv.getArgument(2) /* isNetworkMetered */,
+                        inv.getArgument(3) /* isBackgroundRestricted */)
+        ).when(mNetworkPolicyManager).checkUidNetworkingBlocked(
+                anyInt(), anyInt(), anyBoolean(), anyBoolean());
+    }
+
     private void setUidRulesChanged(int uidRules) throws RemoteException {
-        mPolicyListener.onUidRulesChanged(Process.myUid(), uidRules);
+        mUidRules = uidRules;
+        mPolicyListener.onUidRulesChanged(Process.myUid(), mUidRules);
     }
 
     private void setRestrictBackgroundChanged(boolean restrictBackground) throws RemoteException {
-        mPolicyListener.onRestrictBackgroundChanged(restrictBackground);
+        mRestrictBackground = restrictBackground;
+        mPolicyListener.onRestrictBackgroundChanged(mRestrictBackground);
     }
 
     private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) {
@@ -1392,6 +1403,7 @@
                 mMockNetd,
                 mDeps);
         mService.mLingerDelayMs = TEST_LINGER_DELAY_MS;
+        mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS;
         verify(mDeps).makeMultinetworkPolicyTracker(any(), any(), any());
 
         final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor =
@@ -1734,6 +1746,108 @@
         verifyNoNetwork();
     }
 
+    /**
+     * Verify a newly created network will be inactive instead of torn down even if no one is
+     * requesting.
+     */
+    @Test
+    public void testNewNetworkInactive() throws Exception {
+        // Create a callback that monitoring the testing network.
+        final TestNetworkCallback listenCallback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(new NetworkRequest.Builder().build(), listenCallback);
+
+        // 1. Create a network that is not requested by anyone, and does not satisfy any of the
+        // default requests. Verify that the network will be inactive instead of torn down.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connectWithoutInternet();
+        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        listenCallback.assertNoCallback();
+
+        // Verify that the network will be torn down after nascent expiry. A small period of time
+        // is added in case of flakiness.
+        final int nascentTimeoutMs =
+                mService.mNascentDelayMs + mService.mNascentDelayMs / 4;
+        listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent, nascentTimeoutMs);
+
+        // 2. Create a network that is satisfied by a request comes later.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connectWithoutInternet();
+        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI).build();
+        final TestNetworkCallback wifiCallback = new TestNetworkCallback();
+        mCm.requestNetwork(wifiRequest, wifiCallback);
+        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+        // Verify that the network will be kept since the request is still satisfied. And is able
+        // to get disconnected as usual if the request is released after the nascent timer expires.
+        listenCallback.assertNoCallback(nascentTimeoutMs);
+        mCm.unregisterNetworkCallback(wifiCallback);
+        listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+
+        // 3. Create a network that is satisfied by a request comes later.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connectWithoutInternet();
+        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        mCm.requestNetwork(wifiRequest, wifiCallback);
+        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+        // Verify that the network will still be torn down after the request gets removed.
+        mCm.unregisterNetworkCallback(wifiCallback);
+        listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+
+        // There is no need to ensure that LOSING is never sent in the common case that the
+        // network immediately satisfies a request that was already present, because it is already
+        // verified anywhere whenever {@code TestNetworkCallback#expectAvailable*} is called.
+
+        mCm.unregisterNetworkCallback(listenCallback);
+    }
+
+    /**
+     * Verify a newly created network will be inactive and switch to background if only background
+     * request is satisfied.
+     */
+    @Test
+    public void testNewNetworkInactive_bgNetwork() throws Exception {
+        // Create a callback that monitoring the wifi network.
+        final TestNetworkCallback wifiListenCallback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI).build(), wifiListenCallback);
+
+        // Create callbacks that can monitor background and foreground mobile networks.
+        // This is done by granting using background networks permission before registration. Thus,
+        // the service will not add {@code NET_CAPABILITY_FOREGROUND} by default.
+        grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid());
+        final TestNetworkCallback bgMobileListenCallback = new TestNetworkCallback();
+        final TestNetworkCallback fgMobileListenCallback = new TestNetworkCallback();
+        mCm.registerNetworkCallback(new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR).build(), bgMobileListenCallback);
+        mCm.registerNetworkCallback(new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_FOREGROUND).build(), fgMobileListenCallback);
+
+        // Connect wifi, which satisfies default request.
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        wifiListenCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+
+        // Connect a cellular network, verify that satisfies only the background callback.
+        setAlwaysOnNetworks(true);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        bgMobileListenCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        fgMobileListenCallback.assertNoCallback();
+        assertFalse(isForegroundNetwork(mCellNetworkAgent));
+
+        mCellNetworkAgent.disconnect();
+        bgMobileListenCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+        fgMobileListenCallback.assertNoCallback();
+
+        mCm.unregisterNetworkCallback(wifiListenCallback);
+        mCm.unregisterNetworkCallback(bgMobileListenCallback);
+        mCm.unregisterNetworkCallback(fgMobileListenCallback);
+    }
+
     @Test
     public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception {
         // Test bringing up unvalidated WiFi
@@ -2578,12 +2692,6 @@
         callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
     }
 
-    private int[] makeIntArray(final int size, final int value) {
-        final int[] array = new int[size];
-        Arrays.fill(array, value);
-        return array;
-    }
-
     private void tryNetworkFactoryRequests(int capability) throws Exception {
         // Verify NOT_RESTRICTED is set appropriately
         final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability)
@@ -2605,9 +2713,9 @@
                 mServiceContext, "testFactory", filter);
         testFactory.setScoreFilter(40);
         ConditionVariable cv = testFactory.getNetworkStartedCV();
-        testFactory.expectAddRequestsWithScores(0);
         testFactory.register();
-        testFactory.waitForNetworkRequests(1);
+        testFactory.expectRequestAdd();
+        testFactory.assertRequestCountEquals(1);
         int expectedRequestCount = 1;
         NetworkCallback networkCallback = null;
         // For non-INTERNET capabilities we cannot rely on the default request being present, so
@@ -2616,13 +2724,12 @@
             assertFalse(testFactory.getMyStartRequested());
             NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
             networkCallback = new NetworkCallback();
-            testFactory.expectAddRequestsWithScores(0);  // New request
             mCm.requestNetwork(request, networkCallback);
             expectedRequestCount++;
-            testFactory.waitForNetworkRequests(expectedRequestCount);
+            testFactory.expectRequestAdd();
         }
         waitFor(cv);
-        assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+        testFactory.assertRequestCountEquals(expectedRequestCount);
         assertTrue(testFactory.getMyStartRequested());
 
         // Now bring in a higher scored network.
@@ -2636,15 +2743,14 @@
         // When testAgent connects, ConnectivityService will re-send us all current requests with
         // the new score. There are expectedRequestCount such requests, and we must wait for all of
         // them.
-        testFactory.expectAddRequestsWithScores(makeIntArray(expectedRequestCount, 50));
         testAgent.connect(false);
         testAgent.addCapability(capability);
         waitFor(cv);
-        testFactory.waitForNetworkRequests(expectedRequestCount);
+        testFactory.expectRequestAdds(expectedRequestCount);
+        testFactory.assertRequestCountEquals(expectedRequestCount);
         assertFalse(testFactory.getMyStartRequested());
 
         // Bring in a bunch of requests.
-        testFactory.expectAddRequestsWithScores(makeIntArray(10, 50));
         assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
         ConnectivityManager.NetworkCallback[] networkCallbacks =
                 new ConnectivityManager.NetworkCallback[10];
@@ -2654,24 +2760,24 @@
             builder.addCapability(capability);
             mCm.requestNetwork(builder.build(), networkCallbacks[i]);
         }
-        testFactory.waitForNetworkRequests(10 + expectedRequestCount);
+        testFactory.expectRequestAdds(10);
+        testFactory.assertRequestCountEquals(10 + expectedRequestCount);
         assertFalse(testFactory.getMyStartRequested());
 
         // Remove the requests.
-        testFactory.expectRemoveRequests(10);
         for (int i = 0; i < networkCallbacks.length; i++) {
             mCm.unregisterNetworkCallback(networkCallbacks[i]);
         }
-        testFactory.waitForNetworkRequests(expectedRequestCount);
+        testFactory.expectRequestRemoves(10);
+        testFactory.assertRequestCountEquals(expectedRequestCount);
         assertFalse(testFactory.getMyStartRequested());
 
         // Drop the higher scored network.
         cv = testFactory.getNetworkStartedCV();
-        // With the default network disconnecting, the requests are sent with score 0 to factories.
-        testFactory.expectAddRequestsWithScores(makeIntArray(expectedRequestCount, 0));
         testAgent.disconnect();
         waitFor(cv);
-        testFactory.waitForNetworkRequests(expectedRequestCount);
+        testFactory.expectRequestAdds(expectedRequestCount);
+        testFactory.assertRequestCountEquals(expectedRequestCount);
         assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
         assertTrue(testFactory.getMyStartRequested());
 
@@ -2714,9 +2820,8 @@
             final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
                     mServiceContext, "testFactory", filter);
             // Register the factory and don't be surprised when the default request arrives.
-            testFactory.expectAddRequestsWithScores(0);
             testFactory.register();
-            testFactory.waitForNetworkRequests(1);
+            testFactory.expectRequestAdd();
 
             testFactory.setScoreFilter(42);
             testFactory.terminate();
@@ -3692,10 +3797,13 @@
 
     @Test
     public void testBackgroundNetworks() throws Exception {
-        // Create a background request. We can't do this ourselves because ConnectivityService
-        // doesn't have an API for it. So just turn on mobile data always on.
-        setAlwaysOnNetworks(true);
+        // Create a cellular background request.
         grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid());
+        final TestNetworkCallback cellBgCallback = new TestNetworkCallback();
+        mCm.requestBackgroundNetwork(new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_CELLULAR).build(), null, cellBgCallback);
+
+        // Make callbacks for monitoring.
         final NetworkRequest request = new NetworkRequest.Builder().build();
         final NetworkRequest fgRequest = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_FOREGROUND).build();
@@ -3764,6 +3872,7 @@
 
         mCm.unregisterNetworkCallback(callback);
         mCm.unregisterNetworkCallback(fgCallback);
+        mCm.unregisterNetworkCallback(cellBgCallback);
     }
 
     @Ignore // This test has instrinsic chances of spurious failures: ignore for continuous testing.
@@ -3855,38 +3964,37 @@
         testFactory.setScoreFilter(40);
 
         // Register the factory and expect it to start looking for a network.
-        testFactory.expectAddRequestsWithScores(0);  // Score 0 as the request is not served yet.
         testFactory.register();
 
         try {
-            testFactory.waitForNetworkRequests(1);
+            testFactory.expectRequestAdd();
+            testFactory.assertRequestCountEquals(1);
             assertTrue(testFactory.getMyStartRequested());
 
             // Bring up wifi. The factory stops looking for a network.
             mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
             // Score 60 - 40 penalty for not validated yet, then 60 when it validates
-            testFactory.expectAddRequestsWithScores(20, 60);
             mWiFiNetworkAgent.connect(true);
-            testFactory.waitForRequests();
+            // Default request and mobile always on request
+            testFactory.expectRequestAdds(2);
             assertFalse(testFactory.getMyStartRequested());
 
-            ContentResolver cr = mServiceContext.getContentResolver();
-
             // Turn on mobile data always on. The factory starts looking again.
-            testFactory.expectAddRequestsWithScores(0);  // Always on requests comes up with score 0
             setAlwaysOnNetworks(true);
-            testFactory.waitForNetworkRequests(2);
+            testFactory.expectRequestAdd();
+            testFactory.assertRequestCountEquals(2);
+
             assertTrue(testFactory.getMyStartRequested());
 
             // Bring up cell data and check that the factory stops looking.
             assertLength(1, mCm.getAllNetworks());
             mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-            testFactory.expectAddRequestsWithScores(10, 50);  // Unvalidated, then validated
             mCellNetworkAgent.connect(true);
             cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-            testFactory.waitForNetworkRequests(2);
-            assertFalse(
-                    testFactory.getMyStartRequested());  // Because the cell network outscores us.
+            testFactory.expectRequestAdds(2); // Unvalidated and validated
+            testFactory.assertRequestCountEquals(2);
+            // The cell network outscores the factory filter, so start is not requested.
+            assertFalse(testFactory.getMyStartRequested());
 
             // Check that cell data stays up.
             waitForIdle();
@@ -3894,12 +4002,12 @@
             assertLength(2, mCm.getAllNetworks());
 
             // Turn off mobile data always on and expect the request to disappear...
-            testFactory.expectRemoveRequests(1);
             setAlwaysOnNetworks(false);
-            testFactory.waitForNetworkRequests(1);
+            testFactory.expectRequestRemove();
 
-            // ...  and cell data to be torn down.
-            cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+            // ...  and cell data to be torn down after nascent network timeout.
+            cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
+                    mService.mNascentDelayMs + TEST_CALLBACK_TIMEOUT_MS);
             assertLength(1, mCm.getAllNetworks());
         } finally {
             testFactory.terminate();
@@ -4203,46 +4311,33 @@
         testFactory.setScoreFilter(40);
 
         // Register the factory and expect it to receive the default request.
-        testFactory.expectAddRequestsWithScores(0);
         testFactory.register();
-        SparseArray<NetworkRequest> requests = testFactory.waitForNetworkRequests(1);
-
-        assertEquals(1, requests.size()); // have 1 request at this point
-        int origRequestId = requests.valueAt(0).requestId;
+        testFactory.expectRequestAdd();
 
         // Now file the test request and expect it.
-        testFactory.expectAddRequestsWithScores(0);
         mCm.requestNetwork(nr, networkCallback);
-        requests = testFactory.waitForNetworkRequests(2); // have 2 requests at this point
+        final NetworkRequest newRequest = testFactory.expectRequestAdd().request;
 
-        int newRequestId = 0;
-        for (int i = 0; i < requests.size(); ++i) {
-            if (requests.valueAt(i).requestId != origRequestId) {
-                newRequestId = requests.valueAt(i).requestId;
-                break;
-            }
-        }
-
-        testFactory.expectRemoveRequests(1);
         if (preUnregister) {
             mCm.unregisterNetworkCallback(networkCallback);
 
             // Simulate the factory releasing the request as unfulfillable: no-op since
             // the callback has already been unregistered (but a test that no exceptions are
             // thrown).
-            testFactory.triggerUnfulfillable(requests.get(newRequestId));
+            testFactory.triggerUnfulfillable(newRequest);
         } else {
             // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
-            testFactory.triggerUnfulfillable(requests.get(newRequestId));
+            testFactory.triggerUnfulfillable(newRequest);
 
             networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
-            testFactory.waitForRequests();
 
             // unregister network callback - a no-op (since already freed by the
             // on-unavailable), but should not fail or throw exceptions.
             mCm.unregisterNetworkCallback(networkCallback);
         }
 
+        testFactory.expectRequestRemove();
+
         testFactory.terminate();
         handlerThread.quit();
     }
@@ -5194,20 +5289,22 @@
     private void expectForceUpdateIfaces(Network[] networks, String defaultIface,
             Integer vpnUid, String vpnIfname, String[] underlyingIfaces) throws Exception {
         ArgumentCaptor<Network[]> networksCaptor = ArgumentCaptor.forClass(Network[].class);
-        ArgumentCaptor<VpnInfo[]> vpnInfosCaptor = ArgumentCaptor.forClass(VpnInfo[].class);
+        ArgumentCaptor<UnderlyingNetworkInfo[]> vpnInfosCaptor = ArgumentCaptor.forClass(
+                UnderlyingNetworkInfo[].class);
 
         verify(mStatsService, atLeastOnce()).forceUpdateIfaces(networksCaptor.capture(),
                 any(NetworkState[].class), eq(defaultIface), vpnInfosCaptor.capture());
 
         assertSameElementsNoDuplicates(networksCaptor.getValue(), networks);
 
-        VpnInfo[] infos = vpnInfosCaptor.getValue();
+        UnderlyingNetworkInfo[] infos = vpnInfosCaptor.getValue();
         if (vpnUid != null) {
             assertEquals("Should have exactly one VPN:", 1, infos.length);
-            VpnInfo info = infos[0];
+            UnderlyingNetworkInfo info = infos[0];
             assertEquals("Unexpected VPN owner:", (int) vpnUid, info.ownerUid);
-            assertEquals("Unexpected VPN interface:", vpnIfname, info.vpnIface);
-            assertSameElementsNoDuplicates(underlyingIfaces, info.underlyingIfaces);
+            assertEquals("Unexpected VPN interface:", vpnIfname, info.iface);
+            assertSameElementsNoDuplicates(underlyingIfaces,
+                    info.underlyingIfaces.toArray(new String[0]));
         } else {
             assertEquals(0, infos.length);
             return;
@@ -5268,7 +5365,7 @@
         waitForIdle();
         verify(mStatsService, never())
                 .forceUpdateIfaces(eq(onlyCell), any(NetworkState[].class), eq(MOBILE_IFNAME),
-                        eq(new VpnInfo[0]));
+                        eq(new UnderlyingNetworkInfo[0]));
         reset(mStatsService);
 
         // Roaming change should update ifaces
@@ -5315,20 +5412,20 @@
         // MOBILE_IFNAME even though the default network is wifi.
         // TODO: fix this to pass in the actual default network interface. Whether or not the VPN
         // applies to the system server UID should not have any bearing on network stats.
-        mService.setUnderlyingNetworksForVpn(onlyCell);
+        mMockVpn.setUnderlyingNetworks(onlyCell);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
                 new String[]{MOBILE_IFNAME});
         reset(mStatsService);
 
-        mService.setUnderlyingNetworksForVpn(cellAndWifi);
+        mMockVpn.setUnderlyingNetworks(cellAndWifi);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
                 new String[]{MOBILE_IFNAME, WIFI_IFNAME});
         reset(mStatsService);
 
         // Null underlying networks are ignored.
-        mService.setUnderlyingNetworksForVpn(cellNullAndWifi);
+        mMockVpn.setUnderlyingNetworks(cellNullAndWifi);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME,
                 new String[]{MOBILE_IFNAME, WIFI_IFNAME});
@@ -5351,8 +5448,8 @@
         // network for the VPN...
         verify(mStatsService, never()).forceUpdateIfaces(any(Network[].class),
                 any(NetworkState[].class), any() /* anyString() doesn't match null */,
-                argThat(infos -> infos[0].underlyingIfaces.length == 1
-                        && WIFI_IFNAME.equals(infos[0].underlyingIfaces[0])));
+                argThat(infos -> infos[0].underlyingIfaces.size() == 1
+                        && WIFI_IFNAME.equals(infos[0].underlyingIfaces.get(0))));
         verifyNoMoreInteractions(mStatsService);
         reset(mStatsService);
 
@@ -5365,8 +5462,8 @@
         waitForIdle();
         verify(mStatsService).forceUpdateIfaces(any(Network[].class),
                 any(NetworkState[].class), any() /* anyString() doesn't match null */,
-                argThat(vpnInfos -> vpnInfos[0].underlyingIfaces.length == 1
-                        && WIFI_IFNAME.equals(vpnInfos[0].underlyingIfaces[0])));
+                argThat(vpnInfos -> vpnInfos[0].underlyingIfaces.size() == 1
+                        && WIFI_IFNAME.equals(vpnInfos[0].underlyingIfaces.get(0))));
         mEthernetNetworkAgent.disconnect();
         waitForIdle();
         reset(mStatsService);
@@ -5377,25 +5474,25 @@
         // is probably a performance improvement (though it's very unlikely that a VPN would declare
         // no underlying networks).
         // Also, for the same reason as above, the active interface passed in is null.
-        mService.setUnderlyingNetworksForVpn(new Network[0]);
+        mMockVpn.setUnderlyingNetworks(new Network[0]);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, null);
         reset(mStatsService);
 
         // Specifying only a null underlying network is the same as no networks.
-        mService.setUnderlyingNetworksForVpn(onlyNull);
+        mMockVpn.setUnderlyingNetworks(onlyNull);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, null);
         reset(mStatsService);
 
         // Specifying networks that are all disconnected is the same as specifying no networks.
-        mService.setUnderlyingNetworksForVpn(onlyCell);
+        mMockVpn.setUnderlyingNetworks(onlyCell);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, null);
         reset(mStatsService);
 
         // Passing in null again means follow the default network again.
-        mService.setUnderlyingNetworksForVpn(null);
+        mMockVpn.setUnderlyingNetworks(null);
         waitForIdle();
         expectForceUpdateIfaces(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME,
                 new String[]{WIFI_IFNAME});
@@ -5870,7 +5967,7 @@
         mMockVpn.establishForMyUid(false, true, false);
         assertUidRangesUpdatedForMyUid(true);
         final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId());
-        mService.setUnderlyingNetworksForVpn(new Network[]{wifiNetwork});
+        mMockVpn.setUnderlyingNetworks(new Network[]{wifiNetwork});
         callback.expectAvailableCallbacksUnvalidated(mMockVpn);
         assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
                 .hasTransport(TRANSPORT_VPN));
@@ -5963,23 +6060,18 @@
         callback.expectCapabilitiesThat(mMockVpn,
                 nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
                         && nc.hasTransport(TRANSPORT_WIFI));
-
-        // BUG: the VPN is no longer suspended, so a RESUMED callback should have been sent.
-        // callback.expectCallback(CallbackEntry.RESUMED, mMockVpn);
+        callback.expectCallback(CallbackEntry.RESUMED, mMockVpn);
         callback.assertNoCallback();
 
         assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork())
                 .hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
-        assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED);  // BUG: VPN caps have NOT_SUSPENDED.
+        assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
-        // BUG: the device has connectivity, so this should return true.
-        assertGetNetworkInfoOfGetActiveNetworkIsConnected(false);
+        assertGetNetworkInfoOfGetActiveNetworkIsConnected(true);
 
-        // Unsuspend cellular and then switch back to it.
-        // The same bug happens in the opposite direction: the VPN's capabilities correctly have
-        // NOT_SUSPENDED, but the VPN's NetworkInfo is in state SUSPENDED.
+        // Unsuspend cellular and then switch back to it. The VPN remains not suspended.
         mCellNetworkAgent.resume();
         callback.assertNoCallback();
         mWiFiNetworkAgent.disconnect();
@@ -5996,12 +6088,11 @@
                 .hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED);
-        assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED);  // BUG: VPN caps have NOT_SUSPENDED.
+        assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
-        // BUG: the device has connectivity, so this should return true.
-        assertGetNetworkInfoOfGetActiveNetworkIsConnected(false);
+        assertGetNetworkInfoOfGetActiveNetworkIsConnected(true);
 
-        // Re-suspending the current network fixes the problem.
+        // Suspend cellular and expect no connectivity.
         mCellNetworkAgent.suspend();
         callback.expectCapabilitiesThat(mMockVpn,
                 nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
@@ -6017,6 +6108,7 @@
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED);
         assertGetNetworkInfoOfGetActiveNetworkIsConnected(false);
 
+        // Resume cellular and expect that connectivity comes back.
         mCellNetworkAgent.resume();
         callback.expectCapabilitiesThat(mMockVpn,
                 nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
@@ -6069,7 +6161,7 @@
 
         final Set<UidRange> ranges = uidRangesForUid(uid);
         mMockVpn.registerAgent(ranges);
-        mService.setUnderlyingNetworksForVpn(new Network[0]);
+        mMockVpn.setUnderlyingNetworks(new Network[0]);
 
         // VPN networks do not satisfy the default request and are automatically validated
         // by NetworkMonitor
@@ -6317,7 +6409,7 @@
         mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
         mCellNetworkAgent.connect(true);
 
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6332,7 +6424,7 @@
         mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
         mWiFiNetworkAgent.connect(true);
 
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6343,7 +6435,7 @@
         assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
 
         // Don't disconnect, but note the VPN is not using wifi any more.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6374,7 +6466,7 @@
         vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn);
 
         // Use Wifi but not cell. Note the VPN is now unmetered and not suspended.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6385,7 +6477,7 @@
         assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent);
 
         // Use both again.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6400,21 +6492,18 @@
         vpnNetworkCallback.assertNoCallback();
 
         // Stop using WiFi. The VPN is suspended again.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork() });
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
                 (caps) -> caps.hasTransport(TRANSPORT_VPN)
                 && caps.hasTransport(TRANSPORT_CELLULAR)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        // While the SUSPENDED callback should in theory be sent here, it is not. This is
-        // a bug in ConnectivityService, but as the SUSPENDED and RESUMED callbacks have never
-        // been public and are deprecated and slated for removal, there is no sense in spending
-        // resources fixing this bug now.
+        vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn);
         assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
 
         // Use both again.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
 
         vpnNetworkCallback.expectCapabilitiesThat(mMockVpn,
@@ -6422,8 +6511,7 @@
                 && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
                 && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
                 && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
-        // As above, the RESUMED callback not being sent here is a bug, but not a bug that's
-        // worth anybody's time to fix.
+        vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn);
         assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
 
         // Disconnect cell. Receive update without even removing the dead network from the
@@ -6550,9 +6638,7 @@
         addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
 
         // Send a USER_ADDED broadcast for it.
-        // The BroadcastReceiver for this broadcast checks that is being run on the handler thread.
-        final Handler handler = new Handler(mCsHandlerThread.getLooper());
-        handler.post(() -> mServiceContext.sendBroadcast(addedIntent));
+        processBroadcastForVpn(addedIntent);
 
         // Expect that the VPN UID ranges contain both |uid| and the UID range for the newly-added
         // restricted user.
@@ -6576,7 +6662,7 @@
         // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user.
         final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
         removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
-        handler.post(() -> mServiceContext.sendBroadcast(removedIntent));
+        processBroadcastForVpn(removedIntent);
 
         // Expect that the VPN gains the UID range for the restricted user, and that the capability
         // change made just before that (i.e., loss of TRANSPORT_WIFI) is preserved.
@@ -6633,9 +6719,7 @@
         // TODO: check that VPN app within restricted profile still has access, etc.
         final Intent addedIntent = new Intent(ACTION_USER_ADDED);
         addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
-        final Handler handler = new Handler(mCsHandlerThread.getLooper());
-        handler.post(() -> mServiceContext.sendBroadcast(addedIntent));
-        waitForIdle();
+        processBroadcastForVpn(addedIntent);
         assertNull(mCm.getActiveNetworkForUid(uid));
         assertNull(mCm.getActiveNetworkForUid(restrictedUid));
 
@@ -6645,8 +6729,7 @@
         // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user.
         final Intent removedIntent = new Intent(ACTION_USER_REMOVED);
         removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER);
-        handler.post(() -> mServiceContext.sendBroadcast(removedIntent));
-        waitForIdle();
+        processBroadcastForVpn(removedIntent);
         assertNull(mCm.getActiveNetworkForUid(uid));
         assertNotNull(mCm.getActiveNetworkForUid(restrictedUid));
 
@@ -6748,7 +6831,7 @@
         // Ensure VPN is now the active network.
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
         // VPN is using Cell
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6756,7 +6839,7 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // VPN is now using WiFi
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6764,7 +6847,7 @@
         assertFalse(mCm.isActiveNetworkMetered());
 
         // VPN is using Cell | WiFi.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6772,7 +6855,7 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // VPN is using WiFi | Cell.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mWiFiNetworkAgent.getNetwork(), mCellNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6780,7 +6863,7 @@
         assertTrue(mCm.isActiveNetworkMetered());
 
         // VPN is not using any underlying networks.
-        mService.setUnderlyingNetworksForVpn(new Network[0]);
+        mMockVpn.setUnderlyingNetworks(new Network[0]);
         waitForIdle();
 
         // VPN without underlying networks is treated as metered.
@@ -6807,7 +6890,7 @@
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
 
         // VPN is tracking current platform default (WiFi).
-        mService.setUnderlyingNetworksForVpn(null);
+        mMockVpn.setUnderlyingNetworks(null);
         waitForIdle();
 
         // Despite VPN using WiFi (which is unmetered), VPN itself is marked as always metered.
@@ -6815,7 +6898,7 @@
 
 
         // VPN explicitly declares WiFi as its underlying network.
-        mService.setUnderlyingNetworksForVpn(
+        mMockVpn.setUnderlyingNetworks(
                 new Network[] { mWiFiNetworkAgent.getNetwork() });
         waitForIdle();
 
@@ -6839,13 +6922,20 @@
                 .addTransportType(TRANSPORT_CELLULAR)
                 .build();
         mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
+        mockUidNetworkingBlocked();
 
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
         cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
 
         setUidRulesChanged(RULE_REJECT_ALL);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+        assertNull(mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
 
         // ConnectivityService should cache it not to invoke the callback again.
         setUidRulesChanged(RULE_REJECT_METERED);
@@ -6853,32 +6943,60 @@
 
         setUidRulesChanged(RULE_NONE);
         cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
 
         setUidRulesChanged(RULE_REJECT_METERED);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+        assertNull(mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
 
         // Restrict the network based on UID rule and NOT_METERED capability change.
         mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
         cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
         cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+
         mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
         cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
                 mCellNetworkAgent);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+        assertNull(mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
+
         setUidRulesChanged(RULE_ALLOW_METERED);
         cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
 
         setUidRulesChanged(RULE_NONE);
         cellNetworkCallback.assertNoCallback();
 
-        // Restrict the network based on BackgroundRestricted.
+        // Restrict background data. Networking is not blocked because the network is unmetered.
         setRestrictBackgroundChanged(true);
         cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+        assertNull(mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
         setRestrictBackgroundChanged(true);
         cellNetworkCallback.assertNoCallback();
-        setRestrictBackgroundChanged(false);
+
+        setUidRulesChanged(RULE_ALLOW_METERED);
         cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+
+        setRestrictBackgroundChanged(false);
         cellNetworkCallback.assertNoCallback();
+        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+        assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
+        assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
 
         mCm.unregisterNetworkCallback(cellNetworkCallback);
     }
@@ -6887,6 +7005,7 @@
     public void testNetworkBlockedStatusBeforeAndAfterConnect() throws Exception {
         final TestNetworkCallback defaultCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultCallback);
+        mockUidNetworkingBlocked();
 
         // No Networkcallbacks invoked before any network is active.
         setUidRulesChanged(RULE_REJECT_ALL);
@@ -7156,6 +7275,13 @@
         when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile);
     }
 
+    private void establishLegacyLockdownVpn() throws Exception {
+        // The legacy lockdown VPN only supports userId 0.
+        final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
+        mMockVpn.registerAgent(ranges);
+        mMockVpn.connect(true);
+    }
+
     @Test
     public void testLegacyLockdownVpn() throws Exception {
         mServiceContext.setPermission(
@@ -7180,9 +7306,7 @@
         final int userId = UserHandle.getUserId(Process.myUid());
         final Intent addedIntent = new Intent(ACTION_USER_UNLOCKED);
         addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        final Handler handler = new Handler(mCsHandlerThread.getLooper());
-        handler.post(() -> mServiceContext.sendBroadcast(addedIntent));
-        waitForIdle();
+        processBroadcastForVpn(addedIntent);
 
         // Lockdown VPN disables teardown and enables lockdown.
         assertFalse(mMockVpn.getEnableTeardown());
@@ -7250,22 +7374,30 @@
         mMockVpn.expectStartLegacyVpnRunner();
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
-        mMockVpn.establishForMyUid();
+        establishLegacyLockdownVpn();
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+        NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
         b1.expectBroadcast();
         b2.expectBroadcast();
         assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
+        assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
+        assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
+        assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
+        assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect.
         final LinkProperties wifiLp = new LinkProperties();
         wifiLp.setInterfaceName("wlan0");
         wifiLp.addLinkAddress(new LinkAddress("192.0.2.163/25"));
         wifiLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "wlan0"));
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
+        final NetworkCapabilities wifiNc = new NetworkCapabilities();
+        wifiNc.addTransportType(TRANSPORT_WIFI);
+        wifiNc.addCapability(NET_CAPABILITY_NOT_METERED);
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc);
 
         b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED);
         // Wifi is CONNECTING because the VPN isn't up yet.
@@ -7298,16 +7430,20 @@
         // The VPN comes up again on wifi.
         b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
         b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED);
-        mMockVpn.establishForMyUid();
+        establishLegacyLockdownVpn();
         callback.expectAvailableThenValidatedCallbacks(mMockVpn);
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         b1.expectBroadcast();
         b2.expectBroadcast();
-
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
+        vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
+        assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
+        assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI));
+        assertFalse(vpnNc.hasTransport(TRANSPORT_CELLULAR));
+        assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED));
 
         // Disconnect cell. Nothing much happens since it's not the default network.
         // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any
@@ -7335,39 +7471,68 @@
         b2.expectBroadcast();
     }
 
+    /**
+     * Test mutable and requestable network capabilities such as
+     * {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and
+     * {@link NetworkCapabilities#NET_CAPABILITY_NOT_VCN_MANAGED}. Verify that the
+     * {@code ConnectivityService} re-assign the networks accordingly.
+     */
     @Test
-    public final void testLoseTrusted() throws Exception {
-        final NetworkRequest trustedRequest = new NetworkRequest.Builder()
-                .addCapability(NET_CAPABILITY_TRUSTED)
-                .build();
-        final TestNetworkCallback trustedCallback = new TestNetworkCallback();
-        mCm.requestNetwork(trustedRequest, trustedCallback);
+    public final void testLoseMutableAndRequestableCaps() throws Exception {
+        final int[] testCaps = new int [] {
+                NET_CAPABILITY_TRUSTED,
+                NET_CAPABILITY_NOT_VCN_MANAGED
+        };
+        for (final int testCap : testCaps) {
+            // Create requests with and without the testing capability.
+            final TestNetworkCallback callbackWithCap = new TestNetworkCallback();
+            final TestNetworkCallback callbackWithoutCap = new TestNetworkCallback();
+            mCm.requestNetwork(new NetworkRequest.Builder().addCapability(testCap).build(),
+                    callbackWithCap);
+            mCm.requestNetwork(new NetworkRequest.Builder().removeCapability(testCap).build(),
+                    callbackWithoutCap);
 
-        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
-        mCellNetworkAgent.connect(true);
-        trustedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
-        verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
-        reset(mMockNetd);
+            // Setup networks with testing capability and verify the default network changes.
+            mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+            mCellNetworkAgent.addCapability(testCap);
+            mCellNetworkAgent.connect(true);
+            callbackWithCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+            callbackWithoutCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+            verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+            reset(mMockNetd);
 
-        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.connect(true);
-        trustedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
-        verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId));
-        reset(mMockNetd);
+            mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+            mWiFiNetworkAgent.addCapability(testCap);
+            mWiFiNetworkAgent.connect(true);
+            callbackWithCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+            callbackWithoutCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+            verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId));
+            reset(mMockNetd);
 
-        mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED);
-        trustedCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
-        verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
-        reset(mMockNetd);
+            // Remove the testing capability on wifi, verify the callback and default network
+            // changes back to cellular.
+            mWiFiNetworkAgent.removeCapability(testCap);
+            callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent);
+            callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent);
+            // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has
+            //  it.
+            if (testCap == NET_CAPABILITY_TRUSTED) {
+                verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId));
+                reset(mMockNetd);
+            }
 
-        mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED);
-        trustedCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
-        verify(mMockNetd).networkClearDefault();
+            mCellNetworkAgent.removeCapability(testCap);
+            callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+            callbackWithoutCap.assertNoCallback();
+            if (testCap == NET_CAPABILITY_TRUSTED) {
+                verify(mMockNetd).networkClearDefault();
+            }
 
-        mCm.unregisterNetworkCallback(trustedCallback);
+            mCm.unregisterNetworkCallback(callbackWithCap);
+            mCm.unregisterNetworkCallback(callbackWithoutCap);
+        }
     }
 
-    @Ignore // 40%+ flakiness : figure out why and re-enable.
     @Test
     public final void testBatteryStatsNetworkType() throws Exception {
         final LinkProperties cellLp = new LinkProperties();
@@ -7375,8 +7540,8 @@
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
         mCellNetworkAgent.connect(true);
         waitForIdle();
-        verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
-                TYPE_MOBILE);
+        verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(),
+                new int[] { TRANSPORT_CELLULAR });
         reset(mBatteryStatsService);
 
         final LinkProperties wifiLp = new LinkProperties();
@@ -7384,18 +7549,20 @@
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
         mWiFiNetworkAgent.connect(true);
         waitForIdle();
-        verify(mBatteryStatsService).noteNetworkInterfaceType(wifiLp.getInterfaceName(),
-                TYPE_WIFI);
+        verify(mBatteryStatsService).noteNetworkInterfaceForTransports(wifiLp.getInterfaceName(),
+                new int[] { TRANSPORT_WIFI });
         reset(mBatteryStatsService);
 
         mCellNetworkAgent.disconnect();
+        mWiFiNetworkAgent.disconnect();
 
         cellLp.setInterfaceName("wifi0");
         mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
         mCellNetworkAgent.connect(true);
         waitForIdle();
-        verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
-                TYPE_MOBILE);
+        verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(),
+                new int[] { TRANSPORT_CELLULAR });
+        mCellNetworkAgent.disconnect();
     }
 
     /**
@@ -7468,8 +7635,8 @@
         assertRoutesAdded(cellNetId, ipv6Subnet, defaultRoute);
         verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
         verify(mMockNetd, times(1)).networkAddInterface(cellNetId, MOBILE_IFNAME);
-        verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(),
-                TYPE_MOBILE);
+        verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(),
+                new int[] { TRANSPORT_CELLULAR });
 
         networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
@@ -7489,7 +7656,8 @@
         // Make sure BatteryStats was not told about any v4- interfaces, as none should have
         // come online yet.
         waitForIdle();
-        verify(mBatteryStatsService, never()).noteNetworkInterfaceType(startsWith("v4-"), anyInt());
+        verify(mBatteryStatsService, never()).noteNetworkInterfaceForTransports(startsWith("v4-"),
+                any());
 
         verifyNoMoreInteractions(mMockNetd);
         verifyNoMoreInteractions(mMockDnsResolver);
@@ -7542,8 +7710,8 @@
         assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8"));
 
         for (final LinkProperties stackedLp : stackedLpsAfterChange) {
-            verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(),
-                    TYPE_MOBILE);
+            verify(mBatteryStatsService).noteNetworkInterfaceForTransports(
+                    stackedLp.getInterfaceName(), new int[] { TRANSPORT_CELLULAR });
         }
         reset(mMockNetd);
         when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME))
@@ -8319,13 +8487,14 @@
     private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
             throws Exception {
         final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(PRIMARY_USER));
+        mMockVpn.setVpnType(vpnType);
         mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange);
         assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid);
-        mMockVpn.setVpnType(vpnType);
 
-        final VpnInfo vpnInfo = new VpnInfo();
-        vpnInfo.ownerUid = vpnOwnerUid;
-        mMockVpn.setVpnInfo(vpnInfo);
+        final UnderlyingNetworkInfo underlyingNetworkInfo =
+                new UnderlyingNetworkInfo(vpnOwnerUid, VPN_IFNAME, new ArrayList<String>());
+        mMockVpn.setUnderlyingNetworkInfo(underlyingNetworkInfo);
+        when(mDeps.getConnectionOwnerUid(anyInt(), any(), any())).thenReturn(42);
     }
 
     private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
@@ -8374,8 +8543,7 @@
         final int myUid = Process.myUid();
         setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE);
 
-        // TODO: Test the returned UID
-        mService.getConnectionOwnerUid(getTestConnectionInfo());
+        assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     @Test
@@ -8385,8 +8553,7 @@
         mServiceContext.setPermission(
                 android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
 
-        // TODO: Test the returned UID
-        mService.getConnectionOwnerUid(getTestConnectionInfo());
+        assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     @Test
@@ -8397,8 +8564,7 @@
         mServiceContext.setPermission(
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED);
 
-        // TODO: Test the returned UID
-        mService.getConnectionOwnerUid(getTestConnectionInfo());
+        assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo()));
     }
 
     private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
@@ -8582,7 +8748,7 @@
         setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
                 Manifest.permission.ACCESS_FINE_LOCATION);
 
-        assertTrue(mService.setUnderlyingNetworksForVpn(new Network[] {naiWithoutUid.network}));
+        assertTrue(mMockVpn.setUnderlyingNetworks(new Network[] {naiWithoutUid.network}));
         waitForIdle();
         assertTrue(
                 "Active VPN permission not applied",
@@ -8590,7 +8756,7 @@
                         Process.myPid(), Process.myUid(), naiWithoutUid,
                         mContext.getOpPackageName()));
 
-        assertTrue(mService.setUnderlyingNetworksForVpn(null));
+        assertTrue(mMockVpn.setUnderlyingNetworks(null));
         waitForIdle();
         assertFalse(
                 "VPN shouldn't receive callback on non-underlying network",
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 68aaaed..73cc9f1 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -49,6 +49,7 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -119,6 +120,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -148,6 +150,7 @@
         managedProfileA.profileGroupId = primaryUser.id;
     }
 
+    static final Network EGRESS_NETWORK = new Network(101);
     static final String EGRESS_IFACE = "wlan0";
     static final String TEST_VPN_PKG = "com.testvpn.vpn";
     private static final String TEST_VPN_SERVER = "1.2.3.4";
@@ -212,6 +215,8 @@
 
         when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG);
         when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG);
+        when(mContext.getSystemServiceName(UserManager.class))
+                .thenReturn(Context.USER_SERVICE);
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
         when(mContext.getSystemServiceName(NotificationManager.class))
@@ -252,12 +257,14 @@
 
     @Test
     public void testRestrictedProfilesAreAddedToVpn() {
-        if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API.
         setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
 
         final Vpn vpn = createVpn(primaryUser.id);
-        final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
-                null, null);
+
+        // Assume the user can have restricted profiles.
+        doReturn(true).when(mUserManager).canHaveRestrictedProfile();
+        final Set<UidRange> ranges =
+                vpn.createUserAndRestrictedProfilesRanges(primaryUser.id, null, null);
 
         assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
                 PRI_USER_RANGE, UidRange.createForUser(restrictedProfileA.id)
@@ -266,7 +273,6 @@
 
     @Test
     public void testManagedProfilesAreNotAddedToVpn() {
-        if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API.
         setMockedUsers(primaryUser, managedProfileA);
 
         final Vpn vpn = createVpn(primaryUser.id);
@@ -289,7 +295,6 @@
 
     @Test
     public void testUidAllowAndDenylist() throws Exception {
-        if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API.
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = PRI_USER_RANGE;
         final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
@@ -315,7 +320,6 @@
 
     @Test
     public void testGetAlwaysAndOnGetLockDown() throws Exception {
-        if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API.
         final Vpn vpn = createVpn(primaryUser.id);
 
         // Default state.
@@ -340,7 +344,6 @@
 
     @Test
     public void testLockdownChangingPackage() throws Exception {
-        if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API.
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = PRI_USER_RANGE;
 
@@ -368,7 +371,6 @@
 
     @Test
     public void testLockdownAllowlist() throws Exception {
-        if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API.
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = PRI_USER_RANGE;
 
@@ -443,7 +445,6 @@
 
     @Test
     public void testLockdownRuleRepeatability() throws Exception {
-        if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API.
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
                 new UidRangeParcel(PRI_USER_RANGE.start, PRI_USER_RANGE.stop)};
@@ -476,7 +477,6 @@
 
     @Test
     public void testLockdownRuleReversibility() throws Exception {
-        if (true) return; // TODO(b/175883995): Test disabled until updated for new UserManager API.
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRangeParcel[] entireUser = {
             new UidRangeParcel(PRI_USER_RANGE.start, PRI_USER_RANGE.stop)
@@ -963,7 +963,7 @@
                         InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
         lp.addRoute(defaultRoute);
 
-        vpn.startLegacyVpn(vpnProfile, mKeyStore, lp);
+        vpn.startLegacyVpn(vpnProfile, mKeyStore, EGRESS_NETWORK, lp);
         return vpn;
     }
 
@@ -996,14 +996,12 @@
         profile.ipsecIdentifier = "id";
         profile.ipsecSecret = "secret";
         profile.l2tpSecret = "l2tpsecret";
+
         when(mConnectivityManager.getAllNetworks())
             .thenReturn(new Network[] { new Network(101) });
+
         when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(),
-                anyInt(), any(), anyInt())).thenAnswer(invocation -> {
-                    // The runner has registered an agent and is now ready.
-                    legacyRunnerReady.open();
-                    return new Network(102);
-                });
+                anyInt(), any(), anyInt())).thenReturn(new Network(102));
         final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile);
         final TestDeps deps = (TestDeps) vpn.mDeps;
         try {
@@ -1019,14 +1017,20 @@
                             "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns",
                             "idle", "1800", "mtu", "1270", "mru", "1270" },
                     deps.mtpdArgs.get(10, TimeUnit.SECONDS));
+
             // Now wait for the runner to be ready before testing for the route.
-            legacyRunnerReady.block(10_000);
-            // In this test the expected address is always v4 so /32
+            ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
+            verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
+                    lpCaptor.capture(), any(), anyInt(), any(), anyInt());
+
+            // In this test the expected address is always v4 so /32.
+            // Note that the interface needs to be specified because RouteInfo objects stored in
+            // LinkProperties objects always acquire the LinkProperties' interface.
             final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"),
-                    RouteInfo.RTN_THROW);
-            assertTrue("Routes lack the expected throw route (" + expectedRoute + ") : "
-                    + vpn.mConfig.routes,
-                    vpn.mConfig.routes.contains(expectedRoute));
+                    null, EGRESS_IFACE, RouteInfo.RTN_THROW);
+            final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes();
+            assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes,
+                    actualRoutes.contains(expectedRoute));
         } finally {
             // Now interrupt the thread, unblock the runner and clean up.
             vpn.mVpnRunner.exitVpnRunner();
@@ -1082,6 +1086,11 @@
         }
 
         @Override
+        public PendingIntent getIntentForStatusPanel(Context context) {
+            return null;
+        }
+
+        @Override
         public void sendArgumentsToDaemon(
                 final String daemon, final LocalSocket socket, final String[] arguments,
                 final Vpn.RetryScheduler interruptChecker) throws IOException {
@@ -1178,11 +1187,6 @@
             final int id = (int) invocation.getArguments()[0];
             return userMap.get(id);
         }).when(mUserManager).getUserInfo(anyInt());
-
-        doAnswer(invocation -> {
-            final int id = (int) invocation.getArguments()[0];
-            return (userMap.get(id).flags & UserInfo.FLAG_ADMIN) != 0;
-        }).when(mUserManager).canHaveRestrictedProfile();
     }
 
     /**
diff --git a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
index 3aafe0b..a058a46 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java
@@ -33,8 +33,9 @@
 import static org.junit.Assert.assertEquals;
 
 import android.net.NetworkStats;
+import android.net.UnderlyingNetworkInfo;
 
-import com.android.internal.net.VpnInfo;
+import java.util.Arrays;
 
 /** Superclass with utilities for NetworkStats(Service|Factory)Test */
 abstract class NetworkStatsBaseTest {
@@ -108,15 +109,11 @@
         assertEquals("unexpected operations", operations, entry.operations);
     }
 
-    static VpnInfo createVpnInfo(String[] underlyingIfaces) {
+    static UnderlyingNetworkInfo createVpnInfo(String[] underlyingIfaces) {
         return createVpnInfo(TUN_IFACE, underlyingIfaces);
     }
 
-    static VpnInfo createVpnInfo(String vpnIface, String[] underlyingIfaces) {
-        VpnInfo info = new VpnInfo();
-        info.ownerUid = UID_VPN;
-        info.vpnIface = vpnIface;
-        info.underlyingIfaces = underlyingIfaces;
-        return info;
+    static UnderlyingNetworkInfo createVpnInfo(String vpnIface, String[] underlyingIfaces) {
+        return new UnderlyingNetworkInfo(UID_VPN, vpnIface, Arrays.asList(underlyingIfaces));
     }
 }
diff --git a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
index e4996d9..f3ae9b0 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -36,13 +36,13 @@
 import android.content.res.Resources;
 import android.net.NetworkStats;
 import android.net.TrafficStats;
+import android.net.UnderlyingNetworkInfo;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.tests.net.R;
-import com.android.internal.net.VpnInfo;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -79,7 +79,7 @@
         // related to networkStatsFactory is compiled to a minimal native library and loaded here.
         System.loadLibrary("networkstatsfactorytestjni");
         mFactory = new NetworkStatsFactory(mTestProc, false);
-        mFactory.updateVpnInfos(new VpnInfo[0]);
+        mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
     }
 
     @After
@@ -105,8 +105,9 @@
 
     @Test
     public void testVpnRewriteTrafficThroughItself() throws Exception {
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        mFactory.updateVpnInfos(vpnInfos);
+        UnderlyingNetworkInfo[] underlyingNetworkInfos =
+                new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
@@ -134,8 +135,9 @@
 
     @Test
     public void testVpnWithClat() throws Exception {
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {CLAT_PREFIX + TEST_IFACE})};
-        mFactory.updateVpnInfos(vpnInfos);
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos = new UnderlyingNetworkInfo[] {
+                createVpnInfo(new String[] {CLAT_PREFIX + TEST_IFACE})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
         mFactory.noteStackedIface(CLAT_PREFIX + TEST_IFACE, TEST_IFACE);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
@@ -167,8 +169,9 @@
 
     @Test
     public void testVpnWithOneUnderlyingIface() throws Exception {
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        mFactory.updateVpnInfos(vpnInfos);
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos =
+                new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
@@ -191,8 +194,9 @@
     @Test
     public void testVpnWithOneUnderlyingIfaceAndOwnTraffic() throws Exception {
         // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        mFactory.updateVpnInfos(vpnInfos);
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos =
+                new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
@@ -219,8 +223,9 @@
     @Test
     public void testVpnWithOneUnderlyingIface_withCompression() throws Exception {
         // WiFi network is connected and VPN is using WiFi (which has TEST_IFACE).
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        mFactory.updateVpnInfos(vpnInfos);
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos =
+                new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
@@ -242,8 +247,9 @@
         // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
         // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
         // Additionally, VPN is duplicating traffic across both WiFi and Cell.
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        mFactory.updateVpnInfos(vpnInfos);
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos =
+                new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
@@ -267,10 +273,10 @@
     public void testConcurrentVpns() throws Exception {
         // Assume two VPNs are connected on two different network interfaces. VPN1 is using
         // TEST_IFACE and VPN2 is using TEST_IFACE2.
-        final VpnInfo[] vpnInfos = new VpnInfo[] {
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos = new UnderlyingNetworkInfo[] {
                 createVpnInfo(TUN_IFACE, new String[] {TEST_IFACE}),
                 createVpnInfo(TUN_IFACE2, new String[] {TEST_IFACE2})};
-        mFactory.updateVpnInfos(vpnInfos);
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
@@ -308,8 +314,9 @@
         // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
         // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
         // Additionally, VPN is arbitrarily splitting traffic across WiFi and Cell.
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        mFactory.updateVpnInfos(vpnInfos);
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos =
+                new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
@@ -335,8 +342,9 @@
         // WiFi and Cell networks are connected and VPN is using WiFi (which has TEST_IFACE) and
         // Cell (which has TEST_IFACE2) and has declared both of them in its underlying network set.
         // Additionally, VPN is arbitrarily splitting compressed traffic across WiFi and Cell.
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
-        mFactory.updateVpnInfos(vpnInfos);
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos =
+                new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE, TEST_IFACE2})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface:
         // 1000 bytes (100 packets) were sent/received by UID_RED over VPN.
@@ -357,8 +365,9 @@
     public void testVpnWithIncorrectUnderlyingIface() throws Exception {
         // WiFi and Cell networks are connected and VPN is using Cell (which has TEST_IFACE2),
         // but has declared only WiFi (TEST_IFACE) in its underlying network set.
-        VpnInfo[] vpnInfos = new VpnInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
-        mFactory.updateVpnInfos(vpnInfos);
+        final UnderlyingNetworkInfo[] underlyingNetworkInfos =
+                new UnderlyingNetworkInfo[] {createVpnInfo(new String[] {TEST_IFACE})};
+        mFactory.updateUnderlyingNetworkInfos(underlyingNetworkInfos);
 
         // create some traffic (assume 10 bytes of MTU for VPN interface and 1 byte encryption
         // overhead per packet):
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index c783629..dde78aa 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -21,7 +21,6 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.ConnectivityManager.TYPE_WIMAX;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
@@ -44,6 +43,7 @@
 import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateMobileWithRatType;
+import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.net.TrafficStats.UID_REMOVED;
@@ -86,6 +86,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.UnderlyingNetworkInfo;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -104,7 +105,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
@@ -146,7 +146,7 @@
     private static final String IMSI_2 = "310260";
     private static final String TEST_SSID = "AndroidAP";
 
-    private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard();
+    private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID);
     private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
     private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
 
@@ -286,12 +286,12 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
 
-
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(HOUR_IN_MILLIS);
         expectDefaultSettings();
@@ -329,7 +329,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -402,7 +403,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // modify some number on wifi, and trigger poll event
         incrementCurrentTime(2 * HOUR_IN_MILLIS);
@@ -442,7 +444,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // create some traffic on first network
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -476,7 +479,8 @@
                 .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
                 .insertEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
         forcePollAndWaitForIdle();
 
 
@@ -515,7 +519,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -567,61 +572,6 @@
     }
 
     @Test
-    public void testUid3gWimaxCombinedByTemplate() throws Exception {
-        // pretend that network comes online
-        expectDefaultSettings();
-        NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)};
-        expectNetworkStatsSummary(buildEmptyStats());
-        expectNetworkStatsUidDetail(buildEmptyStats());
-
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
-
-        // create some traffic
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectDefaultSettings();
-        expectNetworkStatsSummary(buildEmptyStats());
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
-        mService.incrementOperationCount(UID_RED, 0xF00D, 5);
-
-        forcePollAndWaitForIdle();
-
-        // verify service recorded history
-        assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5);
-
-
-        // now switch over to wimax network
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectDefaultSettings();
-        states = new NetworkState[] {buildWimaxState(TEST_IFACE2)};
-        expectNetworkStatsSummary(buildEmptyStats());
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
-
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
-        forcePollAndWaitForIdle();
-
-
-        // create traffic on second network
-        incrementCurrentTime(HOUR_IN_MILLIS);
-        expectDefaultSettings();
-        expectNetworkStatsSummary(buildEmptyStats());
-        expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .insertEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
-                .insertEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
-        mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
-
-        forcePollAndWaitForIdle();
-
-        // verify that ALL_MOBILE template combines both
-        assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
-    }
-
-    @Test
     public void testMobileStatsByRatType() throws Exception {
         final NetworkTemplate template3g =
                 buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS);
@@ -637,7 +587,7 @@
 
         setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS);
         mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
-                new VpnInfo[0]);
+                new UnderlyingNetworkInfo[0]);
 
         // Create some traffic.
         incrementCurrentTime(MINUTE_IN_MILLIS);
@@ -711,7 +661,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // create some traffic for two apps
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -769,7 +720,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         NetworkStats.Entry entry1 = new NetworkStats.Entry(
                 TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L);
@@ -812,7 +764,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         NetworkStats.Entry uidStats = new NetworkStats.Entry(
                 TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xF00D, 1024L, 8L, 512L, 4L, 0L);
@@ -866,7 +819,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // create some initial traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -923,7 +877,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // create some initial traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -962,7 +917,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // Create some traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -999,7 +955,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // create some tethering traffic
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -1055,7 +1012,8 @@
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(buildEmptyStats());
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // verify service has empty history for wifi
         assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -1160,7 +1118,8 @@
                 mService.registerNetworkStatsProvider("TEST", provider);
         assertNotNull(cb);
 
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // Verifies that one requestStatsUpdate will be called during iface update.
         provider.expectOnRequestStatsUpdate(0 /* unused */);
@@ -1211,7 +1170,8 @@
         expectDefaultSettings();
         NetworkState[] states =
                 new NetworkState[]{buildWifiState(true /* isMetered */, TEST_IFACE)};
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // Register custom provider and retrieve callback.
         final TestableNetworkStatsProviderBinder provider =
@@ -1260,7 +1220,7 @@
         // 3G network comes online.
         setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_UMTS);
         mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
-                new VpnInfo[0]);
+                new UnderlyingNetworkInfo[0]);
 
         // Create some traffic.
         incrementCurrentTime(MINUTE_IN_MILLIS);
@@ -1330,7 +1290,8 @@
         NetworkState[] states = new NetworkState[]{
                 buildWifiState(true /*isMetered*/, TEST_IFACE2), buildMobile3gState(IMSI_1)};
         expectNetworkStatsUidDetail(buildEmptyStats());
-        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states),
+                new UnderlyingNetworkInfo[0]);
 
         // Create some traffic on mobile network.
         incrementCurrentTime(HOUR_IN_MILLIS);
@@ -1503,6 +1464,7 @@
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !isMetered);
         capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true);
         capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        capabilities.setSSID(TEST_SSID);
         return new NetworkState(info, prop, capabilities, WIFI_NETWORK, null, TEST_SSID);
     }
 
@@ -1524,17 +1486,6 @@
         return new NetworkState(info, prop, capabilities, MOBILE_NETWORK, subscriberId, null);
     }
 
-    private static NetworkState buildWimaxState(@NonNull String iface) {
-        final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null);
-        info.setDetailedState(DetailedState.CONNECTED, null, null);
-        final LinkProperties prop = new LinkProperties();
-        prop.setInterfaceName(iface);
-        final NetworkCapabilities capabilities = new NetworkCapabilities();
-        capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false);
-        capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true);
-        return new NetworkState(info, prop, capabilities, MOBILE_NETWORK, null, null);
-    }
-
     private NetworkStats buildEmptyStats() {
         return new NetworkStats(getElapsedRealtime(), 0);
     }
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index aeb142b..1fe13fe 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -28,8 +28,6 @@
 
 import junit.framework.TestCase;
 
-import org.junit.Test;
-
 /**
  * TODO: Remove this. This is only a placeholder, need to implement this.
  */
@@ -56,7 +54,7 @@
         }
 
         try {
-            mWm.addWindowToken(null, TYPE_APPLICATION, DEFAULT_DISPLAY);
+            mWm.addWindowToken(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null /* options */);
             fail("IWindowManager.addWindowToken did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
@@ -155,29 +153,4 @@
             fail("Unexpected remote exception");
         }
     }
-
-    @Test
-    public void testADD_WINDOW_TOKEN_WITH_OPTIONS() {
-        // Verify if addWindowTokenWithOptions throw SecurityException for privileged window type.
-        try {
-            mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, "");
-            fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        } catch (RemoteException e) {
-            fail("Unexpected remote exception");
-        }
-
-        // Verify if addWindowTokenWithOptions throw SecurityException for null packageName.
-        try {
-            mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, null);
-            fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        } catch (RemoteException e) {
-            fail("Unexpected remote exception");
-        }
-    }
 }
diff --git a/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java b/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java
new file mode 100644
index 0000000..5ab4dc6
--- /dev/null
+++ b/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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.fsverity;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.rules.ExternalResource;
+
+public final class AddFsVerityCertRule extends ExternalResource {
+
+    private static final String APK_VERITY_STANDARD_MODE = "2";
+
+    private final BaseHostJUnit4Test mHost;
+    private final String mCertPath;
+    private String mKeyId;
+
+    public AddFsVerityCertRule(BaseHostJUnit4Test host, String certPath) {
+        mHost = host;
+        mCertPath = certPath;
+    }
+
+    @Override
+    protected void before() throws Throwable {
+        ITestDevice device = mHost.getDevice();
+        String apkVerityMode = device.getProperty("ro.apk_verity.mode");
+        assumeTrue(device.getLaunchApiLevel() >= 30
+                || APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
+
+        String keyId = executeCommand(
+                "mini-keyctl padd asymmetric fsv_test .fs-verity < " + mCertPath).trim();
+        assertThat(keyId).matches("^\\d+$");
+        mKeyId = keyId;
+    }
+
+    @Override
+    protected void after() {
+        if (mKeyId == null) return;
+        try {
+            executeCommand("mini-keyctl unlink " + mKeyId + " .fs-verity");
+        } catch (DeviceNotAvailableException e) {
+            LogUtil.CLog.e(e);
+        }
+        mKeyId = null;
+    }
+
+    private String executeCommand(String cmd) throws DeviceNotAvailableException {
+        CommandResult result = mHost.getDevice().executeShellV2Command(cmd);
+        assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
+                .that(result.getStatus())
+                .isEqualTo(CommandStatus.SUCCESS);
+        return result.getStdout();
+    }
+}
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index a7d0860..a07bce3 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -46,6 +46,8 @@
             "android.view.InsetsSourceTest",
             "android.view.InsetsSourceConsumerTest",
             "android.view.InsetsStateTest",
+            "android.view.RoundedCornerTest",
+            "android.view.RoundedCornersTest",
             "android.view.WindowMetricsTest",
             "android.view.PendingInsetsControllerTest",
             "android.app.WindowContextTest",
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index dfd0c8a..3e659d0 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -28,6 +28,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -39,6 +40,12 @@
                 NetworkCapabilities.NET_CAPABILITY_INTERNET, NetworkCapabilities.NET_CAPABILITY_MMS
             };
     public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN};
+
+    static {
+        Arrays.sort(EXPOSED_CAPS);
+        Arrays.sort(UNDERLYING_CAPS);
+    }
+
     public static final long[] RETRY_INTERVALS_MS =
             new long[] {
                 TimeUnit.SECONDS.toMillis(5),
@@ -52,12 +59,17 @@
 
     // Public for use in VcnGatewayConnectionTest
     public static VcnGatewayConnectionConfig buildTestConfig() {
+        return buildTestConfigWithExposedCaps(EXPOSED_CAPS);
+    }
+
+    // Public for use in VcnGatewayConnectionTest
+    public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) {
         final VcnGatewayConnectionConfig.Builder builder =
                 new VcnGatewayConnectionConfig.Builder()
                         .setRetryInterval(RETRY_INTERVALS_MS)
                         .setMaxMtu(MAX_MTU);
 
-        for (int caps : EXPOSED_CAPS) {
+        for (int caps : exposedCaps) {
             builder.addExposedCapability(caps);
         }
 
@@ -124,12 +136,13 @@
     public void testBuilderAndGetters() {
         final VcnGatewayConnectionConfig config = buildTestConfig();
 
-        for (int cap : EXPOSED_CAPS) {
-            config.hasExposedCapability(cap);
-        }
-        for (int cap : UNDERLYING_CAPS) {
-            config.requiresUnderlyingCapability(cap);
-        }
+        int[] exposedCaps = config.getExposedCapabilities();
+        Arrays.sort(exposedCaps);
+        assertArrayEquals(EXPOSED_CAPS, exposedCaps);
+
+        int[] underlyingCaps = config.getRequiredUnderlyingCapabilities();
+        Arrays.sort(underlyingCaps);
+        assertArrayEquals(UNDERLYING_CAPS, underlyingCaps);
 
         assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMs());
         assertEquals(MAX_MTU, config.getMaxMtu());
diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
index 9c6b719..f9db408 100644
--- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java
@@ -18,14 +18,19 @@
 
 import static androidx.test.InstrumentationRegistry.getContext;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
 import android.net.vcn.VcnManager.VcnUnderlyingNetworkPolicyListener;
 
 import org.junit.Before;
@@ -103,4 +108,28 @@
     public void testRemoveVcnUnderlyingNetworkPolicyListenerNullListener() {
         mVcnManager.removeVcnUnderlyingNetworkPolicyListener(null);
     }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicy() throws Exception {
+        NetworkCapabilities nc = new NetworkCapabilities();
+        LinkProperties lp = new LinkProperties();
+        when(mMockVcnManagementService.getUnderlyingNetworkPolicy(eq(nc), eq(lp)))
+                .thenReturn(new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, nc));
+
+        VcnUnderlyingNetworkPolicy policy = mVcnManager.getUnderlyingNetworkPolicy(nc, lp);
+
+        assertFalse(policy.isTeardownRequested());
+        assertEquals(nc, policy.getMergedNetworkCapabilities());
+        verify(mMockVcnManagementService).getUnderlyingNetworkPolicy(eq(nc), eq(lp));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testGetUnderlyingNetworkPolicyNullNetworkCapabilities() throws Exception {
+        mVcnManager.getUnderlyingNetworkPolicy(null, new LinkProperties());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testGetUnderlyingNetworkPolicyNullLinkProperties() throws Exception {
+        mVcnManager.getUnderlyingNetworkPolicy(new NetworkCapabilities(), null);
+    }
 }
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 29cfdb6f..4859644 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -18,8 +18,10 @@
 
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -27,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.argThat;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
@@ -36,13 +39,20 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.NetworkCapabilities;
+import android.net.NetworkCapabilities.Transport;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnConfigTest;
+import android.net.vcn.VcnUnderlyingNetworkPolicy;
+import android.net.wifi.WifiInfo;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
@@ -56,12 +66,14 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.VcnManagementService.VcnSafemodeCallback;
 import com.android.server.vcn.TelephonySubscriptionTracker;
 import com.android.server.vcn.Vcn;
 import com.android.server.vcn.VcnContext;
 import com.android.server.vcn.VcnNetworkProvider;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -131,6 +143,9 @@
     private final TelephonySubscriptionTracker mSubscriptionTracker =
             mock(TelephonySubscriptionTracker.class);
 
+    private final ArgumentCaptor<VcnSafemodeCallback> mSafemodeCallbackCaptor =
+            ArgumentCaptor.forClass(VcnSafemodeCallback.class);
+
     private final VcnManagementService mVcnMgmtSvc;
 
     private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener =
@@ -138,11 +153,16 @@
     private final IBinder mMockIBinder = mock(IBinder.class);
 
     public VcnManagementServiceTest() throws Exception {
-        setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
-        setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class);
         setupSystemService(
-                mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class);
-        setupSystemService(mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class);
+                mMockContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
+        setupSystemService(
+                mMockContext, mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class);
+        setupSystemService(
+                mMockContext,
+                mSubMgr,
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE,
+                SubscriptionManager.class);
+        setupSystemService(mMockContext, mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class);
 
         doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName();
 
@@ -168,7 +188,7 @@
         doAnswer((invocation) -> {
             // Mock-within a doAnswer is safe, because it doesn't actually run nested.
             return mock(Vcn.class);
-        }).when(mMockDeps).newVcn(any(), any(), any());
+        }).when(mMockDeps).newVcn(any(), any(), any(), any(), any());
 
         final PersistableBundle bundle =
                 PersistableBundleUtils.fromMap(
@@ -186,9 +206,12 @@
         mTestLooper.dispatchAll();
     }
 
-    private void setupSystemService(Object service, String name, Class<?> serviceClass) {
-        doReturn(name).when(mMockContext).getSystemServiceName(serviceClass);
-        doReturn(service).when(mMockContext).getSystemService(name);
+    @Before
+    public void setUp() {
+        doNothing()
+                .when(mMockContext)
+                .enforceCallingOrSelfPermission(
+                        eq(android.Manifest.permission.NETWORK_FACTORY), any());
     }
 
     private void setupMockedCarrierPrivilege(boolean isPrivileged) {
@@ -238,7 +261,14 @@
         verify(mConfigReadWriteHelper).readFromDisk();
     }
 
-    private void triggerSubscriptionTrackerCallback(Set<ParcelUuid> activeSubscriptionGroups) {
+    private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+            Set<ParcelUuid> activeSubscriptionGroups) {
+        return triggerSubscriptionTrackerCbAndGetSnapshot(
+                activeSubscriptionGroups, Collections.emptyMap());
+    }
+
+    private TelephonySubscriptionSnapshot triggerSubscriptionTrackerCbAndGetSnapshot(
+            Set<ParcelUuid> activeSubscriptionGroups, Map<Integer, ParcelUuid> subIdToGroupMap) {
         final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
         doReturn(activeSubscriptionGroups).when(snapshot).getActiveSubscriptionGroups();
 
@@ -252,8 +282,14 @@
                         argThat(val -> activeSubscriptionGroups.contains(val)),
                         eq(TEST_PACKAGE_NAME));
 
+        doAnswer(invocation -> {
+            return subIdToGroupMap.get(invocation.getArgument(0));
+        }).when(snapshot).getGroupForSubId(anyInt());
+
         final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
         cb.onNewSnapshot(snapshot);
+
+        return snapshot;
     }
 
     private TelephonySubscriptionTrackerCallback getTelephonySubscriptionTrackerCallback() {
@@ -272,21 +308,25 @@
 
     @Test
     public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception {
-        triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_1));
-        verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG));
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+        verify(mMockDeps)
+                .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot), any());
     }
 
     @Test
     public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception {
         final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
         final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
-        triggerSubscriptionTrackerCallback(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
 
         // Verify teardown after delay
         mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
         mTestLooper.dispatchAll();
         verify(vcn).teardownAsynchronously();
+        verify(mMockPolicyListener).onPolicyChanged();
     }
 
     @Test
@@ -296,13 +336,13 @@
         final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
 
         // Simulate SIM unloaded
-        triggerSubscriptionTrackerCallback(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
 
         // Simulate new SIM loaded right during teardown delay.
         mTestLooper.moveTimeForward(
                 VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
         mTestLooper.dispatchAll();
-        triggerSubscriptionTrackerCallback(Collections.singleton(TEST_UUID_2));
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
 
         // Verify that even after the full timeout duration, the VCN instance is not torn down
         mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
@@ -316,7 +356,7 @@
         final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
 
         // Simulate SIM unloaded
-        triggerSubscriptionTrackerCallback(Collections.emptySet());
+        triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet());
 
         // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
         // vcnInstance.
@@ -357,6 +397,7 @@
             mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
             fail("Expected security exception for non system user");
         } catch (SecurityException expected) {
+            verify(mMockPolicyListener, never()).onPolicyChanged();
         }
     }
 
@@ -368,6 +409,7 @@
             mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
             fail("Expected security exception for missing carrier privileges");
         } catch (SecurityException expected) {
+            verify(mMockPolicyListener, never()).onPolicyChanged();
         }
     }
 
@@ -377,6 +419,7 @@
             mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage");
             fail("Expected exception due to mismatched packages in config and method call");
         } catch (IllegalArgumentException expected) {
+            verify(mMockPolicyListener, never()).onPolicyChanged();
         }
     }
 
@@ -441,7 +484,13 @@
         verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
 
         // Verify Vcn is started
-        verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG));
+        verify(mMockDeps)
+                .newVcn(
+                        eq(mVcnContext),
+                        eq(TEST_UUID_2),
+                        eq(TEST_VCN_CONFIG),
+                        eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT),
+                        any());
 
         // Verify Vcn is updated if it was previously started
         mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
@@ -454,10 +503,6 @@
 
     @Test
     public void testAddVcnUnderlyingNetworkPolicyListener() throws Exception {
-        doNothing()
-                .when(mMockContext)
-                .enforceCallingPermission(eq(android.Manifest.permission.NETWORK_FACTORY), any());
-
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         verify(mMockIBinder).linkToDeath(any(), anyInt());
@@ -467,17 +512,14 @@
     public void testAddVcnUnderlyingNetworkPolicyListenerInvalidPermission() {
         doThrow(new SecurityException())
                 .when(mMockContext)
-                .enforceCallingPermission(eq(android.Manifest.permission.NETWORK_FACTORY), any());
+                .enforceCallingOrSelfPermission(
+                        eq(android.Manifest.permission.NETWORK_FACTORY), any());
 
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
     }
 
     @Test
     public void testRemoveVcnUnderlyingNetworkPolicyListener() {
-        // verify listener added
-        doNothing()
-                .when(mMockContext)
-                .enforceCallingPermission(eq(android.Manifest.permission.NETWORK_FACTORY), any());
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
@@ -487,4 +529,135 @@
     public void testRemoveVcnUnderlyingNetworkPolicyListenerNeverRegistered() {
         mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
     }
+
+    private void setUpVcnSubscription(int subId, ParcelUuid subGroup) {
+        mVcnMgmtSvc.setVcnConfig(subGroup, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup));
+    }
+
+    private void verifyMergedNetworkCapabilitiesIsVcnManaged(
+            NetworkCapabilities mergedCapabilities, @Transport int transportType) {
+        assertTrue(mergedCapabilities.hasTransport(transportType));
+        assertFalse(
+                mergedCapabilities.hasCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED));
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyCellular() throws Exception {
+        setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
+
+        NetworkCapabilities nc =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+                        .build();
+
+        VcnUnderlyingNetworkPolicy policy =
+                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+
+        assertFalse(policy.isTeardownRequested());
+        verifyMergedNetworkCapabilitiesIsVcnManaged(
+                policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyWifi() throws Exception {
+        setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
+
+        WifiInfo wifiInfo = mock(WifiInfo.class);
+        when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo);
+        when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID);
+        NetworkCapabilities nc =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .setTransportInfo(wifiInfo)
+                        .build();
+
+        VcnUnderlyingNetworkPolicy policy =
+                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+
+        assertFalse(policy.isTeardownRequested());
+        verifyMergedNetworkCapabilitiesIsVcnManaged(
+                policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI);
+    }
+
+    @Test
+    public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception {
+        NetworkCapabilities nc =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID))
+                        .build();
+
+        VcnUnderlyingNetworkPolicy policy =
+                mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
+
+        assertFalse(policy.isTeardownRequested());
+        assertEquals(nc, policy.getMergedNetworkCapabilities());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testGetUnderlyingNetworkPolicyInvalidPermission() {
+        doThrow(new SecurityException())
+                .when(mMockContext)
+                .enforceCallingOrSelfPermission(
+                        eq(android.Manifest.permission.NETWORK_FACTORY), any());
+
+        mVcnMgmtSvc.getUnderlyingNetworkPolicy(new NetworkCapabilities(), new LinkProperties());
+    }
+
+    @Test
+    public void testSubscriptionSnapshotUpdateNotifiesVcn() {
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns();
+        final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2);
+
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2));
+
+        verify(vcnInstance).updateSubscriptionSnapshot(eq(snapshot));
+    }
+
+    @Test
+    public void testAddNewVcnUpdatesPolicyListener() throws Exception {
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+
+        verify(mMockPolicyListener).onPolicyChanged();
+    }
+
+    @Test
+    public void testRemoveVcnUpdatesPolicyListener() throws Exception {
+        mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+
+        verify(mMockPolicyListener).onPolicyChanged();
+    }
+
+    @Test
+    public void testVcnSafemodeCallbackOnEnteredSafemode() throws Exception {
+        TelephonySubscriptionSnapshot snapshot =
+                triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1));
+        verify(mMockDeps)
+                .newVcn(
+                        eq(mVcnContext),
+                        eq(TEST_UUID_1),
+                        eq(TEST_VCN_CONFIG),
+                        eq(snapshot),
+                        mSafemodeCallbackCaptor.capture());
+
+        mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
+
+        VcnSafemodeCallback safemodeCallback = mSafemodeCallbackCaptor.getValue();
+        safemodeCallback.onEnteredSafemode();
+
+        assertFalse(mVcnMgmtSvc.getAllVcns().get(TEST_UUID_1).isActive());
+        verify(mMockPolicyListener).onPolicyChanged();
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
new file mode 100644
index 0000000..1d459a3
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2021 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.vcn;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.TelephonyNetworkSpecifier;
+import android.os.ParcelUuid;
+import android.os.test.TestLooper;
+import android.telephony.SubscriptionInfo;
+import android.util.ArraySet;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback;
+import com.android.server.vcn.UnderlyingNetworkTracker.RouteSelectionCallback;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.UUID;
+
+public class UnderlyingNetworkTrackerTest {
+    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+    private static final int INITIAL_SUB_ID_1 = 1;
+    private static final int INITIAL_SUB_ID_2 = 2;
+    private static final int UPDATED_SUB_ID = 3;
+
+    private static final Set<Integer> INITIAL_SUB_IDS =
+            new ArraySet<>(Arrays.asList(INITIAL_SUB_ID_1, INITIAL_SUB_ID_2));
+    private static final Set<Integer> UPDATED_SUB_IDS =
+            new ArraySet<>(Arrays.asList(UPDATED_SUB_ID));
+
+    private static final NetworkCapabilities INITIAL_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                    .build();
+    private static final NetworkCapabilities SUSPENDED_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder(INITIAL_NETWORK_CAPABILITIES)
+                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                    .build();
+    private static final NetworkCapabilities UPDATED_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .build();
+
+    private static final LinkProperties INITIAL_LINK_PROPERTIES =
+            getLinkPropertiesWithName("initial_iface");
+    private static final LinkProperties UPDATED_LINK_PROPERTIES =
+            getLinkPropertiesWithName("updated_iface");
+
+    @Mock private Context mContext;
+    @Mock private VcnNetworkProvider mVcnNetworkProvider;
+    @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+    @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb;
+    @Mock private Network mNetwork;
+
+    @Captor private ArgumentCaptor<RouteSelectionCallback> mRouteSelectionCallbackCaptor;
+
+    private TestLooper mTestLooper;
+    private VcnContext mVcnContext;
+    private UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTestLooper = new TestLooper();
+        mVcnContext = spy(new VcnContext(mContext, mTestLooper.getLooper(), mVcnNetworkProvider));
+        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+        setupSystemService(
+                mContext,
+                mConnectivityManager,
+                Context.CONNECTIVITY_SERVICE,
+                ConnectivityManager.class);
+
+        when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS);
+
+        mUnderlyingNetworkTracker =
+                new UnderlyingNetworkTracker(
+                        mVcnContext,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET),
+                        mNetworkTrackerCb);
+    }
+
+    private static LinkProperties getLinkPropertiesWithName(String iface) {
+        LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName(iface);
+        return linkProperties;
+    }
+
+    private SubscriptionInfo getSubscriptionInfoForSubId(int subId) {
+        SubscriptionInfo subInfo = mock(SubscriptionInfo.class);
+        when(subInfo.getSubscriptionId()).thenReturn(subId);
+        return subInfo;
+    }
+
+    @Test
+    public void testNetworkCallbacksRegisteredOnStartup() {
+        // verify NetworkCallbacks registered when instantiated
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getWifiRequest()),
+                        any(),
+                        any(NetworkBringupCallback.class));
+        verifyBackgroundCellRequests(mSubscriptionSnapshot, SUB_GROUP, INITIAL_SUB_IDS);
+
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getRouteSelectionRequest()),
+                        any(),
+                        any(RouteSelectionCallback.class));
+    }
+
+    private void verifyBackgroundCellRequests(
+            TelephonySubscriptionSnapshot snapshot,
+            ParcelUuid subGroup,
+            Set<Integer> expectedSubIds) {
+        verify(snapshot).getAllSubIdsInGroup(eq(subGroup));
+
+        for (final int subId : expectedSubIds) {
+            verify(mConnectivityManager)
+                    .requestBackgroundNetwork(
+                            eq(getCellRequestForSubId(subId)),
+                            any(),
+                            any(NetworkBringupCallback.class));
+        }
+    }
+
+    @Test
+    public void testUpdateSubscriptionSnapshot() {
+        // Verify initial cell background requests filed
+        verifyBackgroundCellRequests(mSubscriptionSnapshot, SUB_GROUP, INITIAL_SUB_IDS);
+
+        TelephonySubscriptionSnapshot subscriptionUpdate =
+                mock(TelephonySubscriptionSnapshot.class);
+        when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS);
+
+        mUnderlyingNetworkTracker.updateSubscriptionSnapshot(subscriptionUpdate);
+
+        // verify that initially-filed bringup requests are unregistered
+        verify(mConnectivityManager, times(INITIAL_SUB_IDS.size()))
+                .unregisterNetworkCallback(any(NetworkBringupCallback.class));
+        verifyBackgroundCellRequests(subscriptionUpdate, SUB_GROUP, UPDATED_SUB_IDS);
+    }
+
+    private NetworkRequest getWifiRequest() {
+        return getExpectedRequestBase()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build();
+    }
+
+    private NetworkRequest getCellRequestForSubId(int subId) {
+        return getExpectedRequestBase()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId))
+                .build();
+    }
+
+    private NetworkRequest getRouteSelectionRequest() {
+        return getExpectedRequestBase().build();
+    }
+
+    private NetworkRequest.Builder getExpectedRequestBase() {
+        return new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addUnwantedCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+    }
+
+    @Test
+    public void testTeardown() {
+        mUnderlyingNetworkTracker.teardown();
+
+        // Expect 3 NetworkBringupCallbacks to be unregistered: 1 for WiFi and 2 for Cellular (1x
+        // for each subId)
+        verify(mConnectivityManager, times(3))
+                .unregisterNetworkCallback(any(NetworkBringupCallback.class));
+        verify(mConnectivityManager).unregisterNetworkCallback(any(RouteSelectionCallback.class));
+    }
+
+    @Test
+    public void testUnderlyingNetworkRecordEquals() {
+        UnderlyingNetworkRecord recordA =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        UnderlyingNetworkRecord recordB =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        UnderlyingNetworkRecord recordC =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        UPDATED_NETWORK_CAPABILITIES,
+                        UPDATED_LINK_PROPERTIES,
+                        false /* isBlocked */);
+
+        assertEquals(recordA, recordB);
+        assertNotEquals(recordA, recordC);
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkChange() {
+        verifyRegistrationOnAvailableAndGetCallback();
+    }
+
+    private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback() {
+        return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
+    }
+
+    private RouteSelectionCallback verifyRegistrationOnAvailableAndGetCallback(
+            NetworkCapabilities networkCapabilities) {
+        verify(mConnectivityManager)
+                .requestBackgroundNetwork(
+                        eq(getRouteSelectionRequest()),
+                        any(),
+                        mRouteSelectionCallbackCaptor.capture());
+
+        RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue();
+        cb.onAvailable(mNetwork);
+        cb.onCapabilitiesChanged(mNetwork, networkCapabilities);
+        cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES);
+        cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        networkCapabilities,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        return cb;
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        UPDATED_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForLinkPropertiesChange() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        UPDATED_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onNetworkSuspended(mNetwork);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        SUSPENDED_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkResumed() {
+        RouteSelectionCallback cb =
+                verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
+
+        cb.onNetworkResumed(mNetwork);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        false /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForBlocked() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */);
+
+        UnderlyingNetworkRecord expectedRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        INITIAL_NETWORK_CAPABILITIES,
+                        INITIAL_LINK_PROPERTIES,
+                        true /* isBlocked */);
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+    }
+
+    @Test
+    public void testRecordTrackerCallbackNotifiedForNetworkLoss() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onLost(mNetwork);
+
+        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(null);
+    }
+
+    @Test
+    public void testRecordTrackerCallbackIgnoresDuplicateRecord() {
+        RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
+
+        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+
+        // Verify no more calls to the UnderlyingNetworkTrackerCallback when the
+        // UnderlyingNetworkRecord does not actually change
+        verifyNoMoreInteractions(mNetworkTrackerCb);
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
new file mode 100644
index 0000000..e20070e
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 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.vcn;
+
+import static android.net.IpSecManager.DIRECTION_IN;
+import static android.net.IpSecManager.DIRECTION_OUT;
+
+import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for VcnGatewayConnection.ConnectedState */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
+    private VcnIkeSession mIkeSession;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
+
+        mIkeSession = mGatewayConnection.buildIkeSession();
+        mGatewayConnection.setIkeSession(mIkeSession);
+
+        mGatewayConnection.transitionTo(mGatewayConnection.mConnectedState);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testEnterStateCreatesNewIkeSession() throws Exception {
+        verify(mDeps).newIkeSession(any(), any(), any(), any(), any());
+    }
+
+    @Test
+    public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(null);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession, never()).close();
+    }
+
+    @Test
+    public void testNewNetworkTriggersMigration() throws Exception {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession, never()).close();
+        verify(mIkeSession).setNetwork(TEST_UNDERLYING_NETWORK_RECORD_2.network);
+    }
+
+    @Test
+    public void testSameNetworkDoesNotTriggerMigration() throws Exception {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testCreatedTransformsAreApplied() throws Exception {
+        for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) {
+            getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction);
+            mTestLooper.dispatchAll();
+
+            verify(mIpSecSvc)
+                    .applyTunnelModeTransform(
+                            eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any());
+        }
+
+        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testChildSessionClosedTriggersDisconnect() throws Exception {
+        getChildSessionCallback().onClosed();
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testIkeSessionClosedTriggersDisconnect() throws Exception {
+        getIkeSessionCallback().onClosed();
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession).close();
+    }
+
+    // TODO: Add tests for childOpened() when ChildSessionConfiguration can be mocked or created
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
new file mode 100644
index 0000000..d936183
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 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.vcn;
+
+import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for VcnGatewayConnection.ConnectingState */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectionTestBase {
+    private VcnIkeSession mIkeSession;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);
+        mGatewayConnection.transitionTo(mGatewayConnection.mConnectingState);
+        mTestLooper.dispatchAll();
+
+        mIkeSession = mGatewayConnection.getIkeSession();
+    }
+
+    @Test
+    public void testEnterStateCreatesNewIkeSession() throws Exception {
+        verify(mDeps).newIkeSession(any(), any(), any(), any(), any());
+    }
+
+    @Test
+    public void testNullNetworkTriggersDisconnect() throws Exception {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(null);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession).kill();
+    }
+
+    @Test
+    public void testNewNetworkTriggersReconnect() throws Exception {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession).close();
+        verify(mIkeSession, never()).kill();
+    }
+
+    @Test
+    public void testSameNetworkDoesNotTriggerReconnect() throws Exception {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testChildSessionClosedTriggersDisconnect() throws Exception {
+        getChildSessionCallback().onClosed();
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession).close();
+    }
+
+    @Test
+    public void testIkeSessionClosedTriggersDisconnect() throws Exception {
+        getIkeSessionCallback().onClosed();
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession).close();
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
new file mode 100644
index 0000000..8643d8a
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 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.vcn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for VcnGatewayConnection.DisconnectedState */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnectionTestBase {
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testEnterWhileNotRunningTriggersQuit() throws Exception {
+        final VcnGatewayConnection vgc =
+                new VcnGatewayConnection(
+                        mVcnContext,
+                        TEST_SUB_GRP,
+                        TEST_SUBSCRIPTION_SNAPSHOT,
+                        mConfig,
+                        mGatewayStatusCallback,
+                        mDeps);
+
+        vgc.setIsRunning(false);
+        vgc.transitionTo(vgc.mDisconnectedState);
+        mTestLooper.dispatchAll();
+
+        assertNull(vgc.getCurrentState());
+    }
+
+    @Test
+    public void testNetworkChangesTriggerStateTransitions() throws Exception {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testNullNetworkDoesNotTriggerStateTransition() throws Exception {
+        mGatewayConnection
+                .getUnderlyingNetworkTrackerCallback()
+                .onSelectedUnderlyingNetworkChanged(null);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testTeardown() throws Exception {
+        mGatewayConnection.teardownAsynchronously();
+        mTestLooper.dispatchAll();
+
+        assertNull(mGatewayConnection.getCurrentState());
+        verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any());
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
new file mode 100644
index 0000000..d0fec55
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 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.vcn;
+
+import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/** Tests for VcnGatewayConnection.DisconnectedState */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnectionTestBase {
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession());
+
+        mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testIkeSessionClosed() throws Exception {
+        getIkeSessionCallback().onClosed();
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState());
+    }
+
+    @Test
+    public void testTimeoutExpired() throws Exception {
+        mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS));
+        mTestLooper.dispatchAll();
+
+        verify(mMockIkeSession).kill();
+    }
+
+    @Test
+    public void testTeardown() throws Exception {
+        mGatewayConnection.teardownAsynchronously();
+        mTestLooper.dispatchAll();
+
+        // Should do nothing; already tearing down.
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index d741e5c..bc6bee2 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -16,20 +16,36 @@
 
 package com.android.server.vcn;
 
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
-import android.annotation.NonNull;
-import android.content.Context;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.net.LinkProperties;
+import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
 import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.net.vcn.VcnTransportInfo;
+import android.net.wifi.WifiInfo;
 import android.os.ParcelUuid;
-import android.os.test.TestLooper;
+import android.os.Process;
 import android.telephony.SubscriptionInfo;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,7 +57,9 @@
 /** Tests for TelephonySubscriptionTracker */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class VcnGatewayConnectionTest {
+public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
+    private static final int TEST_UID = Process.myUid();
+
     private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID());
     private static final int TEST_SIM_SLOT_INDEX = 1;
     private static final int TEST_SUBSCRIPTION_ID_1 = 2;
@@ -57,26 +75,67 @@
         TEST_SUBID_TO_GROUP_MAP = Collections.unmodifiableMap(subIdToGroupMap);
     }
 
-    @NonNull private final Context mContext;
-    @NonNull private final TestLooper mTestLooper;
-    @NonNull private final VcnNetworkProvider mVcnNetworkProvider;
-    @NonNull private final VcnGatewayConnection.Dependencies mDeps;
+    private WifiInfo mWifiInfo;
 
-    public VcnGatewayConnectionTest() {
-        mContext = mock(Context.class);
-        mTestLooper = new TestLooper();
-        mVcnNetworkProvider = mock(VcnNetworkProvider.class);
-        mDeps = mock(VcnGatewayConnection.Dependencies.class);
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mWifiInfo = mock(WifiInfo.class);
+    }
+
+    private void verifyBuildNetworkCapabilitiesCommon(int transportType) {
+        final NetworkCapabilities underlyingCaps = new NetworkCapabilities();
+        underlyingCaps.addTransportType(transportType);
+        underlyingCaps.addCapability(NET_CAPABILITY_NOT_METERED);
+        underlyingCaps.addCapability(NET_CAPABILITY_NOT_ROAMING);
+
+        if (transportType == TRANSPORT_WIFI) {
+            underlyingCaps.setTransportInfo(mWifiInfo);
+            underlyingCaps.setOwnerUid(TEST_UID);
+        } else if (transportType == TRANSPORT_CELLULAR) {
+            underlyingCaps.setAdministratorUids(new int[] {TEST_UID});
+            underlyingCaps.setNetworkSpecifier(
+                    new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_1));
+        }
+
+        UnderlyingNetworkRecord record =
+                new UnderlyingNetworkRecord(
+                        new Network(0), underlyingCaps, new LinkProperties(), false);
+        final NetworkCapabilities vcnCaps =
+                VcnGatewayConnection.buildNetworkCapabilities(
+                        VcnGatewayConnectionConfigTest.buildTestConfig(), record);
+
+        assertTrue(vcnCaps.hasTransport(TRANSPORT_CELLULAR));
+        assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING));
+        assertArrayEquals(new int[] {TEST_UID}, vcnCaps.getAdministratorUids());
+        assertTrue(vcnCaps.getTransportInfo() instanceof VcnTransportInfo);
+
+        final VcnTransportInfo info = (VcnTransportInfo) vcnCaps.getTransportInfo();
+        if (transportType == TRANSPORT_WIFI) {
+            assertEquals(mWifiInfo, info.getWifiInfo());
+        } else if (transportType == TRANSPORT_CELLULAR) {
+            assertEquals(TEST_SUBSCRIPTION_ID_1, info.getSubId());
+        }
     }
 
     @Test
-    public void testBuildNetworkCapabilities() throws Exception {
-        final NetworkCapabilities caps =
-                VcnGatewayConnection.buildNetworkCapabilities(
-                        VcnGatewayConnectionConfigTest.buildTestConfig());
+    public void testBuildNetworkCapabilitiesUnderlyingWifi() throws Exception {
+        verifyBuildNetworkCapabilitiesCommon(TRANSPORT_WIFI);
+    }
 
-        for (int exposedCapability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
-            assertTrue(caps.hasCapability(exposedCapability));
-        }
+    @Test
+    public void testBuildNetworkCapabilitiesUnderlyingCell() throws Exception {
+        verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR);
+    }
+
+    @Test
+    public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() {
+        final TelephonySubscriptionSnapshot updatedSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+        mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot);
+
+        verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot));
     }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
new file mode 100644
index 0000000..333b5b9
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 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.vcn;
+
+import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord;
+import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession;
+import static com.android.server.vcn.VcnTestUtils.setupIpSecManager;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.IpSecConfig;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.net.IpSecTunnelInterfaceResponse;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.os.ParcelUuid;
+import android.os.test.TestLooper;
+
+import com.android.server.IpSecService;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.UUID;
+
+public class VcnGatewayConnectionTestBase {
+    protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID());
+    protected static final int TEST_IPSEC_SPI_VALUE = 0x1234;
+    protected static final int TEST_IPSEC_SPI_RESOURCE_ID = 1;
+    protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2;
+    protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3;
+    protected static final int TEST_SUB_ID = 5;
+    protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE";
+    protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 =
+            new UnderlyingNetworkRecord(
+                    new Network(0),
+                    new NetworkCapabilities(),
+                    new LinkProperties(),
+                    false /* blocked */);
+    protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_2 =
+            new UnderlyingNetworkRecord(
+                    new Network(1),
+                    new NetworkCapabilities(),
+                    new LinkProperties(),
+                    false /* blocked */);
+
+    protected static final TelephonySubscriptionSnapshot TEST_SUBSCRIPTION_SNAPSHOT =
+            new TelephonySubscriptionSnapshot(
+                    Collections.singletonMap(TEST_SUB_ID, TEST_SUB_GRP), Collections.EMPTY_MAP);
+
+    @NonNull protected final Context mContext;
+    @NonNull protected final TestLooper mTestLooper;
+    @NonNull protected final VcnNetworkProvider mVcnNetworkProvider;
+    @NonNull protected final VcnContext mVcnContext;
+    @NonNull protected final VcnGatewayConnectionConfig mConfig;
+    @NonNull protected final VcnGatewayStatusCallback mGatewayStatusCallback;
+    @NonNull protected final VcnGatewayConnection.Dependencies mDeps;
+    @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker;
+
+    @NonNull protected final IpSecService mIpSecSvc;
+
+    protected VcnIkeSession mMockIkeSession;
+    protected VcnGatewayConnection mGatewayConnection;
+
+    public VcnGatewayConnectionTestBase() {
+        mContext = mock(Context.class);
+        mTestLooper = new TestLooper();
+        mVcnNetworkProvider = mock(VcnNetworkProvider.class);
+        mVcnContext = mock(VcnContext.class);
+        mConfig = VcnGatewayConnectionConfigTest.buildTestConfig();
+        mGatewayStatusCallback = mock(VcnGatewayStatusCallback.class);
+        mDeps = mock(VcnGatewayConnection.Dependencies.class);
+        mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class);
+
+        mIpSecSvc = mock(IpSecService.class);
+        setupIpSecManager(mContext, mIpSecSvc);
+
+        doReturn(mContext).when(mVcnContext).getContext();
+        doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
+        doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
+
+        doReturn(mUnderlyingNetworkTracker)
+                .when(mDeps)
+                .newUnderlyingNetworkTracker(any(), any(), any(), any(), any());
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        IpSecTunnelInterfaceResponse resp =
+                new IpSecTunnelInterfaceResponse(
+                        IpSecManager.Status.OK,
+                        TEST_IPSEC_TUNNEL_RESOURCE_ID,
+                        TEST_IPSEC_TUNNEL_IFACE);
+        doReturn(resp).when(mIpSecSvc).createTunnelInterface(any(), any(), any(), any(), any());
+
+        mMockIkeSession = mock(VcnIkeSession.class);
+        doReturn(mMockIkeSession).when(mDeps).newIkeSession(any(), any(), any(), any(), any());
+
+        mGatewayConnection =
+                new VcnGatewayConnection(
+                        mVcnContext,
+                        TEST_SUB_GRP,
+                        TEST_SUBSCRIPTION_SNAPSHOT,
+                        mConfig,
+                        mGatewayStatusCallback,
+                        mDeps);
+    }
+
+    protected IpSecTransform makeDummyIpSecTransform() throws Exception {
+        return new IpSecTransform(mContext, new IpSecConfig());
+    }
+
+    protected IkeSessionCallback getIkeSessionCallback() {
+        ArgumentCaptor<IkeSessionCallback> captor =
+                ArgumentCaptor.forClass(IkeSessionCallback.class);
+        verify(mDeps).newIkeSession(any(), any(), any(), captor.capture(), any());
+        return captor.getValue();
+    }
+
+    protected ChildSessionCallback getChildSessionCallback() {
+        ArgumentCaptor<ChildSessionCallback> captor =
+                ArgumentCaptor.forClass(ChildSessionCallback.class);
+        verify(mDeps).newIkeSession(any(), any(), any(), any(), captor.capture());
+        return captor.getValue();
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java
new file mode 100644
index 0000000..66cbf84
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2021 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.vcn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.NetworkRequest;
+import android.net.vcn.VcnConfig;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnGatewayConnectionConfigTest;
+import android.os.ParcelUuid;
+import android.os.test.TestLooper;
+
+import com.android.server.VcnManagementService.VcnSafemodeCallback;
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.Vcn.VcnGatewayStatusCallback;
+import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Set;
+import java.util.UUID;
+
+public class VcnTest {
+    private static final String PKG_NAME = VcnTest.class.getPackage().getName();
+    private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+    private static final int NETWORK_SCORE = 0;
+    private static final int PROVIDER_ID = 5;
+
+    private Context mContext;
+    private VcnContext mVcnContext;
+    private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+    private VcnNetworkProvider mVcnNetworkProvider;
+    private VcnSafemodeCallback mVcnSafemodeCallback;
+    private Vcn.Dependencies mDeps;
+
+    private ArgumentCaptor<VcnGatewayStatusCallback> mGatewayStatusCallbackCaptor;
+
+    private TestLooper mTestLooper;
+    private VcnGatewayConnectionConfig mGatewayConnectionConfig;
+    private VcnConfig mConfig;
+    private Vcn mVcn;
+
+    @Before
+    public void setUp() {
+        mContext = mock(Context.class);
+        mVcnContext = mock(VcnContext.class);
+        mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class);
+        mVcnNetworkProvider = mock(VcnNetworkProvider.class);
+        mVcnSafemodeCallback = mock(VcnSafemodeCallback.class);
+        mDeps = mock(Vcn.Dependencies.class);
+
+        mTestLooper = new TestLooper();
+
+        doReturn(PKG_NAME).when(mContext).getOpPackageName();
+        doReturn(mContext).when(mVcnContext).getContext();
+        doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
+        doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
+
+        // Setup VcnGatewayConnection instance generation
+        doAnswer((invocation) -> {
+            // Mock-within a doAnswer is safe, because it doesn't actually run nested.
+            return mock(VcnGatewayConnection.class);
+        }).when(mDeps).newVcnGatewayConnection(any(), any(), any(), any(), any());
+
+        mGatewayStatusCallbackCaptor = ArgumentCaptor.forClass(VcnGatewayStatusCallback.class);
+
+        final VcnConfig.Builder configBuilder = new VcnConfig.Builder(mContext);
+        for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
+            configBuilder.addGatewayConnectionConfig(
+                    VcnGatewayConnectionConfigTest.buildTestConfigWithExposedCaps(capability));
+        }
+        configBuilder.addGatewayConnectionConfig(VcnGatewayConnectionConfigTest.buildTestConfig());
+        mConfig = configBuilder.build();
+
+        mVcn =
+                new Vcn(
+                        mVcnContext,
+                        TEST_SUB_GROUP,
+                        mConfig,
+                        mSubscriptionSnapshot,
+                        mVcnSafemodeCallback,
+                        mDeps);
+    }
+
+    private NetworkRequestListener verifyAndGetRequestListener() {
+        ArgumentCaptor<NetworkRequestListener> mNetworkRequestListenerCaptor =
+                ArgumentCaptor.forClass(NetworkRequestListener.class);
+        verify(mVcnNetworkProvider).registerListener(mNetworkRequestListenerCaptor.capture());
+
+        return mNetworkRequestListenerCaptor.getValue();
+    }
+
+    private void startVcnGatewayWithCapabilities(
+            NetworkRequestListener requestListener, int... netCapabilities) {
+        final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder();
+        for (final int netCapability : netCapabilities) {
+            requestBuilder.addCapability(netCapability);
+        }
+
+        requestListener.onNetworkRequested(requestBuilder.build(), NETWORK_SCORE, PROVIDER_ID);
+        mTestLooper.dispatchAll();
+    }
+
+    @Test
+    public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() {
+        final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+        startVcnGatewayWithCapabilities(
+                requestListener, VcnGatewayConnectionConfigTest.EXPOSED_CAPS);
+
+        final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections();
+        assertFalse(gatewayConnections.isEmpty());
+
+        final TelephonySubscriptionSnapshot updatedSnapshot =
+                mock(TelephonySubscriptionSnapshot.class);
+
+        mVcn.updateSubscriptionSnapshot(updatedSnapshot);
+        mTestLooper.dispatchAll();
+
+        for (final VcnGatewayConnection gateway : gatewayConnections) {
+            verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot));
+        }
+    }
+
+    @Test
+    public void testGatewayEnteringSafemodeNotifiesVcn() {
+        final NetworkRequestListener requestListener = verifyAndGetRequestListener();
+        for (final int capability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
+            startVcnGatewayWithCapabilities(requestListener, capability);
+        }
+
+        // Each Capability in EXPOSED_CAPS was split into a separate VcnGatewayConnection in #setUp.
+        // Expect one VcnGatewayConnection per capability.
+        final int numExpectedGateways = VcnGatewayConnectionConfigTest.EXPOSED_CAPS.length;
+
+        final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections();
+        assertEquals(numExpectedGateways, gatewayConnections.size());
+        verify(mDeps, times(numExpectedGateways))
+                .newVcnGatewayConnection(
+                        eq(mVcnContext),
+                        eq(TEST_SUB_GROUP),
+                        eq(mSubscriptionSnapshot),
+                        any(),
+                        mGatewayStatusCallbackCaptor.capture());
+
+        // Doesn't matter which callback this gets - any Gateway entering Safemode should shut down
+        // all Gateways
+        final VcnGatewayStatusCallback statusCallback = mGatewayStatusCallbackCaptor.getValue();
+        statusCallback.onEnteredSafemode();
+        mTestLooper.dispatchAll();
+
+        for (final VcnGatewayConnection gatewayConnection : gatewayConnections) {
+            verify(gatewayConnection).teardownAsynchronously();
+        }
+        verify(mVcnNetworkProvider).unregisterListener(requestListener);
+        verify(mVcnSafemodeCallback).onEnteredSafemode();
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java b/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java
new file mode 100644
index 0000000..2b10806
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.vcn;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.net.IpSecManager;
+
+import com.android.server.IpSecService;
+
+public class VcnTestUtils {
+    /** Mock system services by directly mocking the *Manager interface. */
+    public static void setupSystemService(
+            Context mockContext, Object service, String name, Class<?> serviceClass) {
+        doReturn(name).when(mockContext).getSystemServiceName(serviceClass);
+        doReturn(service).when(mockContext).getSystemService(name);
+    }
+
+    /** Mock IpSecService by mocking the underlying service binder. */
+    public static IpSecManager setupIpSecManager(Context mockContext, IpSecService service) {
+        doReturn(Context.IPSEC_SERVICE).when(mockContext).getSystemServiceName(IpSecManager.class);
+
+        final IpSecManager ipSecMgr = new IpSecManager(mockContext, service);
+        doReturn(ipSecMgr).when(mockContext).getSystemService(Context.IPSEC_SERVICE);
+
+        // Return to ensure this doesn't get reaped.
+        return ipSecMgr;
+    }
+}
diff --git a/tools/fonts/update_font_metadata.py b/tools/fonts/update_font_metadata.py
new file mode 100755
index 0000000..c07a98a
--- /dev/null
+++ b/tools/fonts/update_font_metadata.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+import argparse
+
+from fontTools import ttLib
+
+
+def update_font_revision(font, revisionSpec):
+    if revisionSpec.startswith('+'):
+      font['head'].fontRevision += float(revisionSpec[1:])
+    else:
+      font['head'].fontRevision = float(revisionSpec)
+
+
+def main():
+    args_parser = argparse.ArgumentParser(description='Update font file metadata')
+    args_parser.add_argument('--input', help='Input otf/ttf font file.')
+    args_parser.add_argument('--output', help='Output file for updated font file.')
+    args_parser.add_argument('--revision', help='Updated font revision. Use + to update revision based on the current revision')
+    args = args_parser.parse_args()
+
+    font = ttLib.TTFont(args.input)
+    update_font_revision(font, args.revision)
+    font.save(args.output)
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/powerstats/PowerStatsServiceProtoParser.java b/tools/powerstats/PowerStatsServiceProtoParser.java
index e4135b5..04f9bf2 100644
--- a/tools/powerstats/PowerStatsServiceProtoParser.java
+++ b/tools/powerstats/PowerStatsServiceProtoParser.java
@@ -30,7 +30,7 @@
         for (int i = 0; i < proto.getChannelCount(); i++) {
             ChannelProto energyMeterInfo = proto.getChannel(i);
             csvHeader += "Index,Timestamp,Duration," + energyMeterInfo.getId()
-                + "/" + energyMeterInfo.getName() + ",";
+                + "/" + energyMeterInfo.getName() + "/" + energyMeterInfo.getSubsystem() + ",";
         }
         System.out.println(csvHeader);
     }
@@ -86,6 +86,12 @@
                     csvRow += energyConsumerResult.getId() + ","
                         + energyConsumerResult.getTimestampMs() + ","
                         + energyConsumerResult.getEnergyUws() + ",";
+                    for (int k = 0; k < energyConsumerResult.getAttributionCount(); k++) {
+                        final EnergyConsumerAttributionProto energyConsumerAttribution =
+                                energyConsumerResult.getAttribution(k);
+                        csvRow += energyConsumerAttribution.getUid() + ","
+                            + energyConsumerAttribution.getEnergyUws() + ",";
+                    }
                 }
                 System.out.println(csvRow);
             }
diff --git a/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java b/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java
index 24b1854..1d479fc 100644
--- a/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java
+++ b/wifi/java/src/android/net/wifi/nl80211/SingleScanSettings.java
@@ -32,6 +32,7 @@
     private static final String TAG = "SingleScanSettings";
 
     public int scanType;
+    public boolean enable6GhzRnr;
     public ArrayList<ChannelSettings> channelSettings;
     public ArrayList<HiddenNetwork> hiddenNetworks;
 
@@ -50,6 +51,7 @@
             return false;
         }
         return scanType == settings.scanType
+                && enable6GhzRnr == settings.enable6GhzRnr
                 && channelSettings.equals(settings.channelSettings)
                 && hiddenNetworks.equals(settings.hiddenNetworks);
     }
@@ -57,7 +59,7 @@
     /** override hash code */
     @Override
     public int hashCode() {
-        return Objects.hash(scanType, channelSettings, hiddenNetworks);
+        return Objects.hash(scanType, channelSettings, hiddenNetworks, enable6GhzRnr);
     }
 
 
@@ -83,6 +85,7 @@
             Log.wtf(TAG, "Invalid scan type " + scanType);
         }
         out.writeInt(scanType);
+        out.writeBoolean(enable6GhzRnr);
         out.writeTypedList(channelSettings);
         out.writeTypedList(hiddenNetworks);
     }
@@ -100,6 +103,7 @@
             if (!isValidScanType(result.scanType)) {
                 Log.wtf(TAG, "Invalid scan type " + result.scanType);
             }
+            result.enable6GhzRnr = in.readBoolean();
             result.channelSettings = new ArrayList<ChannelSettings>();
             in.readTypedList(result.channelSettings, ChannelSettings.CREATOR);
             result.hiddenNetworks = new ArrayList<HiddenNetwork>();
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index db2eb99..c64f4bc 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -20,6 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.AlarmManager;
@@ -28,6 +29,7 @@
 import android.net.wifi.WifiAnnotations;
 import android.net.wifi.WifiScanner;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -90,6 +92,10 @@
      */
     public static final int SCAN_TYPE_PNO_SCAN = 1;
 
+    // Extra scanning parameter used to enable 6Ghz RNR (Reduced Neighbour Support).
+    public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR =
+            "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR";
+
     private AlarmManager mAlarmManager;
     private Handler mEventHandler;
 
@@ -911,6 +917,15 @@
     }
 
     /**
+     * @deprecated replaced by {@link #startScan(String, int, Set, List, Bundle)}
+     **/
+    @Deprecated
+    public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
+            @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) {
+        return startScan(ifaceName, scanType, freqs, hiddenNetworkSSIDs, null);
+    }
+
+    /**
      * Start a scan using the specified parameters. A scan is an asynchronous operation. The
      * result of the operation is returned in the {@link ScanEventCallback} registered when
      * setting up an interface using
@@ -929,11 +944,14 @@
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
      * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that
      *                           no hidden frequencies will be scanned for.
+     * @param extraScanningParams bundle of extra scanning parameters.
      * @return Returns true on success, false on failure (e.g. when called before the interface
      * has been set up).
      */
     public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
-            @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) {
+            @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
+            @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
+            @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
         IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
         if (scannerImpl == null) {
             Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
@@ -948,6 +966,9 @@
         }
         settings.channelSettings  = new ArrayList<>();
         settings.hiddenNetworks  = new ArrayList<>();
+        if (extraScanningParams != null) {
+            settings.enable6GhzRnr = extraScanningParams.getBoolean(SCANNING_PARAM_ENABLE_6GHZ_RNR);
+        }
 
         if (freqs != null) {
             for (Integer freq : freqs) {
diff --git a/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java b/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
index 9059208..fd595fa 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
@@ -74,6 +74,7 @@
                 new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
         scanSettings.hiddenNetworks =
                 new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+        scanSettings.enable6GhzRnr = true;
 
         Parcel parcel = Parcel.obtain();
         scanSettings.writeToParcel(parcel, 0);
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 9ee0acbf..4b03a49 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -44,6 +44,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.util.HexEncoding;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -506,7 +507,51 @@
                 SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
         verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
                 IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
-                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false)));
+    }
+
+    /**
+     * Verify the new startScan() API can convert input parameters to SingleScanSettings correctly.
+     */
+    @Test
+    public void testScanWithBundle() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(WifiNl80211Manager.SCANNING_PARAM_ENABLE_6GHZ_RNR, true);
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, bundle));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, true)));
+    }
+
+    /**
+     * Verify default values in SingleScanSettings when the input Bundle to startScan is null.
+     */
+    @Test
+    public void testScanWithNullBundle() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, null));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false)));
+    }
+
+    /**
+     * Verify default values in SingleScanSettings when the input Bundle to startScan is empty.
+     */
+    @Test
+    public void testScanWithEmptyBundle() throws Exception {
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, new Bundle()));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false)));
     }
 
     /**
@@ -527,7 +572,7 @@
         // But the argument passed down should have the duplicate removed.
         verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
                 IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
-                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST, false)));
     }
 
     /**
@@ -539,7 +584,7 @@
         assertTrue(mWificondControl.startScan(
                 TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, null, null));
         verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
-                IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null)));
+                IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null, false)));
     }
 
     /**
@@ -1068,11 +1113,14 @@
         int mExpectedScanType;
         private final Set<Integer> mExpectedFreqs;
         private final List<byte[]> mExpectedSsids;
+        private final boolean mExpectedEnable6GhzRnr;
 
-        ScanMatcher(int expectedScanType, Set<Integer> expectedFreqs, List<byte[]> expectedSsids) {
+        ScanMatcher(int expectedScanType, Set<Integer> expectedFreqs, List<byte[]> expectedSsids,
+                boolean expectedEnable6GhzRnr) {
             this.mExpectedScanType = expectedScanType;
             this.mExpectedFreqs = expectedFreqs;
             this.mExpectedSsids = expectedSsids;
+            this.mExpectedEnable6GhzRnr = expectedEnable6GhzRnr;
         }
 
         @Override
@@ -1080,6 +1128,9 @@
             if (settings.scanType != mExpectedScanType) {
                 return false;
             }
+            if (settings.enable6GhzRnr != mExpectedEnable6GhzRnr) {
+                return false;
+            }
             ArrayList<ChannelSettings> channelSettings = settings.channelSettings;
             ArrayList<HiddenNetwork> hiddenNetworks = settings.hiddenNetworks;
             if (mExpectedFreqs != null) {